1

Example: LinkedList printing method. For this object, you will find a printing method using block, proc, and lambda. It is not clear to me what the advantages/disadvantages are (if any).

Thank you

What is a 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).

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)
Pibol
  • 21
  • 4

4 Answers4

2

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. Firstly, need a bit more display how to works magic &. The great article can help with that:

&object is evaluated in the following way:

  • if object is a block, it converts the block into a simple proc.

  • if object is a Proc, it converts the object into a block while preserving the 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.

But this does not show the differences between them. So, now let go to the ruby source:

Proc objects are blocks of code that have been bound to a set of local variables. 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(&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:

VALUE block = proc_new(klass, FALSE);

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

When lambda:

return proc_new(rb_cProc, TRUE);

Both are Proc. In this case, the difference is just in TRUE or FALSE. TRUE, FALSE - check the number of parameters passed when called.

So, lambda is like more strict Proc:

is_proc = !proc->is_lambda;

Summary of Lambda vs Proc:

  1. Lambdas check the number of arguments, while procs do not.

  2. Return within the proc would exit the method from where it is called.

  3. Return within a lambda would exit it from the lambda and the method would continue executing.

  4. Lambdas are closer to a method.


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.

Block is not an object and can not be saved in a variable. Lambda and Proc are both an object.


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.

Thanks, @engineersmnky, for good references in comments.

Leo
  • 1,673
  • 1
  • 13
  • 15
  • You can define a `Proc` or a `lambda` and use it as a block argument. For instance combining your first and second examples the first could be `node3.block_print_values(node3, &callback)` – engineersmnky Jul 20 '18 at 19:41
  • Also your block has a name in the first example its name is `block` – engineersmnky Jul 20 '18 at 19:48
  • @engineersmnky, `&` "promotes" the block to a Proc and binds the Proc to the variable with the given name. It is like: `'1'.to_i == 1 => true`, but not means `String` is the same as `Integer` – Leo Jul 20 '18 at 22:52
  • But `block` has a local variable name in the same fashion as `callback` so your to iterate your point the first method should actually accept an anonymous block rather than a named one because the only reason this is true ". A block must be defined every time when called block_print_value" is because of `yield` without a block check not `&block` – engineersmnky Jul 20 '18 at 22:58
  • @engineersmnky, I was a little mistaken. Thanks. I was updated my answer. – Leo Jul 21 '18 at 03:22
1

Proc is an object wrapper over block. Lambda basically is a proc with different behavior.

AFAIK pure blocks are more rational to use compared to procs.

def f
  yield 123
end

Should be faster than

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

But proc can be passed on further.

I guess you should find some articles with performance comparison on the toppic

Nondv
  • 769
  • 6
  • 11
0

IMO, your block_print_value method is poorly designed/named, which makes it impossible to answer your question directly. 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. 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.

user1934428
  • 19,864
  • 7
  • 42
  • 87
0

At a high level, procs are methods that can be stored inside variables like so:

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:

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. Procs can take a code block as a parameter so I passed it two different arguments, arguments go inside the pipes.

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.

Why use Procs?

The answer is Procs give you more flexibility than methods. With Procs you can store an entire set of processes inside a variable and then call the variable anywhere else in your program.

Similar to Procs, Lambdas allow you to store functions inside a variable and call the method from other parts of the program. 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. 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. 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:

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

If I run this method, it prints out Text within method. However, if we try the same exact implementation with a Proc:

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

This will return a nil value.

Why did this occur?

When the Proc saw the word return it exited out of the entire method and returned a nil value. However, in the case of the Lambda, it processed the remaining part of the method.

Daniel
  • 14,004
  • 16
  • 96
  • 156