Skip to content

janlelis/value_struct

Repository files navigation

Value Struct [version] [ci]

A value struct is a subclass of the normal Ruby struct that behaves almost the same. However, it has a major difference:

Value structs are immutable, i.e. they don't have setters (although, not recursively*)

Additionally, this gem provides the following optional mixins to make life easier when using immutable structs:

  • :dup_with_changes Extends #dup to take a optional hash for setting new values in the duplicate
  • :strict_arguments Value structs need to be initialized with the exact amount of arguments
  • :freeze Automatically freezes new instances
  • :no_clone Alters #clone to return the same object

By default, only :dup_with_changes will be included.

Without mixins, ValueStructs are almost as fast as normal structs. Some mixins add noticable overhead, e.g. strict_arguments

Why?

See this blog article for more information.

Example 1

require 'value_struct'

SimplePoint = ValueStruct.new(:x, :y)

Please refer to the documentation of Ruby's struct for more details on general struct usage.

How to use structs with mixins

Point = ValueStruct.new_with_mixins :x, :y, [
  :freeze,
  :dup_with_changes,
  :strict_arguments,
]

p = Point.new(1,2)
p.frozen?    #=> true
p.dup(x: 0)  #=> #<ValueStruct Point x=0, y=2>
Point.new(1) # ArgumentError

Alternatively, you can put custom modules in the mixin array.

Example 2

require 'value_struct'

Point = ValueStruct.new_with_mixins(
  :x,
  :y,
   [:dup_with_changes, :freeze, :no_clone],
) do

  def initialize(x,y)
    raise ArgumentError, 'points must be initialized with two numerics' unless x.is_a?(Numeric) && y.is_a?(Numeric)
    super(x,y)
  end

  def abs
    ( x**2 + y**2 ) ** 0.5
  end

  def +(o)
    dup(x: x + o.x, y: y + o.y)
  end

  def -(o)
    dup(x: x - o.x, y: y - o.y)
  end

  def +@
    self
  end

  def -@
    dup(x: -x, y: -o.y)
  end

  def to_c
    Complex(x,y)
  end

  def to_s
    "(#{x},#{y})"
  end
  alias inspect to_s
end

*

Because of the nature of Ruby, most things are not really immutable. So if you have an attribute :by and initialize it with an array, you cannot change the value struct anymore, but still the array:

Ru = ValueStruct.new(:by)
ruby = Ru.by([1,2,3])
ruby.by # => [1,2,3]

ruby.by = [1,2,3,4] # not possible
ruby.by << 4        # possible

Install

$ gem install value_struct

Influenced by / Thanks to

J-_-L

Copyright © 2012-2015 Jan Lelis, janlelis.com, released under the MIT license.