Skip to content

Instantly share code, notes, and snippets.

@plexus
Created December 9, 2014 14:01
Show Gist options
  • Save plexus/42c6c9c63212182ee440 to your computer and use it in GitHub Desktop.
Save plexus/42c6c9c63212182ee440 to your computer and use it in GitHub Desktop.
# How to update values on immutable objects?
class Foo
def initialize(attrs)
@x = attrs.fetch(:x)
@y = attrs.fetch(:y)
freeze
end
end
foo = Foo.new(x: 5, y: 7)
# Option 0
# Use x=9. Would be great but doesn't work in Ruby, it will always
# return it's argument so you can chain assignments
# Option 1
class Foo
attr_reader :x, :y
def set_x(x)
self.class.new(x: x, y: y)
end
end
foo.set_x(9) # => #<Foo:0x007fbe1e7acc48 @x=9, @y=7>
foo # => #<Foo:0x007fbe1e7acd60 @x=5, @y=7>
# Option 2
Undefined = Object.new
def Undefined.inspect ; 'Undefined' ; end
class Foo
def x(x = Undefined)
if x == Undefined
@x
else
self.class.new(x: x, y: @y)
end
end
end
foo.x(9) # => #<Foo:0x007fbe1e7ac4c8 @x=9, @y=7>
foo # => #<Foo:0x007fbe1e7acd60 @x=5, @y=7>
# Option 3
# Same as #1, but with_x, apparently common in Scala/Java
# Option 4
# Anima style, use explicit "update" function
require 'anima'
class Foo
include Anima.new(:x, :y)
include Anima::Update
end
foo.update(x: 9) # => #<Foo x=9 y=7>
foo # => #<Foo x=5 y=7>
# Option 5
# ...
# Any other ideas?
@moonglum
Copy link

moonglum commented Dec 9, 2014

Option #4, but I would use copy to make it obvious that you get a new object back ๐Ÿ˜„

@tak1n
Copy link

tak1n commented Dec 9, 2014

@plexus, reading your comments about it made me switch from #1 to #3 ๐Ÿ˜„

@machisuji
Copy link

Option #4 is common in plain Scala where it's called copy. Probably for the reason @moonglum stated.

case class Foo(x: Int, y: Int)

val foo = Foo(5, 7) // => Foo(5, 7)
val bar = foo.copy(x = 9) // => Foo(9, 7)

All those options will be a pain with nested, immutable data structures, though. At that point lenses will come in handy. I'm not aware of a library for that in Ruby, though. You can probably do something more convenient with Ruby meta magic anyway, though. I'd think something like this:

Person = Sruct.new :name, :address
Address = Struct.new :name, :number

person = Person.new "Hans", Addres.new("Doubledykes Rd.", 42)

person.copy(
  name: "Richard",
  address: {
    number: 1
  })

As opposed to

person.copy(
  name: "Richard",
  address: person.address.copy(number: 1))

@plexus
Copy link
Author

plexus commented Dec 10, 2014

Great to get so many replies on this! It seems some people also name their update / copy / with method new. I've seen this and think I might have actually used it at some point, although I'm not a fan because it's different enough from the class method new to cause confusion.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment