简体   繁体   English

在Ruby中使用block,proc,lambda的优势

[英]Advantages of using block, proc, lambda in Ruby

Example: LinkedList printing method. 示例:LinkedList打印方法。 For this object, you will find a printing method using block, proc, and lambda. 对于此对象,您将找到一种使用block,proc和lambda的打印方法。 It is not clear to me what the advantages/disadvantages are (if any). 对我来说,尚不清楚什么优点/缺点(如果有)。

Thank you 谢谢

What is a LinkedList? 什么是LinkedList? A LinkedList is a node that has a specific value attached to it (which is sometimes called a payload), and a link to another node (or nil if there is no next item). 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)

Examples display how to use different things to do the same. 示例显示了如何使用不同的方法来执行相同的操作。 So, there is no principal difference between them in this context: 因此,在这种情况下,它们之间没有主要区别:

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)

Also, there is possible to define a method by using any of them: 另外,可以使用以下任意一种来定义方法:

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

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

But Proc, Lambda, block are not the same. 但是Proc,Lambda,block并不相同。 Firstly, need a bit more display how to works magic & . 首先,需要多展示一些魔术&工作原理。 The great article can help with that: 很棒的文章可以帮助您:

&object is evaluated in the following way: &object通过以下方式求值:

  • if object is a block, it converts the block into a simple proc. 如果object是一个块,它将对象转换成一个简单的proc。

  • if object is a Proc, it converts the object into a block while preserving the lambda? 如果object是Proc,则在保留lambda?同时将对象转换为块lambda? status of the object. 对象的状态。

  • if object is not a Proc, it first calls #to_proc on the object and then converts it into a block. 如果object不是Proc,它将首先在该对象上调用#to_proc ,然后将其转换为块。

But this does not show the differences between them. 但这并没有显示它们之间的差异。 So, now let go to the ruby source : 因此,现在让我们转到ruby源

Proc objects are blocks of code that have been bound to a set of local variables. Proc对象是已绑定到一组局部变量的代码块。 Once bound, the code may be called in different contexts and still access those variables. 一旦绑定,就可以在不同的上下文中调用代码,并且仍然可以访问这些变量。

And

+lambda+, +proc+ and Proc.new preserve the tricks of a Proc object given by & argument. + 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 created as: Proc创建为:

VALUE block = proc_new(klass, FALSE);

rb_obj_call_init(block, argc, argv);
return block;

When lambda : lambda

return proc_new(rb_cProc, TRUE);

Both are Proc . 两者都是Proc In this case, the difference is just in TRUE or FALSE . 在这种情况下,差异只是TRUEFALSE TRUE , FALSE - check the number of parameters passed when called. TRUEFALSE检查调用时传递的参数数量。

So, lambda is like more strict Proc : 因此, lambda就像更严格的Proc

is_proc = !proc->is_lambda;

Summary of Lambda vs Proc: Lambda vs Proc的摘要:

  1. Lambdas check the number of arguments, while procs do not. Lambda检查参数的数量,而proc不检查。

  2. Return within the proc would exit the method from where it is called. proc中的return将退出调用该方法的位置。

  3. Return within a lambda would exit it from the lambda and the method would continue executing. 在lambda中返回将​​退出lambda,并且该方法将继续执行。

  4. Lambdas are closer to a method. Lambda更接近一种方法。


Blocks : They are called closures in other languages, it is a way of grouping code/statements. :它们在其他语言中称为闭包,这是一种将代码/语句分组的方式。 In ruby single line blocks are written in {} and multi-line blocks are represented using do..end. 在ruby中,单行代码块用{}编写,多行代码块使用do..end表示。

Block is not an object and can not be saved in a variable. 块不是对象,不能保存在变量中。 Lambda and Proc are both an object. Lambda和Proc都是对象。


So, let do small code test based on 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)

This shows that using block.call is almost 2x slower than using yield. 这表明使用block.call几乎比使用yield慢2倍。

Thanks, @engineersmnky, for good references in comments. 谢谢@engineersmnky,他们在评论中提供了很好的参考。

IMO, your block_print_value method is poorly designed/named, which makes it impossible to answer your question directly. IMO,您的block_print_value方法设计/命名不正确,这使得无法直接回答您的问题。 From the name of the method, we would expect that the method "prints" something, but the only printing is the border condition, which does a 从方法的名称来看,我们希望该方法“打印”某些东西,但唯一的打印是边界条件,它会

print "nil\n"

So, while I would strongly vote against using this way to print the tree, it doesn't mean that the whole idea of using a block for the printing problem is bad. 因此,尽管我强烈反对使用这种方式来打印树,但这并不意味着使用块来解决打印问题的整个想法是不好的。

Since your problem looks like a programming assignment, I don't post a whole solution, but give a hint: 由于您的问题看起来像是编程任务,因此我不会发布完整的解决方案,而是给出一个提示:

Replace your block_print_value by a, say block_visit_value , which does the same like your current method, but doesn't do any printing. 更换你block_print_value通过,说block_visit_value ,这不同样喜欢你目前的方法,但没有做任何的印刷。 Instead, the "else" part could also invoke the block to let it do the printing. 相反,“其他”部分也可以调用该块以使其进行打印。

I'm sure that you will see afterwards the advantage of this method. 我相信您以后会看到此方法的优点。 If not, come back here for a discussion. 如果没有,请回到这里进行讨论。

Proc is an object wrapper over block. Proc是块上的对象包装器。 Lambda basically is a proc with different behavior. Lambda基本上是具有不同行为的proc。

AFAIK pure blocks are more rational to use compared to procs. 与proc相比,使用AFAIK纯块更合理。

def f
  yield 123
end

Should be faster than 应该比

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

But proc can be passed on further. 但是proc可以进一步传递。

I guess you should find some articles with performance comparison on the toppic 我想您应该在Toppic上找到一些具有性能比较的文章

At a high level, procs are methods that can be stored inside variables like so: procsprocs可以存储在变量内部的方法,如下所示:

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

I can call this in two ways, using the bracket syntax followed by the arguments I want to pass to it or use the call method to run the proc and pass in arguments inside of parentheses like so: 我可以通过两种方式调用此方法,使用方括号语法后跟要传递给它的参数,或者使用call方法运行proc并在括号内传递参数,如下所示:

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

What I did with the first line above is create a new instance of Proc and assigned it to a variable called full_name . 我在上面的第一行中所做的是创建一个Proc的新实例,并将其分配给一个名为full_name的变量。 Procs can take a code block as a parameter so I passed it two different arguments, arguments go inside the pipes. Procs可以将代码块作为参数,因此我向它传递了两个不同的参数,参数进入管道内部。

I can also make it print my name five times: 我也可以使它打印我的名字五次:

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

The block I was referring to is called a closure in other programming languages. 我所指的块在其他编程语言中称为闭包 Blocks allow you to group statements together and encapsulate behavior. 块允许您将语句分组在一起并封装行为。 You can create blocks with curly braces or do...end syntax. 您可以使用花括号或do ... end语法创建块。

Why use Procs ? 为什么要使用Procs

The answer is Procs give you more flexibility than methods. 答案是Procs比方法给您更大的灵活性。 With Procs you can store an entire set of processes inside a variable and then call the variable anywhere else in your program. 使用Procs您可以将整个过程集存储在一个变量中,然后在程序中的其他任何地方调用该变量。

Similar to Procs , Lambdas allow you to store functions inside a variable and call the method from other parts of the program. Procs类似, Lambdas允许您将函数存储在变量中,并从程序的其他部分调用方法。 So really the same code I had above can be used like so: 因此,实际上可以使用与上面相同的代码,如下所示:

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

So what is the difference between the two? 那么两者之间有什么区别?

There are two key differences in addition to syntax. 除语法外,还有两个主要区别。 Please note that the differences are subtle, even to the point that you may never even notice them while programming. 请注意,这些差异是细微的,甚至到您在编程时甚至都不会注意到它们的程度。

The first key difference is that Lambdas count the arguments you pass to them whereas Procs do not. 第一个关键区别是Lambda会对您传递给它们的参数进行计数,而Procs则不会。 For example: 例如:

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

The code above works, however, if I pass it another argument: 上面的代码有效,但是,如果我给它传递另一个参数:

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

The application throws an error saying that I am passing in the wrong number of arguments. 应用程序引发错误,提示我传递了错误数量的参数。

However, with Procs it will not throw an error. 但是,使用Procs不会引发错误。 It simply looks at the first two arguments and ignores anything after that. 它仅查看前两个参数,然后忽略任何内容。

Secondly, Lambdas and Procs have different behavior when it comes to returning values from methods, for example: 其次,在从方法返回值时,Lambdas和Procs具有不同的行为,例如:

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

If I run this method, it prints out Text within method . 如果运行此方法,它将在method中打印出Text However, if we try the same exact implementation with a Proc: 但是,如果我们尝试使用Proc进行完全相同的实现:

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

This will return a nil value. 这将返回nil值。

Why did this occur? 为什么会发生这种情况?

When the Proc saw the word return it exited out of the entire method and returned a nil value. Proc看到单词return时,它退出整个方法并返回nil值。 However, in the case of the Lambda , it processed the remaining part of the method. 但是,对于Lambda ,它处理了该方法的其余部分。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM