RubyでのProc/lambda

11487 ワード

Rubyの中のProcは、2種類ありますが、1つはProcで1つはLambdaで、lambdaを通ることができますか?lambdaであるかどうかを検出します.実はlambdaはprocのもう一つの形態です.
 >  ->{}  #     lambda
 => #<Proc:0x007fc3fb809e60@(irb):46 (lambda)>  #         Proc  
>  ->{}.class 
=> Proc #    Proc  

ProcとLambdaの違いは、Procはコードインプラントに相当し、Lambdaは関数呼び出しなので、Procはコードブロックに翻訳できますが、Lambdaは匿名関数と呼ばれます.Lambdaは匿名関数であるため、パラメータが間違っているときに異常を投げ出すが、procにはこのような問題はなく、次にproc内でreturnは呼び出し全体を直ちに返し、後のコードは実行されないので、lambdaはできないので、procにreturn文を書くべきではない.
Procの作成方法は2つあります.
Proc.new{|x| x + 1 }
proc{|x| x + 1 }

本当にprocなのか、lambdaなのかを調べることができます.
>  Proc.new{}.lambda?
=> false 
>  proc{}.lambda?
=> false

注:Ruby 1.8において、Kernel#proc()は、実はKernel#lambda()の別名であり、Rubyistsの反対多数を受けているため、Ruby 1にある.9バージョンでは、Kernel#proc()がProcになりました.new()の別名です.Lambdaにも2種類あります
lambda{|x| x + 1 }
-> x { x + 1 }

Lambdaであるかどうかを確認します.
>  lambda{}.lambda?
=> true
>  ->{}.lambda?
=> true

ProcとLambdaには、次の4つの呼び出し方法があります.
pc = Proc.new{|x| x + 1 } 
pc.call(x)
pc[x]
pc.(x) # Ruby1.9   
pc===x # Ruby1.9   

最後の==3つの等号については、資料が非常に少なく、単一のパラメータ呼び出しproc/lambdaは問題ありませんが、複数のパラメータがある場合:
>  proc{|x,y| x+y } === 1,2
=> SyntaxError: (irb):189: syntax error, unexpected ',', expecting end-of-input

呼び出しができず、構文解析にエラーが発生したことがわかりました.
>  proc{|x,y| x+y } === [1,2]
=> 3

呼び出しに成功しました.procは配列の伝達をサポートし、配列を自動的に展開するので、通常呼び出すことができます.しかしlambdaの場合
>  lambda{|x,y| x+y } === 1, 2
=> SyntaxError: (irb):191: syntax error, unexpected ',', expecting end-of-input
>  lambda{|x,y| x+y } === [1,2]
=> ArgumentError: wrong number of arguments (1 for 2)

lambdaはパラメータの個数を検出し、配列を展開しないため、呼び出すことはできません.ここでの簡単な書き方は正しくありません.rubiniusソースコードを表示するには、次の手順に従います.
alias_method :===, :call

3つの等号がcallの別名であることがわかりますが、この方法は特にありません.このように書いてみてください.
>  lambda{|x,y| x+y}.=== 1, 2
=> 3

ここに点呼をつけました.もちろん、次のように書くこともできます.
>  lambda{|x,y| x+y}.===(1, 2)
=> 3

ここの等号と括弧は直接スペースがあることはできません.(1,2)エラーが発生します.だから、prod/lambdaを呼び出すには3つの方法をお勧めします.このパラメータだけを使うときは===を使います.3等号はDSLを書くときにもっと爽やかに見えるだけで、できるだけ使わないでください.
pc.call(x,y)
pc[x,y]
pc.(x,y)

stackoverflowでproc/lambda呼び出しはなぜ明記しなければならないのかと聞かれた.call/.()/[]このように、なぜ省略できないのか、メソッドのように、直接呼び出すのか.例えばpcは、rubyが関数やメソッドを呼び出すときにカッコを省略できるため、()、解析器はprocを渡しているのか、呼び出しているのかを区別できません!Ruby1.9 lambdaサポートパラメータのデフォルト値の開始
>  ->(x, y=2){ x+y }.(1)
=> 3

sendメソッド
RubyのObjectにはsendメソッドがあり、メソッド名を渡すことで動的呼び出しメソッドを実現できます.たとえば、加算
>  1.send(:+, 2) 
=> 3

サブストリングの取得
>  "hello".send :[], 0,1
=> h

「hello」[0,1]に相当するがsend以外にもそっくりな_send__ 方法、どうして2つの名前がありますか?最初はsendのみだったが、socketがsendを使用してパケットを送信するシーンが多いことを考慮すると、ユーザがsendメソッドをカスタマイズすることもあり、送信元のsendメソッドを呼び出すことができないため、また1つの_send__さあ、この方法はoverrideされるべきではありません.Rubyのmapは本来procしか受信していないが,%w(a b c)と書くことがある.map(&:upcase)
>  %w(a b c).map &:upcase
=> ["A", "B", "C"]

&記号は後に伝達される変数がprocであることを示すので、等価になります.
>  %w(a b c).map &:upcase.to_proc
=> ["A", "B", "C"]

rubyがprocオブジェクトではないことを発見すると、そのオブジェクトのto_が呼び出されます.Procメソッドは、procオブジェクトに変換することを実現します.
Proc.new{|obj| obj.send :upcase}

定義to_を使わなかったらproc、では呼び出しに失敗しました.mapの後にSymbolオブジェクトが入力できるのは、RubyがSymbolオブジェクトのto_を実現したからです.Proc法、この用法はRailsの中で最も早く現れて、Ruby 1.8.7里原生がSymbolオブジェクトを実現したto_procメソッド.Rubiniusの実装を表示するには、次の手順に従います.
class Symbol
  def to_proc
    # Put sym in the outer enclosure so that this proc can be instance_eval'd.
    # If we used self in the block and the block is passed to instance_eval, then
    # self becomes the object instance_eval was called on. So to get around this,
    # we leave the symbol in sym and use it in the block.
    #
    sym = self
    Proc.new do |*args, &b|
      raise ArgumentError, "no receiver given" if args.empty?
      args.shift.__send__(sym, *args, &b)
    end
  end
end

sendメソッドが複写されないようにrubiniusで使われているのは_send__方法.最も簡単なto_を実現しますmy_procメソッド
class Symbol
    def to_my_proc
        Proc.new{|obj| obj.send self}
    end
end

テスト:
>  %w(a b c).map(&:upcase.to_my_proc)
=> ["A", "B", "C"]

呼び出しに成功しました.この簡単なto_と比較してください.my_procとRubiniusの実装の違い、第一に、procは1つのパラメータだけを受信して、残りのパラメータを無視して、rubiniusは残りのパラメータも方法の呼び出しパラメータとして、一緒にsendはobjに呼び出して、第2の違いはselfが外で単独で1回値を割り当てて、instance_を呼び出すことを避けることですevalの場合は上書きされ,3つ目の違いは,伝達可能なblockパラメータを同時に伝達することである.ただし、暗黙的にto_を呼び出すproc、このようにして、より多くのパラメータを伝達することはできません.例えば、以下の文字列をhash'a:bに解析することを実現します.c:d'をhashに変換:{a=>b,c=>d}基本書き方:
>  'a:b;c:d'.split(';').map{|s| s.split ':' }.to_h
=> {"a"=>"b", "c"=>"d"}

to_の使用proc略記:最初のステップは、hashに変換する配列にsplitを作成します.
>  'a:b;c:d'.split(';').map &:split
=> [["a:b"], ["c:d"]]

splitのデフォルトのパラメータはスペースなので、変換に失敗しました.パラメータを追加してみます.
>  'a:b;c:d'.split(';').map &:split(':')
=>  SyntaxError: (irb):187: syntax error, unexpected '(', expecting end-of-input
=>  'a:b;c:d'.split(';').map &:split(':')
                                    ^

構文エラー❌,split(':')は合法的なSymbolオブジェクトではないので、このように書くことはできません.実は、to_を通してProcのソースコードも見えますが、デフォルトのto_Procメソッドは、より多くのパラメータを受け入れません.(to_procの定義の後にルートパラメータがまったくないので、実はついても伝わらないし、暗黙的な呼び出しなので)、自分のto_を改造しますmy_Procメソッドは、より多くのパラメータを受信し、明示的に呼び出します.
class Symbol
  def to_my_proc(*args)
    Proc.new{|obj| obj.send self, *args }
  end
end
>  'a:b;c:d'.split(';').map(&:split.to_my_proc(':')).to_h
=> {"a"=>"b", "c"=>"d"}

呼び出しに成功しました.メソッド名をto_ではなくcallとして定義するとmy_procは、通過することができる.()を呼び出し、テストします.
class TestCall
    def call(*args)
        puts "called: #{args}"
    end
end
>  TestCall.new.call "hello", "world"
=> call method called: ["hello", "world"]
>  TestCall.new.("hello", "world")
=> call method called: ["hello", "world"]

同様に呼び出しに成功し、ruby内部が実現したことを証明した.を表示します.callの別名ですが、実装されたソースコードはしばらく見つかりません.だからto_my_procはcallと書きます.
class Symbol
  def call(*args)
    Proc.new{|obj| obj.send self, *args }
  end
end
>  'a:b;c:d'.split(';').map(&:split.(':')).to_h
=> {"a"=>"b", "c"=>"d"}

メソッドをto_と定義しても呼び出しに成功しましたProcも暗黙的に呼び出すことはできません.後で合法的なSymbolオブジェクトではなく、構文が間違っているため、パラメータはサポートされていますが、このメソッドを明示的に呼び出し、map呼び出しのためにprocオブジェクトを返す必要があります.このto_my_Proc/callは非常に簡単で、最も基本的なパラメータを渡しただけです.stackoverflowには、より完璧なコードが書かれています.
class Symbol
  def call(*args, &block)
    ->(caller, *rest) { caller.send(self, *rest, *args, &block) }
  end
end

ここではlambdaを使って、procに変えます.このような方法は、each_のような複数のパラメータがprocに伝達されることを考慮するとwith_index{|e,i|}の場合、例えば[1,2,3].reduce(0){|r,e|r+=e}では、呼び出されたprocは複数のパラメータを渡します(*restに入れます)が、結果は望ましくないかもしれません!たとえば、実際の呼び出しは、->(e,i){e.send:メソッド、i、その他のパラメータ}と解析される可能性があります.残りのパラメータも最初のパラメータ:callerに一括して渡されるメソッド呼び出しに相当するので、block内に複数のパラメータがある場合は、略記はお勧めしません.どんなクラスもto_を実現すればProcメソッドは、前に&記号を付けてproc呼び出しに直接変換できるので、配列のto_を定義するとproc、同じようにできます.だから、stackoverflowでこのようなコードを思いついた人がいます.
class Array
  def to_proc
    Proc.new{|obj| obj.send *self }
  end
end

これにより、map呼び出しに配列を直接渡すことができます.ここで*selfは配列展開を表し、コードをテストします.
>  'a:b;c:d'.split(';').map(&[:split, ':']).to_h
=> {"a"=>"b", "c"=>"d"}

多くの人が使っていると思いますが、ルビーはこの方法を内蔵することを考えているかもしれません.この書き方はSymbolのto_より優れています.Procはより適切で、特にパラメータをメソッドに渡す必要がある場合、この書き方は&:メソッドよりも適切である.(パラメータ)より直感的に見えます.inject/reduceメソッドを見ると、このメソッドは以下のように有効な書き方ができます.
[1,2,3].reduce(0, &:+)
[1,2,3].reduce(&:+)
[1,2,3].reduce(:+)
[1,2,3].reduce('+')

1つ目の書き方は問題なく、2つ目のパラメータはprocとして渡され、Symbolのto_を呼び出します.procメソッドはresultに変換する.send:+,itemメソッド呼び出し,2つ目も実はあまり疑問はありませんが,初期値が欠けているため,実際の内部ループは2回しか実行されず,1回目,1回目,2回目,1を初期値として実行し,block{|result,item|result+item}に持ち込んで最終値を得ることができ,3種類目と4種類目はmapに直接書くとエラーが提示されますが,ここでは正常です.なぜならreduceは実際には2つのパラメータを受信することができ(mapはパラメータを受信しない)、blockが渡されない場合、パラメータをsymbolに変換してsymbolのto_を呼び出すことを試みるからである.Procメソッドはそれをproc呼び出しに変換するので,里第2種に等価である.Rubinius実装コード:
def inject(initial=undefined, sym=undefined)
    if !block_given? or !undefined.equal?(sym) #    block、        !
      if undefined.equal?(sym) #            ,                 ,   :+。
        sym = initial  
        initial = undefined
      end
      # Do the sym version
      sym = sym.to_sym #    to_sym     ,     ,       :+    '+'
      each do
        o = Rubinius.single_block_arg
        if undefined.equal? initial #       ,            ,   sym    proc  
          initial = o
        else #       sym   proc  
          initial = initial.__send__(sym, o)
        end
      end
      # Block version
    else #   block  ,             
      each do
        o = Rubinius.single_block_arg
        if undefined.equal? initial #          ,         ,   proc   
          initial = o
        else #       proc  。
          initial = yield(initial, o)
        end
      end
    end
    undefined.equal?(initial) ? nil : initial
end

上で私自身が定義したSymbolのcallメソッドはblock後の複数のパラメータの場合、すなわちinject/reduceのコードブロック:[1,2,3].inject{| r,e|r+e}の場合、callを呼び出そうとすると失敗します.以下を少し改造して、Symbolのcallにより多くのパラメータを受信させます.
class Symbol
    def call(*args)
        Proc.new{|first, *others| first.send self, *others, *args}
    end
end

なぜ2つのバンド*のパラメータが現れるのかについては、これは関数定義ではなく、関数呼び出しであり、すべてのバンド*パラメータが順次sendメソッド呼び出しを展開するので、曖昧さは発生しません.呼び出し:
>  [1,2,3].inject &:+.()
=> 6

ここでは必ず&記号を付けて、単一のパラメータではなく、持ち込み時にproc呼び出しが行われることを示します.なぜなら、単一のパラメータであればSymbolオブジェクトに変換し、Symbolのto_を通過する必要があるからです.Procは呼び出しを実現し、:+.()合法的なSymbolオブジェクトではなく、callメソッドを呼び出すオブジェクトの略記であることは明らかです.同様に、Arrayのto_procメソッドも改造する必要がある
class Array
  def to_proc
    sym = self.shift
    Proc.new{|first, *others| first.send sym, *others, *self}
  end
end
> [1,2,3].inject &[:+]
=> 6

実際には何の役にも立たない.
Ruby 2.3バージョンでは、Hashのto_が追加されましたprocメソッド:
>  h = { foo:1, bar: 2, baz: 3} 
>  p = h.to_proc
>  p.call :foo #     h[:foo] 
=> 1

単純には何の役にも立たないが、mapですべての値を取得することができます.
>  [:foo, :bar].map { |key| h[key] } # h         h 
=> [1, 2]

これにより、以下のように簡単に書くことができます.
>  [:foo, :bar].map &h 
=> [1, 2]

簡単なハシトを自分で実現できるproc
Class Hash
    def to_proc
        Proc.new{ |key| self[key] }
    end
end

しばらくここまで書きます.