How do I marshal a lambda (Proc) in Ruby?
Asked Answered
B

7

21

Joe Van Dyk asked the Ruby mailing list:

Hi,

In Ruby, I guess you can't marshal a lambda/proc object, right? Is that possible in lisp or other languages?

What I was trying to do:

l = lamda { ... }
Bj.submit "/path/to/ruby/program", :stdin => Marshal.dump(l)

So, I'm sending BackgroundJob a lambda object, which contains the context/code for what to do. But, guess that wasn't possible. I ended up marshaling a normal ruby object that contained instructions for what to do after the program ran.

Joe

Boffa answered 23/8, 2008 at 4:22 Comment(0)
D
21

You cannot marshal a Lambda or Proc. This is because both of them are considered closures, which means they close around the memory on which they were defined and can reference it. (In order to marshal them you'd have to Marshal all of the memory they could access at the time they were created.)

As Gaius pointed out though, you can use ruby2ruby to get a hold of the string of the program. That is, you can marshal the string that represents the ruby code and then reevaluate it later.

Draggletailed answered 1/9, 2008 at 23:10 Comment(2)
ruby2ruby only works on 1.8, there is no official way to deserialize bytecode of 1.9 yet.Redbird
Ruby2ruby is working in MRI 1.9 for some time. Ripper is also cool and it comes with MRI (since 1.9).Christiano
C
12

you could also just enter your code as a string:

code = %{
    lambda {"hello ruby code".split(" ").each{|e| puts e + "!"}}
}

then execute it with eval

eval code

which will return a ruby lamda.

using the %{} format escapes a string, but only closes on an unmatched brace. i.e. you can nest braces like this %{ [] {} } and it's still enclosed.

most text syntax highlighters don't realize this is a string, so still display regular code highlighting.

Cherilyncherilynn answered 14/9, 2010 at 0:14 Comment(0)
N
4

If you're interested in getting a string version of Ruby code using Ruby2Ruby, you might like this thread.

Nicotinism answered 4/3, 2010 at 3:13 Comment(0)
B
3

Try ruby2ruby

Boffa answered 23/8, 2008 at 4:24 Comment(0)
H
1

I've found proc_to_ast to do the best job: https://github.com/joker1007/proc_to_ast.

Works for sure in ruby 2+, and I've created a PR for ruby 1.9.3+ compatibility(https://github.com/joker1007/proc_to_ast/pull/3)

Halfcock answered 10/11, 2015 at 0:7 Comment(1)
This is an interesting solution, though afaict it looks like it does require the source for the proc to be present on disk. This makes sense; it would be non-trivial (perhaps impossible) to decompile YARV bytecode back into an AST. YARV discards the original AST when it is no longer needed, but a custom bytecode compiler could retain the original AST, making it possible for this technique to also work with dynamically-generated procs (i.e. created with "eval").Postremogeniture
P
1

Once upon a time, this was possible using ruby-internal gem (https://github.com/cout/ruby-internal), e.g.:

p = proc { 1 + 1 }    #=> #<Proc>
s = Marshal.dump(p)   #=> #<String>
u = Marshal.load(s)   #=> #<UnboundProc>
p2 = u.bind(binding)  #=> #<Proc>
p2.call()             #=> 2

There are some caveats, but it has been many years and I cannot remember the details. As an example, I'm not sure what happens if a variable is a dynvar in the binding where it is dumped and a local in the binding where it is re-bound. Serializing an AST (on MRI) or bytecode (on YARV) is non-trivial.

The above code works on YARV (up to 1.9.3) and MRI (up to 1.8.7). There's no reason why it cannot be made to work on Ruby 2.x, with a small amount of effort.

Postremogeniture answered 16/9, 2018 at 10:11 Comment(0)
N
0

If proc is defined into a file, U can get the file location of proc then serialize it, then after deserialize use the location to get back to the proc again

proc_location_array = proc.source_location

after deserialize:

file_name = proc_location_array[0]

line_number = proc_location_array[1]

proc_line_code = IO.readlines(file_name)[line_number - 1]

proc_hash_string = proc_line_code[proc_line_code.index("{")..proc_line_code.length]

proc = eval("lambda #{proc_hash_string}")

Nanna answered 11/4, 2017 at 17:20 Comment(1)
This only works for one line declarationsChantry

© 2022 - 2024 — McMap. All rights reserved.