Как вы структурируете / сериализуете код Ruby?

Asked
Viewd6973

16

Я хочу иметь возможность написать лямбда / 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 имеет данные :: Dump :: Streamer , а в Лиспе это тривиально. Но есть ли способ сделать это в Ruby? Другими словами, какова будет реализация save_ для _ позже ?

Изменить : Мой ответ ниже - это хорошо, но оно не закрывает свободные переменные (например, x) и не сериализует их вместе с лямбда. Итак, в моем примере ...

 x = 40
s = save_for_later { |y| x + y }
# => "lambda { |y|\n  (x + y)\n}"
 

... строковый вывод не включает определение для x. Есть ли решение, которое учитывает это, возможно, путем сериализации таблицы символов? Можете ли вы получить к нему доступ в Ruby?

Изменить 2 : я обновил свой ответ, добавив сериализацию локальных переменных. Это кажется приемлемым.

4 ответов

12

Используйте 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 { ||').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 выполняется неразрешимый . Кроме того, в идеале мы бы также сохранили 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 { || ' + 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
 
  • В моей версии Ruby2Ruby (1.2.4), похоже, нет метода translate. Есть ли другой API для этого в новых версиях?

    Stephen McCarthy02 марта 2010, 21:24
  • Я не совсем понимаю, но этот поток рекомендует перейти на Ruby2Ruby 1.2.2. Я также нашел этот патч обезьяны , который утверждает, что добавляет его обратно в более поздние версии. Хотя еще не пробовал.

    Jonathan Tran04 марта 2010, 02:59
2

Ознакомьтесь с ответами на этот вопрос .

-9

В Ruby есть класс Marshal с вызываемым методом дампа.

Взгляните сюда:

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

  • Marshal на самом деле не работает над Procs, о чем и спрашивается.

    Kyle Burton14 октября 2008, 00:56