How do you stringize/serialize Ruby code?

前端 未结 4 483
后悔当初
后悔当初 2020-12-16 13:32

I want to be able to write a lambda/Proc in my Ruby code, serialize it so that I can write it to disk, and then execute the lambda later. Sort of like...

x          


        
相关标签:
4条回答
  • 2020-12-16 14:09

    Ruby has the Marshal class that has a dump method that you can call.

    Take a look here:

    http://rubylearning.com/satishtalim/object_serialization.html

    0 讨论(0)
  • 2020-12-16 14:13

    Use Ruby2Ruby

    def save_for_later(&block)
      return nil unless block_given?
    
      c = Class.new
      c.class_eval do
        define_method :serializable, &block
      end
      s = Ruby2Ruby.translate(c, :serializable)
      s.sub(/^def \S+\(([^\)]*)\)/, 'lambda { |\1|').sub(/end$/, '}')
    end
    
    x = 40
    s = save_for_later { |y| x + y }
    # => "lambda { |y|\n  (x + y)\n}"
    g = eval(s)
    # => #<Proc:0x4037bb2c@(eval):1>
    g.call(2) 
    # => 42
    

    This is great, but it does not close over free variables (like x) and serialize them along with the lambda.

    To serialize variables also, you can iterate over local_variables and serialize them as well. The problem, though, is that local_variables from within save_for_later accesses only c and s in the code above -- i.e. variables local to the serialization code, not the caller. So unfortunately, we must push the grabbing of local variables and their values to the caller.

    Maybe this is a good thing, though, because in general, finding all free variables in a piece of Ruby code is undecidable. Plus, ideally we would also save global_variables and any loaded classes and their overridden methods. This seems impractical.

    Using this simple approach, you get the following:

    def save_for_later(local_vars, &block)
      return nil unless block_given?
    
      c = Class.new
      c.class_eval do
        define_method :serializable, &block
      end
      s = Ruby2Ruby.translate(c, :serializable)
      locals = local_vars.map { |var,val| "#{var} = #{val.inspect}; " }.join
      s.sub(/^def \S+\(([^\)]*)\)/, 'lambda { |\1| ' + locals).sub(/end$/, '}')
    end
    
    x = 40
    s = save_for_later(local_variables.map{ |v| [v,eval(v)] }) { |y| x + y }
    # => "lambda { |y| _ = 40; x = 40;\n  (x + y)\n}"
    
    # In a separate run of Ruby, where x is not defined...
    g = eval("lambda { |y| _ = 40; x = 40;\n  (x + y)\n}")
    # => #<Proc:0xb7cfe9c0@(eval):1>
    g.call(2)
    # => 42
    
    # Changing x does not affect it.
    x = 7
    g.call(3)
    # => 43
    
    0 讨论(0)
  • 2020-12-16 14:14

    Use sourcify

    This will work on Ruby 1.8 or 1.9.

    def save_for_later(&block)
      block.to_source
    end
    
    x = 40
    s = save_for_later {|y| x + y }
    # => "proc { |y| (x + y) }"
    g = eval(s)
    # => #<Proc:0x00000100e88450@(eval):1>
    g.call(2) 
    # => 42
    

    See my other answer for capturing free variables.

    Update: Now you can also use the serializable_proc gem, which uses sourcify, and captures local, instance, class, and global variables.

    0 讨论(0)
  • 2020-12-16 14:15

    Check out the answers to this question.

    0 讨论(0)
提交回复
热议问题