16

Ruby コードにラムダ/Proc を記述し、それをシリアル化してディスクに書き込み、後でラムダを実行できるようにしたいと考えています。みたいな...

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があることは知っていますが、Lisp ではこれは些細なことです。しかし、Rubyでそれを行う方法はありますか? 言い換えれば、の実装は何でしょうか?save_for_later

編集以下の私の答えはいいですが、自由変数( など)を閉じずx、ラムダとともにそれらをシリアル化します。だから私の例では...

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

... 文字列出力に の定義が含まれていませんx。おそらくシンボルテーブルをシリアル化することによって、これを考慮した解決策はありますか? Rubyでアクセスできますか?

編集 2 : シリアル化されたローカル変数を組み込むように回答を更新しました。これは許容できるようです。

4

4 に答える 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 { |\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のみアクセスすることです。つまり、呼び出し元ではなく、シリアライゼーション コードに対してローカルな変数です。残念ながら、ローカル変数とその値のグラブを呼び出し元にプッシュする必要があります。cs

一般に、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
于 2008-10-14T02:16:44.040 に答える
11

ソース化を使用する

これは 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

自由変数をキャプチャするための私の他の回答を参照してください。

更新: sourcify を使用し、ローカル、インスタンス、クラス、およびグローバル変数をキャプチャする serializable_proc gemも使用できるようになりました。

于 2011-05-20T13:30:14.927 に答える
2

この質問に対する回答を確認してください。

于 2008-10-14T01:40:31.633 に答える
-9

Ruby には、呼び出すことができるダンプ メソッドを持つ Marshal クラスがあります。

ここを見てください:

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

于 2008-10-14T00:49:11.190 に答える