Lassen Sie uns zunächst, dass ein wenig aufzuräumen, so dass es einfacher ist, zu sehen, was falsch läuft:
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' }
Nun wollen wir verfolgen es nur durch.
Wir beginnen also
call_block(10) { puts 'Take this' }
Aufruf n
ist 10
und der Block {puts 'Take this'}. Da n
weder 1
noch 2
ist, gelangen wir zur yield
, die die Kontrolle an den Block übergibt.
Jetzt rufen wir
call_block(n-1)
die
call_block(9)
Hinweis ist, dass wir es nicht mit einem Block anrufen. Also, für diesen neuen Anruf ist n
9
und es gibt keinen Block. Auch hier überspringen wir die ersten beiden Zeilen und kommen zu der yield
.
Aber es gibt keinen Block zu yield
zu, und deshalb explodiert der Code hier.
Die Lösung ist sowohl offensichtlich als auch subtil. Der offensichtliche Teil ist: Das Problem ist, dass wir keinen Block passieren, daher ist die Lösung, dass wir den Block weiterleiten müssen. Der subtile Teil ist: Wie machen wir das?
Die Sache, die Ruby-Blöcke so syntaktisch leicht macht, ist, dass sie anonym sind. Aber wenn der Block keinen Namen hat, können wir uns nicht darauf beziehen, und wenn wir uns nicht darauf beziehen können, können wir ihn nicht weitergeben.
Die Lösung zu diesem Zweck ist, ein anderes Konstrukt in Ruby zu verwenden, das eine schwerere Abstraktion für die Idee von "einem Stück Code" als ein Block ist: 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' })
Wie Sie sehen können, diese ist etwas schwerer syntaktisch, aber wir können die Proc
einen Namen geben, und es somit zusammen mit den rekursiven Aufrufen übergeben.
Allerdings ist dieses Muster tatsächlich häufig genug, dass es spezielle Unterstützung in Ruby dafür gibt. Wenn Sie ein &
Sigil vor einen Parameternamen in einer Parameterliste setzen, "packt" Ruby einen Block, der als Argument übergeben wird, in ein Proc
Objekt und bindet ihn an diesen Namen. Und umgekehrt, wenn Sie einen &
Sigill vor einem Argument Ausdruck in einer Argumentliste setzen, wird es „auspacken“, dass Proc
in einen Block:
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' }
diesen ‚Ertrag‘ Aussagen innerhalb der ansonsten leeren Blöcke - die Blöcke geben sie nach? Ich bin verwirrt, ich dachte, diese Blöcke selbst würden wegen einer anderen yield-Anweisung innerhalb der call_block-Methode aufgerufen. – Bruce
'yield' ruft den block oder proc auf, der in den Block-/proc-Argument-Slot der aktuell laufenden Methode übergeben wurde. – yfeldblum
Um genauer zu sein, ruft 'yield' den block oder proc auf, der in die Methode im Argumentblock block/proc übergeben wurde, wobei in dieser Methode der' yield' lexikalisch erscheint. – yfeldblum