Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I deep copy a Proc in Ruby?

Is there a straightforward way in Ruby to produce a copy of a Proc?

I have a Proc called @foo. I want another method to periodically augment @foo with additional logic. For example:

# create initial Proc
@foo = lambda { |x| x }

# augment with more logic
@foo = lambda { |x| x > 1 ? x*x : @foo[x] }

I don't want the second line that does the augmentation to produce a recursive function. Instead, I want @foo to be bound by value into the lexical scope of the new @foo definition, producing a function that looks more like this:

@foo = lambda { |x| x > 1 ? x*x : lambda{ |x| x }[x] }

I get an infinite recursion and an eventual stack overflow instead, due to the resulting function looking like this:

@foo = lambda { |x| x > 1 ? x*x : lambda { |x| x > 1 ? x*x : { lambda |x| # etc...

I'd like the code to be like this:

# augment with more logic
@foo = lambda { |x| x > 1 ? x*x : (@foo.clone)[x] }

but clone doesn't work on Procs.

Additionally, the standard Ruby deep copy hack, using marshal and unmarshal, doesn't work on Procs either. Is there some way to do this?

like image 883
sgibbons Avatar asked Nov 28 '10 19:11

sgibbons


People also ask

How to deep copy in Ruby?

Ruby does provide two methods for making copies of objects, including one that can be made to do deep copies. The Object#dup method will make a shallow copy of an object. To achieve this, the dup method will call the initialize_copy method of that class. What this does exactly is dependent on the class.

How to make a copy of an object in Ruby?

You generally use #clone if you want to copy an object including its internal state. This is what Rails is using with its #dup method on ActiveRecord. It uses #dup to allow you to duplicate a record without its "internal" state (id and timestamps), and leaves #clone up to Ruby to implement.


1 Answers

Even if clone would work on Procs, it wouldn't help you, because you'd still be calling clone on the new value of @foo, not on the previous one like you want.

What you can do instead is just store the old value of @foo in a local variable that the lambda can close over.

Example:

def augment_foo()
  old_foo = @foo
  @foo = lambda { |x| x > 1 ? x*x : old_foo[x] }
end

This way old_foo will refer to the value that @foo had when augment_foo was called and everything will work as you want.

like image 66
sepp2k Avatar answered Sep 29 '22 20:09

sepp2k