Ответ 1
Использовать 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:[email protected](eval):1>
g.call(2)
# => 42
Это замечательно, но оно не закрывается над свободными переменными (например, x
) и сериализует их вместе с лямбдой.
Чтобы сериализовать переменные, вы также можете перебирать local_variables
и сериализовывать их. Проблема состоит в том, что local_variables
из save_for_later
получает доступ только к c
и s
в приведенном выше коде, т.е. Переменные, локальные для кода сериализации, а не вызывающий. Поэтому, к сожалению, мы должны подтолкнуть захвата локальных переменных и их значений вызывающему.
Может быть, это хорошо, хотя, поскольку в общем случае найти все свободные переменные в куске кода Ruby undecidable, Плюс, в идеале мы также сохранили бы global_variables
и любые загруженные классы и их переопределенные методы. Это кажется непрактичным.
Используя этот простой подход, вы получаете следующее:
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:[email protected](eval):1>
g.call(2)
# => 42
# Changing x does not affect it.
x = 7
g.call(3)
# => 43