【问题标题】:Problem with Ruby blocksRuby 块的问题
【发布时间】:2011-05-14 15:57:13
【问题描述】:

代码有什么问题?

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"}

我正在尝试使用 yield 打印除第十个斐波那契数之外的其他值。

我收到错误: 在 `call_block' 中:没有给出块 (LocalJumpError)

即使下面的代码也会抛出错误:

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"}

【问题讨论】:

    标签: ruby yield block


    【解决方案1】:

    首先,让我们稍微清理一下,以便更容易看出问题所在:

    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' }
    

    所以,n10,块是 { puts 'Take this' }。由于n 既不是1 也不是2,所以我们到达yield,它将控制权转移到块。

    现在我们正在调用

    call_block(n-1)
    

    这是

    call_block(9)
    

    请注意,我们不是用块调用它。因此,对于这个新呼叫,n9 并且没有阻塞。同样,我们跳过前两行,来到yield

    但是yield 没有阻止,这就是代码在这里爆炸的原因。

    解决方案既明显又微妙。显而易见的部分是:问题是我们没有传递一个块,因此解决方案是我们需要传递这个块。微妙的部分是:我们如何做到这一点?

    Ruby 块在语法上如此轻量的原因在于它们是匿名的。但是如果块没有名字,我们就不能引用它,如果我们不能引用它,那么我们就不能传递它。

    解决方案是在 Ruby 中使用另一种构造,它基本上是对“代码块”概念的更重量级的抽象,而不是块: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 对象中并将其绑定到该名称。相反,如果您在参数列表中的参数表达式前面放置一个& sigil,它会将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' }
    

    【讨论】:

    • Justice 的回答表明,在这种情况下,您可以“将其 [块] 传递”。
    • +1 用于解释正在发生的事情以及如何改进 OP 的代码。
    • @Andrew Grimm:这不是通过街区。这传递了两个全新的、独立的、不同的块,它们恰好调用了原始块。如果您查看堆栈跟踪,您可以清楚地看到差异。只需强制一个例外:a = 0; call_block(100){raise if (a+=1) > 10} 你会看到,在我的例子中,只有一个块,堆栈的深度要小得多,而在正义的版本中,在方法堆栈的顶部有一个很深的块堆栈.我也不完全相信所有块相关的控制流都能正常工作。
    • 这是一个非常好的答案。解决了我的问题,让我明白了。写一本书! ;o)
    【解决方案2】:

    您可能想使用此行,如Adam Vandenberg 提示:

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

    【讨论】:

    • 那些'yield'语句在其他为空的块中——它们让给了哪些块?我很困惑,我认为这些块本身被调用是因为 call_block 方法中的 yield 语句不同。
    • yield 调用传入当前执行方法的块/过程参数槽的块或过程。
    • 更准确地说,yield调用block/proc参数槽中传入方法的block或proc,yield词法出现在该方法中。
    【解决方案3】:

    这是因为递归调用了方法call_block 而没有传入一个块。一种方法是:

    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"}
    

    编辑:我必须承认posted by Justice 的解决方案似乎更合乎逻辑。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-06-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-09-22
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多