Как вы структурируете / сериализуете код Ruby? - PullRequest
16 голосов
/ 14 октября 2008

Я хочу иметь возможность записывать лямбда / процесс в моем коде 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<code>_ для _ позже ?

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

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

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

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

Ответы [ 4 ]

11 голосов
/ 20 мая 2011

Использование 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

См. мой другой ответ для получения свободных переменных.

Обновление : Теперь вы также можете использовать гем serializable_proc , который использует sourcify и захватывает локальные, экземпляры, класс и глобальные переменные.

11 голосов
/ 14 октября 2008

Использовать 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
2 голосов
/ 14 октября 2008

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

0 голосов
/ 14 октября 2008

В Ruby есть класс Marshal, у которого есть метод dump, который вы можете вызвать.

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

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

...