まず、何が問題なのかを簡単に確認できるように、少し整理しましょう。
def call_block(n)
return 0 if n == 1
return 1 if n == 2
yield
call_block(n-1) + call_block(n-2)
end
puts call_block(10) { puts 'Take this' }
では、それをたどってみましょう。
電話することから始めます
call_block(10) { puts 'Take this' }
つまり、ブロックnは10{ puts 'Take this' } です。nは も1でもないので、制御をブロックに移す2に到達します。yield
今、私たちは呼んでいます
call_block(n-1)
これは
call_block(9)
ブロックで呼び出していないことに注意してください。したがって、この新しい呼び出しでnは9ブロックはありません。繰り返しますが、最初の 2 行をスキップしてyield.
しかし、to にはブロックがありませんyield。そのため、コードはここで爆破されます。
解決策は明らかであり、巧妙です。明らかな部分は次のとおりです。問題はブロックを渡していないことです。したがって、解決策はブロックを渡す必要があることです。微妙な部分は次のとおりです。どうすればそれを行うことができますか?
Ruby ブロックが構文的に軽量である理由は、それらが匿名であることです。しかし、ブロックに名前がなければ参照できず、参照できない場合は渡すことができません。
これに対する解決策は、Ruby で別の構造を使用することです。これは基本的に、ブロックよりも「コードの塊」の概念をより重く抽象化したものです: a Proc.
def call_block(n, blk)
return 0 if n == 1
return 1 if n == 2
blk.()
call_block(n-1, blk) + call_block(n-2, blk)
end
puts call_block(10, ->{ puts 'Take this' })
ご覧のとおり、これは構文的に少し重いですがProc、名前を付けて再帰呼び出しに渡すことができます。
ただし、このパターンは実際には十分に一般的であるため、Ruby では特別にサポートされています。&パラメータリストのパラメータ名の前にシジルを置くと、Ruby は引数として渡されたブロックをProcオブジェクトに「パッケージ化」し、それをその名前にバインドします。&逆に、引数リストの引数式の前にシジルを置くと、それはProcブロックに「アンパック」されます。
def call_block(n, &blk)
return 0 if n == 1
return 1 if n == 2
yield # or `blk.()`, whichever you prefer
call_block(n-1, &blk) + call_block(n-2, &blk)
end
puts call_block(10) { puts 'Take this' }