2010-11-23 11 views
2

Was ist falsch im Code?Problem mit Ruby-Blöcken

def call_block(n) 

    if n==1 

    return 0 
    elsif n== 2 

    return 1 
    else 
    yield 
    return call_block(n-1) + call_block(n-2) 

    end 

end 


puts call_block(10) {puts "Take this"} 

Ich versuche, Ausbeute zu drucken Nehmen Sie diese andere als die zehnte Fibonacci-Nummer.

Ich erhalte die Fehlermeldung: in `call_block ': kein Block gegeben (LocalJumpError)

Auch der folgende Code löst Fehler:

def call_block(n) 

    if n==1 
    yield 
    return 0 
    elsif n== 2 
    yield 
    return 1 
    else 
    yield 
    return call_block(n-1) + call_block(n-2) 

    end 

end 


puts call_block(10) {puts "Take this"} 

Antwort

3

Sie können diese Linie verwenden, da Adam Vandenberg Hinweise:

return call_block(n-1) { yield } + call_block(n-2) { yield } 
+0

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

+0

'yield' ruft den block oder proc auf, der in den Block-/proc-Argument-Slot der aktuell laufenden Methode übergeben wurde. – yfeldblum

+0

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

1

, die mit dem Verfahren call_block in einem Block ohne Umleitung wegen des rekursiven Aufrufs ist. Eine Möglichkeit, es zu tun wäre:

def call_block(n, &blk) 
    if n == 1 
     return 0 
    elsif n == 2 
     return 1 
    else 
     blk.call() 
     return call_block(n-1, &blk) + call_block(n-2, &blk) 
    end 
end 

puts call_block(4) {puts "Take this"} 

EDIT: Ich muss zugeben, dass die Lösung posted by Justice mehr logisch erscheint.

7

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 n9 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' } 
+0

Die Antwort von Justice zeigt an, dass Sie in diesem Fall "[den Block] entlang" weitergeben können. –

+0

+1, um zu erklären, was passiert und wie der OP-Code verbessert werden kann. –

+0

@Andrew Grimm: Das geht nicht entlang des Blocks. Das sind zwei völlig neue, unabhängige Blöcke, die zufällig den ursprünglichen Block aufrufen. Sie können den Unterschied deutlich sehen, wenn Sie sich die Stacktraces ansehen. Erzwinge einfach eine Ausnahme: 'a = 0; call_block (100) {raise if (a + = 1)> 10} 'und du wirst sehen, dass es in meinem Fall nur einen Block gibt und der Stapel viel weniger tief ist, während in der Version von Justice ein tiefer Stapel von Blöcke oben auf dem Stapel von Methoden. Ich bin auch nicht ganz davon überzeugt, dass alle Block-relativen Kontrollflüsse richtig funktionieren. –