【问题标题】:Advantages of using block, proc, lambda in Ruby在 Ruby 中使用 block、proc、lambda 的优点
【发布时间】:2018-12-28 09:15:54
【问题描述】:

示例:LinkedList 打印方法。 对于这个对象,你会发现一个使用 block、proc 和 lambda 的打印方法。 我不清楚优点/缺点是什么(如果有的话)。

谢谢

什么是链表? LinkedList 是一个附加了特定值的节点(有时称为有效负载),以及到另一个节点的链接(如果没有下一项,则为 nil)。

class LinkedListNode
    attr_accessor :value, :next_node

    def initialize(value, next_node = nil)
        @value = value
        @next_node = next_node
    end

    def method_print_values(list_node)
        if list_node 
            print "#{list_node.value} --> "
            method_print_values(list_node.next_node)
        else
            print "nil\n"
            return
        end
    end


end

node1 = LinkedListNode.new(37)
node2 = LinkedListNode.new(99, node1)
node3 = LinkedListNode.new(12, node2)

#printing the linked list through a method defined within the scope of the class
node3.method_print_values(node3)

#----------------------------  Defining the printing method through a BLOCK
def block_print_value(list_node, &block)
    if list_node
        yield list_node
        block_print_value(list_node.next_node, &block)
    else
        print "nil\n"
        return
    end
end

block_print_value(node3) { |list_node| print "#{list_node.value} --> " }

#----------------------------  Defining the printing method through a PROC

def proc_print_value(list_node, callback)
    if list_node
        callback.call(list_node)  #this line invokes the print function defined below
        proc_print_value(list_node.next_node, callback)
    else
        print "nil\n"
    end
end


proc_print_value(node3, Proc.new {|list_node| print "#{list_node.value} --> "})

#----------------------------  Defining the printing method through a LAMBDA

def lambda_print_value(list_node, callback)
    if list_node
        callback.call(list_node)  #this line invokes the print function defined below
        lambda_print_value(list_node.next_node, callback)
    else
        print "nil\n"
    end
end



lambda_print_value(node3, lambda {|list_node| print "#{list_node.value} --> "})

#----------------------------  Defining the printing method outside the class
def print_values(list_node)
    if list_node 
        print "#{list_node.value} --> "
        print_values(list_node.next_node)
    else
        print "nil\n"
        return
    end
end

print_values(node3)

【问题讨论】:

    标签: ruby recursion lambda block proc


    【解决方案1】:

    示例展示了如何使用不同的东西来做同样的事情。因此,在这种情况下,它们之间没有主要区别:

    my_proc = Proc.new { |list_node| print "#{list_node.value} --> " }
    
    node3.block_print_values(node3, &my_proc)
    node3.proc_print_value(node3, my_proc)
    node3.lambda_print_value(node3, my_proc)
    

    此外,还可以使用其中任何一个来定义方法:

    define_method(:my_method, p, &proc { puts p })
    my_method 'hello' #=> hello
    
    define_method(:my_method, p, &-> { puts p })
    my_method 'hello' #=> hello
    

    但是 Proc、Lambda、block 是不一样的。首先,需要更多展示如何使用魔法&great article 可以提供帮助:

    &object 的评估方式如下:

    • 如果对象是一个块,它会将块转换为一个简单的过程。

    • 如果对象是 Proc,它将对象转换为块,同时保留对象的lambda? 状态。

    • 如果对象不是Proc,它首先在对象上调用#to_proc,然后将其转换为块。

    但这并没有显示它们之间的差异。所以,现在放开ruby source

    Proc 对象是绑定到一组局部变量的代码块。绑定后,代码可以在不同的上下文中调用,并且仍然可以访问这些变量。

    +lambda+、+proc+ 和 Proc.new 保留了由 & 参数给出的 Proc 对象的技巧。

    lambda(&lambda {}).lambda?   #=> true
    proc(&lambda {}).lambda?     #=> true
    Proc.new(&lambda {}).lambda? #=> true
    
    lambda(&proc {}).lambda?     #=> false
    proc(&proc {}).lambda?       #=> false
    Proc.new(&proc {}).lambda?   #=> false
    

    Proc 创建为:

    VALUE block = proc_new(klass, FALSE);
    
    rb_obj_call_init(block, argc, argv);
    return block;
    

    lambda:

    return proc_new(rb_cProc, TRUE);
    

    两者都是Proc。在这种情况下,区别仅在于TRUEFALSETRUE, FALSE - 检查调用时传递的参数个数。

    所以,lambda 就像更严格的Proc

    is_proc = !proc->is_lambda;
    

    Lambda 与 Proc 的总结:

    1. Lambdas 检查参数的数量,而 procs 不检查。

    2. 在 proc 内返回会退出调用它的方法。

    3. 在 lambda 中返回会使它从 lambda 中退出,并且该方法将继续执行。

    4. Lambda 更接近于方法。


    :它们在其他语言中被称为闭包,它是一种对代码/语句进行分组的方式。在 ruby​​ 中,单行块用 {} 编写,多行块用 do..end 表示。

    块不是对象,不能保存在变量中。 Lambda 和 Proc 都是一个对象。


    那么,让我们基于this answer做小代码测试:

    # ruby 2.5.1
        user     system      total        real
    0.016815   0.000000   0.016815 (  0.016823)
    0.023170   0.000001   0.023171 (  0.023186)
    0.117713   0.000000   0.117713 (  0.117775)
    0.217361   0.000000   0.217361 (  0.217388)
    

    这表明使用 block.call 几乎比使用 yield 慢 2 倍。

    感谢@engineersmnky,在 cmets 中提供了很好的参考。

    【讨论】:

    • 您可以定义 Proclambda 并将其用作块参数。例如结合你的第一个和第二个例子,第一个可以是node3.block_print_values(node3, &callback)
    • 在第一个例子中你的块也有一个名字,它的名字是block
    • @engineersmnky, & 将块“提升”到 Proc 并将 Proc 绑定到具有给定名称的变量。就像:'1'.to_i == 1 => true,但不意味着StringInteger 相同
    • 但是block 具有与callback 相同的局部变量名称,因此您要迭代您的观点,第一种方法实际上应该接受匿名块而不是命名块,因为唯一的原因为真“。每次调用block_print_value时都必须定义一个块”是因为yield没有块检查不是&block
    • @engineersmnky,我有点误会了。谢谢。我更新了我的答案。
    【解决方案2】:

    IMO,您的 block_print_value 方法设计/命名不佳,因此无法直接回答您的问题。从方法的名称来看,我们希望该方法“打印”一些东西,但唯一的打印是边框条件,它做了一个

    print "nil\n"
    

    所以,虽然我强烈反对使用 这种 方式来打印树,但这并不意味着使用块来解决打印问题的整个想法是不好的。

    由于您的问题看起来像编程作业,因此我不会发布完整的解决方案,而是提供提示:

    将您的block_print_value 替换为block_visit_value,这与您当前的方法相同,但不进行任何打印。相反,“else”部分也可以调用该块来让它进行打印。

    我相信您以后会看到这种方法的优势。如果没有,请回到这里讨论。

    【讨论】:

      【解决方案3】:

      Proc 是块上的对象包装器。 Lambda 基本上是一个具有不同行为的过程。

      AFAIK 纯块与 procs 相比使用起来更合理。

      def f
        yield 123
      end
      

      应该比

      def g(&block)
        block.call(123)
      end
      

      但proc可以进一步传递。

      我想你应该在主题上找到一些性能比较的文章

      【讨论】:

        【解决方案4】:

        概括地说,procs可以存储在变量中的方法,如下所示:

        full_name = Proc.new { |first,last| first + " " + last }
        

        我可以通过两种方式调用它,使用括号语法后跟要传递给它的参数,或者使用 call 方法运行 proc 并在括号内传递参数,如下所示:

        p full_name.call("Daniel","Cortes")
        

        我对上面第一行所做的是创建一个Proc 的新实例并将其分配给一个名为full_name 的变量。 Procs 可以将代码块作为参数,所以我向它传递了两个不同的参数,参数进入管道。

        我也可以让它打印我的名字五次:

        full_name = Proc.new { |first| first * 5 }
        

        我所指的块在其他编程语言中称为 closure。块允许您将语句组合在一起并封装行为。您可以使用花括号或 do...end 语法创建块。

        为什么要使用Procs

        答案是Procs 比方法更灵活。使用Procs,您可以将一整套进程存储在一个变量中,然后在程序中的任何其他位置调用该变量。

        类似于ProcsLambdas 允许您将函数存储在变量中并从程序的其他部分调用方法。所以我上面的代码真的可以像这样使用:

        full_name = lambda { |first,last| first + " " + last }
        p full_name["daniel","cortes"]
        

        那么这两者有什么区别呢?

        除了语法之外,还有两个主要区别。请注意,这些差异是微妙的,甚至到了您在编程时可能从未注意到它们的地步。

        第一个关键区别是 Lambda 计算您传递给它们的参数,而 Procs 不计算。例如:

        full_name = lambda { |first,last| first + " " + last }
        p full_name.call("Daniel","Cortes")
        

        但是,如果我将另一个参数传递给上面的代码:

        p full_name.call("Daniel","Abram","Cortes")
        

        应用程序抛出一个错误,提示我传入了错误数量的参数。

        但是,Procs 不会引发错误。它只查看前两个参数并忽略之后的任何内容。

        其次,当从方法返回值时,Lambda 和 Procs 有不同的行为,例如:

        def my_method
          x = lambda { return }
          x.call
          p "Text within method"
        end
        

        如果我运行这个方法,它会打印出Text within method。但是,如果我们尝试使用 Proc 进行完全相同的实现:

        def my_method
           x = Proc.new { return }
           x.call
           p "Text within method"
        end
        

        这将返回一个 nil 值。

        为什么会这样?

        Proc 看到单词 return 时,它退出整个方法并返回 nil 值。但是,对于 Lambda,它处理了方法的剩余部分。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2012-04-06
          • 1970-01-01
          • 2010-09-06
          • 1970-01-01
          • 2012-07-24
          • 2011-02-26
          • 2012-02-12
          • 2010-12-16
          相关资源
          最近更新 更多