ラムダを使用する場合、Proc.newを使用する場合


336

Ruby 1.8では、一方でproc / lambdaと他方で微妙な違いがありますProc.new

  • それらの違いは何ですか?
  • どれを選択するかを決める方法についてのガイドラインを教えてください。
  • Ruby 1.9では、procとlambdaが異なります。どうしたんだ?

3
参照:MatzとFlanaganによるRubyプログラミング言語の本。このトピックを包括的にカバーしています。procはブロックのように動作します-セマンティクスを生成しますが、lambdaはメソッドのように動作します-メソッド呼び出しセマンティクス。また、リターン、ブレイクなど。procsのnはラムダのすべての振る舞いの差分
Gishu


あなたはprocとlambdaの違いを言っているだけの答えを受け入れましたが、質問のタイトルはそれらをいつ使うべきか
Shri

回答:


378

で作成されprocsの間にもう一つの重要な微妙な違いlambdaを使用して作成し、procsのはProc.new、彼らがどのように処理するかであるreturn文を:

  • -作成されたlambdaプロシージャでは、returnステートメントはプロシージャ自体からのみ返されます
  • -作成されたProc.newプロシージャでは、returnステートメントは少し意外です。これは、プロシージャからだけでなく、プロシージャを囲むメソッドからも制御を返します

これがlambda-created procのreturn動作です。それはおそらくあなたが期待する方法で動作します:

def whowouldwin

  mylambda = lambda {return "Freddy"}
  mylambda.call

  # mylambda gets called and returns "Freddy", and execution
  # continues on the next line

  return "Jason"

end


whowouldwin
#=> "Jason"

これがProc.new-created proc returnが同じことをしているところです。Rubyが非常に自慢している最小サプライズの原則を破るケースの1つを見ようとしています。

def whowouldwin2

  myproc = Proc.new {return "Freddy"}
  myproc.call

  # myproc gets called and returns "Freddy", 
  # but also returns control from whowhouldwin2!
  # The line below *never* gets executed.

  return "Jason"

end


whowouldwin2         
#=> "Freddy"

この驚くべき動作のおかげで(タイピングが減っただけでなく)、Procを作成lambdaするProc.newときよりも使用することを好む傾向があります。


12
そしてそのproc方法もあります。それは単に省略形Proc.newですか?
panzi

6
@panzi、はい、proc同等ですProc.new
ma11hew28

4
私のテストでは@mattdipasquale、procのような行為lambdaはないようにProc.newreturn文に関して。つまり、ruby docは不正確です。
ケルビン

31
@mattdipasqualeすみません、私は半分しか正しかった。1.8のprocようlambdaに動作しますがProc.new、1.9のように動作します。Peter Wagenetの回答を参照してください。
ケルビン

55
なぜこの「意外な」振る舞いなのですか?A lambdaは匿名メソッドです。これはメソッドであるため、値を返し、それを呼び出したメソッドは、それを無視して別の値を返すことを含め、必要に応じてそれを実行できます。A Procは、コードスニペットの貼り付けに似ています。メソッドのようには動作しません。したがって、内でリターンが発生した場合、Procそれはそれを呼び出したメソッドのコードの一部にすぎません。
Arcolye

96

さらに明確にするために:

ジョーイは、の復帰行動Proc.newは意外であると言います。ただし、Proc.newがブロックのように動作すると考えると、これはブロックの動作とまったく同じであるため、当然のことです。一方、ランバはメソッドのように動作します。

これは実際に、Procがアリティ(引数の数)に関して柔軟であるのに対し、ラムダは柔軟でない理由を説明しています。ブロックでは、すべての引数を指定する必要はありませんが、メソッドは必要です(デフォルトが指定されていない場合)。ラムダ引数のデフォルトを提供することはRuby 1.8ではオプションではありませんが、Ruby 1.9では代替のラムダ構文(webmatで言及されている)でサポートされるようになりました。

concat = ->(a, b=2){ "#{a}#{b}" }
concat.call(4,5) # => "45"
concat.call(1)   # => "12"

そして、Michiel de Mare(OP)は、Ruby 1.9のアリティで同じように動作するProcsとlambdaについては正しくありません。上記のように1.8からの動作を維持していることを確認しました。

breakステートメントは実際にはProcsでもラムダでもあまり意味がありません。Procsでは、ブレークにより、既に完了しているProc.newから戻ります。そして、ラムダは本質的にメソッドなので、ラムダからブレークしても意味がなく、メソッドのトップレベルからブレークすることは決してありません。

nextredo、およびraise手続きオブジェクトおよびラムダの両方で同じように動作。一方retry、どちらでも許可されておらず、例外が発生します。

最後に、このprocメソッドは一貫性がなく、予期しない動作をするため、使用しないでください。Ruby 1.8では、実際にはラムダを返します!Ruby 1.9ではこれが修正され、Procが返されます。Procを作成する場合は、を使用しProc.newます。

詳細については、この情報のほとんどの情報源であるオライリーのRubyプログラミング言語を強くお勧めします。


1
"" "ただし、Proc.newがブロックのように動作すると考えると、これはブロックの動作とまったく同じであるため、当然のことです。" "" <-ブロックはオブジェクトの一部であり、Proc.newはオブジェクトを作成します。lambdaとProc.newはどちらも、クラスがProcのオブジェクトを作成します。なぜdiffなのですか?
2014年

1
ルビー2.5、のようにbreak手続きオブジェクトから上昇させるLocalJumpErrorのに対し、break同じようラムダの挙動からreturnすなわちreturn nil)。
坂野雅

43

私が見つかりました。このページの間のショーどのような違いをProc.newしてlambdaいます。このページによると、唯一の違いは、ラムダが受け入れる引数の数について厳密であるのに対し、Proc.new欠落している引数をに変換することnilです。違いを示すIRBセッションの例を次に示します。

irb(main):001:0> l =ラムダ{| x、y | x + y}
=>#<Proc:0x00007fc605ec0748 @(irb):1>
irb(main):002:0> p = Proc.new {| x、y | x + y}
=>#<Proc:0x00007fc605ea8698 @(irb):2>
irb(main):003:0> l.call "hello"、 "world"
=> "helloworld"
irb(main):004:0> p.call "hello"、 "world"
=> "helloworld"
irb(main):005:0> l.call "hello"
ArgumentError:引数の数が正しくありません(1対2)
    (irb):1から
    from(irb):5:in `call '
    (irb)から:5
    から:0
irb(main):006:0> p.call "hello"
TypeError:nilを文字列に変換できません
    from(irb):2:in `+ '
    (irb)から:2
    from(irb):6:in `call '
    (irb)から:6
    から:0

このページでは、特にエラー耐性のある動作が必要でない限り、ラムダの使用を推奨しています。私はこの感情に同意します。ラムダを使用する方が少し簡潔に思え、そのようなわずかな違いで、平均的な状況ではより良い選択のようです。

Ruby 1.9については、申し訳ありませんが、まだ1.9を調べていませんが、それほど変更されているとは思いません(ただし、私の言葉を使わないでください。いくつかの変更を聞いたことがあるようです。私はおそらく間違っています)。


2
プロシージャも、ラムダとは異なる方法で戻ります。
Cam

"" "Proc.newは欠落している引数をnilに変換します" "" Proc.newも余分な引数を無視します(もちろん、lambdaはエラーを報告します)。
2014年

16

Procは古いですが、returnのセマンティクスは私には非常に直観に反しています(少なくとも私が言語を学んでいたとき)。

  1. procを使用している場合、おそらく何らかの機能パラダイムを使用しています。
  2. Procは、基本的にgotoであり、本質的に非常に非機能的な囲みスコープ(以前の応答を参照)から戻ることができます。

Lambdaは機能的に安全であり、推論が容易です。私は常にprocの代わりにそれを使用しています。


11

微妙な違いについてはあまり言えません。ただし、Ruby 1.9ではラムダとブロックのオプションのパラメーターが許可されていることを指摘できます。

これは1.9以下のスタビーラムダの新しい構文です:

stabby = ->(msg='inside the stabby lambda') { puts msg }

Ruby 1.8にはその構文はありませんでした。また、ブロック/ラムダを宣言する従来の方法はオプションの引数をサポートしていませんでした:

# under 1.8
l = lambda { |msg = 'inside the stabby lambda'|  puts msg }
SyntaxError: compile error
(irb):1: syntax error, unexpected '=', expecting tCOLON2 or '[' or '.'
l = lambda { |msg = 'inside the stabby lambda'|  puts msg }

ただし、Ruby 1.9は古い構文でもオプションの引数をサポートしています。

l = lambda { |msg = 'inside the regular lambda'|  puts msg }
#=> #<Proc:0x0e5dbc@(irb):1 (lambda)>
l.call
#=> inside the regular lambda
l.call('jeez')
#=> jeez

LeopardまたはLinux用のRuby1.9をビルドする場合は、この記事(恥知らずなセルフプロモーション)をチェックしてください


ラムダ内のオプションのパラメーターが大いに必要でした、1.9で追加してよかったです。ブロックもオプションのパラメータを持つことができると思います(1.9)?
mpd 2010

デフォルトのパラメーターをブロックで示すのではなく、ラムダのみを示します
iconoclast

11

短い答え:重要なのは何をするかreturnです:lambdaはそれ自体から戻り、procはそれ自体とそれを呼び出した関数から戻ります。

あまり明確でないのは、それぞれを使用する理由です。ラムダは、関数型プログラミングの意味で私たちがすべきことを期待しています。これは基本的に、現在のスコープが自動的にバインドされる匿名メソッドです。2つのうち、ラムダはおそらく使用すべきものです。

一方、Procは、言語自体を実装するのに非常に役立ちます。たとえば、「if」ステートメントまたは「for」ループを実装できます。プロシージャで見つかった戻り値は、それを呼び出したメソッドから返されます。「if」ステートメントだけではありません。これが言語のしくみであり、 "if"ステートメントのしくみでもあるので、Rubyはこれを裏で使用していて、強力なように見えただけだと思います。

ループやif-else構成などの新しい言語構成を作成する場合にのみ、これが本当に必要になります。


1
「lambdaはそれ自体から戻り、procはそれ自体から戻り、それを呼び出した関数」は明らかに間違っており、よくある誤解です。プロシージャはクロージャであり、それを作成したメソッドから戻ります。ページの別の場所にある私の完全な回答を参照してください。
ComDubh 2018年

10

それを確認する良い方法は、ラムダが独自のスコープで(それがメソッド呼び出しであるかのように)実行されることですが、Procsは呼び出し元のメソッドとインラインで実行されていると見なされることがあります。いずれの場合にも。


8

Questonの3番目のメソッドである「proc」のコメントには気付かなかったが、1.8と1.9では処理が異なっていた。

これは、3つの類似した呼び出しの違いを簡単に確認できるかなり冗長な例です。

def meth1
  puts "method start"

  pr = lambda { return }
  pr.call

  puts "method end"  
end

def meth2
  puts "method start"

  pr = Proc.new { return }
  pr.call

  puts "method end"  
end

def meth3
  puts "method start"

  pr = proc { return }
  pr.call

  puts "method end"  
end

puts "Using lambda"
meth1
puts "--------"
puts "using Proc.new"
meth2
puts "--------"
puts "using proc"
meth3

1
Matzは、procとProc.newが異なる結果を返すのが混乱していたため、廃止する予定であると述べていました。ただし、1.9では動作は同じです(procはProc.newのエイリアスです)。eigenclass.org/hiki/Changes+in+Ruby+1.9#l47
デイブRapin

@banister:proc1.8でラムダを返しました。1.9ではprocを返すように修正されましたが、これは重大な変更です。したがって、
今後は

つるはしは脚注でどこかにprocが実質的に値下げされていると言っていると思います。正確なページ番号がわかりません。
dertoni

7

Rubyのクロージャーは、Rubyでのブロック、ラムダ、プロシージャの動作の概要です。


「関数は複数のブロックを受け入れることができない-クロージャーを値として自由に渡すことができるという原則に違反している」を読んだ後、私はこれを読むのをやめました。ブロックはクロージャーではありません。プロシージャはあり、関数は複数のプロシージャを受け入れることができます。
ComDubh 2018

5

ラムダは他の言語と同様に期待どおりに動作します。

有線Proc.newは意外で紛らわしいです。

returnによって作成されたproc のステートメントはProc.new、それ自体からだけでなく、それを囲むメソッドからも制御を返します。

def some_method
  myproc = Proc.new {return "End."}
  myproc.call

  # Any code below will not get executed!
  # ...
end

Proc.newブロックのように、それがコードを囲んでいるメソッドに挿入することを主張できます。ただしProc.new、オブジェクトを作成しますが、ブロックはオブジェクトの一部です

また、ラムダとには別の違いがありProc.newます。それは、(間違った)引数の処理です。lambdaはそれについて文句を言う一方、Proc.new余分な引数を無視するか、引数がないことをnilと見なします。

irb(main):021:0> l = -> (x) { x.to_s }
=> #<Proc:0x8b63750@(irb):21 (lambda)>
irb(main):022:0> p = Proc.new { |x| x.to_s}
=> #<Proc:0x8b59494@(irb):22>
irb(main):025:0> l.call
ArgumentError: wrong number of arguments (0 for 1)
        from (irb):21:in `block in irb_binding'
        from (irb):25:in `call'
        from (irb):25
        from /usr/bin/irb:11:in `<main>'
irb(main):026:0> p.call
=> ""
irb(main):049:0> l.call 1, 2
ArgumentError: wrong number of arguments (2 for 1)
        from (irb):47:in `block in irb_binding'
        from (irb):49:in `call'
        from (irb):49
        from /usr/bin/irb:11:in `<main>'
irb(main):050:0> p.call 1, 2
=> "1"

ところで、procRuby 1.8ではラムダを作成しますが、Ruby 1.9+ではのように動作しますがProc.new、これは本当に混乱します。


3

アコーディオンガイの対応について詳しく説明するには:

Proc.newブロックが渡されることでproc out が作成されることに注意してください。lambda {...}これは、ブロックを渡すメソッド呼び出しではなく、一種のリテラルとして解析されると思います。 returnメソッド呼び出しにアタッチされたブロックの内部から実行すると、ブロックではなくメソッドから返されProc.newます。ケースは、この例です。

(これは1.8です。これが1.9にどのように変換されるかはわかりません。)


3

私はこれに少し遅れていますがProc.new、コメントで言及されていないことについて、1つの素晴らしいがほとんど知られていないことが1つあります。などによるドキュメント

Proc::newブロックがアタッチされているメソッド内でのみ、ブロックなしで呼び出すことができます。その場合、そのブロックはProcオブジェクトに変換されます。

そうは言っても、Proc.newyieldingメソッドを連鎖させましょう:

def m1
  yield 'Finally!' if block_given?
end

def m2
  m1 &Proc.new
end

m2 { |e| puts e } 
#⇒ Finally!

興味深いのは、で&block引数を宣言するのと同じことdefですが、def argリストでそれを行う必要はありません。
jrochkind 2016

2

returnprocでは、字句的に囲んでいるメソッド、つまりprocを呼び出したメソッドではなくprocが作成されたメソッドから戻ることを強調する価値があります。これは、プロシージャのクロージャプロパティの結果です。したがって、次のコードは何も出力しません。

def foo
  proc = Proc.new{return}
  foobar(proc)
  puts 'foo'
end

def foobar(proc)
  proc.call
  puts 'foobar'
end

foo

procはで実行されますがfoobar、それはで作成されたfooため、単にreturn終了するのfooではなく、終了しますfoobar。Charles Caldwellが上で書いたように、それはそれにGOTOの感じを持っています。私の意見でreturnは、レキシカルコンテキストで実行されるブロックでは問題ありませんが、別のコンテキストで実行されるprocで使用すると、直感性が大幅に低下します。


1

との動作の違いはreturn、IMHOと2の最も重要な違いです。Proc.newよりも入力が少ないため、ラムダも好みます:-)


2
更新:を使用してprocを作成できるようになりましたproc {}。これがいつ有効になったかはわかりませんが、Proc.newと入力するよりも(少し)簡単です。
aceofbassgreg 2013
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.