Как вы структурируете / сериализуете код Ruby?
Я хочу иметь возможность написать лямбда /Proc в своем коде Ruby, сериализовать его, чтобы я мог записать его на диск, а затем выполнить лямбда позже. Вроде как...
x = 40
f = lambda { |y| x + y }
save_for_later(f)
Позже, в отдельном выпуске интерпретатора Ruby, я хочу сказать...
f = load_from_before
z = f.call(2)
z.should == 42
Marshal.dump не работает для Procs. Я знаю, что в Perl есть Data:: Dump:: Streamer, и в Лиспе это тривиально. Но есть ли способ сделать это в Ruby? Другими словами, какова будет реализация save
_
за _
позже?
Редактировать: мой ответ ниже хороший, но он не закрывается по свободным переменным (например, x
) и сериализовать их вместе с лямбдой. Так что в моем примере...
x = 40
s = save_for_later { |y| x + y }
# => "lambda { |y|\n (x + y)\n}"
... вывод строки не содержит определения для x
, Есть ли решение, которое учитывает это, возможно, путем сериализации таблицы символов? Можете ли вы получить доступ к этому в Ruby?
Изменить 2: я обновил свой ответ, чтобы включить сериализацию локальных переменных. Это кажется приемлемым.
4 ответа
Используйте 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
Это здорово, но не закрывает свободные переменные (например, x
) и сериализовать их вместе с лямбдой.
Также для сериализации переменных вы можете перебирать local_variables
и сериализовать их. Проблема, однако, в том, что local_variables
изнутри save_for_later
только доступ c
а также s
в приведенном выше коде - т.е. переменные, локальные для кода сериализации, а не вызывающая сторона. Так что, к сожалению, мы должны передать захват локальных переменных и их значений вызывающей стороне.
Возможно, это хорошая вещь, потому что, как правило, поиск всех свободных переменных в фрагменте кода Ruby неразрешим. Плюс в идеале мы бы тоже сэкономили 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:0xb7cfe9c0@(eval):1>
g.call(2)
# => 42
# Changing x does not affect it.
x = 7
g.call(3)
# => 43
Используйте sourcify
Это будет работать на Ruby 1.8 или 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
Смотрите мой другой ответ для захвата свободных переменных.
Обновление: теперь вы также можете использовать gem https://github.com/ngty/serializable_proc, который использует sourcify и захватывает локальные, экземплярные, классовые и глобальные переменные.
В Ruby есть класс Marshal, у которого есть метод dump, который вы можете вызвать.
Посмотрите здесь:
http://rubylearning.com/satishtalim/object_serialization.html