What are the behavioural differences between the following two implementations in Ruby of the thrice
method?
module WithYield def self.thrice 3.times { yield } # yield to the implicit block argument end end module WithProcCall def self.thrice(&block) # & converts implicit block to an explicit, named Proc 3.times { block.call } # invoke Proc#call end end WithYield::thrice { puts "Hello world" } WithProcCall::thrice { puts "Hello world" }
By "behavioural differences" I include error handling, performance, tool support, etc.
A Proc object is an encapsulation of a block of code, which can be stored in a local variable, passed to a method or another Proc, and can be called. Proc is an essential concept in Ruby and a core of its functional programming features.
Blocks are syntactic structures in Ruby; they are not objects, and cannot be manipulated as objects. It is possible, however, to create an object that represents a block. Depending on how the object is created, it is called a proc or a lambda.
When using parameters prefixed with ampersands, passing a block to a method results in a proc in the method's context. Procs behave like blocks, but they can be stored in a variable. Lambdas are procs that behave like methods, meaning they enforce arity and return as methods instead of in their parent scope.
In Ruby, a lambda is an object similar to a proc. Unlike a proc, a lambda requires a specific number of arguments passed to it, and it return s to its calling method rather than returning immediately. proc_demo = Proc.
I think the first one is actually a syntactic sugar of the other. In other words there is no behavioural difference.
What the second form allows though is to "save" the block in a variable. Then the block can be called at some other point in time - callback.
Ok. This time I went and did a quick benchmark:
require 'benchmark' class A def test 10.times do yield end end end class B def test(&block) 10.times do block.call end end end Benchmark.bm do |b| b.report do a = A.new 10000.times do a.test{ 1 + 1 } end end b.report do a = B.new 10000.times do a.test{ 1 + 1 } end end b.report do a = A.new 100000.times do a.test{ 1 + 1 } end end b.report do a = B.new 100000.times do a.test{ 1 + 1 } end end end
The results are interesting:
user system total real 0.090000 0.040000 0.130000 ( 0.141529) 0.180000 0.060000 0.240000 ( 0.234289) 0.950000 0.370000 1.320000 ( 1.359902) 1.810000 0.570000 2.380000 ( 2.430991)
This shows that using block.call is almost 2x slower than using yield.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With