Search code examples
rubyblock

Eloquent Ruby - saving code blocks to execute later example


In Eloquent Ruby there's a code example I don't understand.

class Document
  attr_accessor :save_listener

  # most of the class omitted...

  def on_save( &block )
    @save_listener = block
  end

  def save( path )
    File.open( path, 'w' ) { |f| f.print( @contents ) }
    @save_listener.call( self, path ) if @save_listener
  end
end

# usage
my_doc = Document.new( 'block based example', 'russ', '' )
my_doc.on_save do |doc|
  puts "Hey, I've been saved!"
end

Why is it that @save_listener.call( self, path ) takes TWO arguments? The block that's saved looks like it only has ONE parameter |doc|. Is this a typo in the book or is there something here I'm missing?

I even tried typing this code in and executing it and I found I can add as many parameters as I want and there wont be any errors. But I still don't get why there are TWO parameters in this example.


Solution

  • This is due to a subtle difference between Proc and Lambda. When you create a new Proc with a block of code, you can pass as many arguments as you'd like to it when you call it. For instance:

    proc = Proc.new {|a,b| a + b}
    proc.arity #=> 2 -- the number of arguments the Proc expects
    proc.call(4,8) #=> 12
    proc.call(4,8,15,16,23,42) #=> 12
    

    It is taking in those arguments but just not assigning them to any of the variables in your block.

    However, a Lambda cares about the number of arguments.

    proc = lambda {|a,b| a + b}
    proc.arity #=> 2
    proc.call(4,8) #=> 12
    proc.call(4,8,15,16,23,42) #=> ArgumentError: wrong number of arguments (6 for 2)
    

    The reason for this is because Proc.call assigns the method's arguments similar to parallel assignment of variables.

    num1, num2 = 1,2 #=> num1 is 1, num2 is 2
    num1, num2 = 1  #=> num1 is 1, num2 is nil
    num1, num2 = 1,2,3,4,5 #=> num1 is 1, num2 is 2 and the rest are discarded
    

    However, Lambda does not work like this. Lambda acts more like a method call than a variable assignment.

    So, if you are worried about only allowing a certain number of arguments, use Lambda. However, in this example, since there is a chance you can add a path to the block, a Proc is best.