Getting Started With Ruby - A Functional Language
Written by Mike James   
Wednesday, 03 April 2013
Article Index
Getting Started With Ruby - A Functional Language
Passing blocks
Reflection and Metaprogramming

Passing blocks

The use of blocks with methods is a syntactic cover that hides the fact that blocks can be passed like data.

There are both Proc and Lambda objects which can be used to wrap methods as objects so that they can be passed as parameters. The difference between the two types of "wrapping" is subtle but basically comes down the behavior of the return statement.

In a block, or a proc object that wraps it, a return acts as if it was in the calling method, i.e. the method that used the yield to call the block.

That is, return doesn't just terminate the block it terminates the calling method and the method that called it and so on.

In the case of a Lambda the return simply terminates the method and returns control to the calling program.

If you don't want to use the syntactic sugar of a block you can explicitly pass either a proc or a Lambda object.

To see this in action try:

class MyClass
 def myMethod p
  p.call 1
  p.call 2
 end
end

now the method accepts a parameter p which it assumes to be a Proc object wrapping a block of code. To execute this code you simply use the Proc object's call method. To create the Proc object all we have to do is:

myObject=MyClass.new
p1=Proc.new  {|x| puts x}
myObject.myMethod p1

where the new method accepts the block of code to be wrapped by the Proc object. There are shorter ways of writing this (using the & operator for example) but this form reveals what is actually going on.

To see the surprising behavior when a return is used within a block/Proc we need another method to call the method that calls the Proc:

class MyClass
 def myMethod1 p
  p.call 1
  puts 'End MyMethod'
 end


 def myMethod2
  p1=Proc.new  {|x| puts x}
  myMethod1 p1
  puts 'End myMethod2'
 end
end

Notice that the only real difference here is that myMethod2 calls myMethod1 in the same way as it was called in the main program. If you add:

myObject=MyClass.new
myObject.myMethod2

and run the program you will see both methods ending with suitable messages.

Now add a return to the Proc:

p1=Proc.new  {|x| puts x;return}

Now when you run the program you will see 1 printed but neither of the methods get to print their ending message.

The return in the block returns control from the block to the calling method method1, from there to method2 and from there to the main program - this is probably not what you are expecting.

A single return seems to unwind three procedure calls! Note that this works in the same way even if you use blocks and yield - as blocks default to Proc objects.

However if you change the Proc object to a Lambda object then the return just terminates the block code and the two methods get to print their final messages as you would expect:

p1=lambda  {|x| puts x;return}

Notice that lambda is a method of the Kernel object that creates a Lambda object - hence there is no call to new. 

You can also create a lambda object using a notation that is much closer to other languages:

  p1= ->  (x) {puts x;return}

This hides the fact that it is a lambda object that is being created and makes it look much more like an anonymous function.

Closures

Of course given that Ruby incorporates elements of functional programming it has closures.

That is when you create a Proc or a Lambda it incorporates the current bindings. What this means is that everything that is in scope when the object is created remains in scope for the entire lifetime of the object even if they have gone out of scope in the normal execution of the program.

For example, if you define a method that returns a Lambda object then any local method variables that the object uses are available whenever you call the wrapped code. To see this in action first define a suitable method:

class MyClass
 def myMethod
  @n=7
  return lambda {puts @n}
 end
end

Notice that the method returns a lambda object that makes use of n which is only in scope, i.e. exists, while the method is active. Even so you can write:

myObject=MyClass.new
p=myObject.myMethod
p.call

and you will see 7 displayed indicating that the variable is still available to the lambda object.

Once again unless you are familiar with the idea of closure this is surprising behavior.

Ruby actually takes this a stage further and provides a binding object which records all of the relevant bindings to the object it is created in. You can capture a set of bindings, i.e. the current state, and execute code in the context of the stored bindings  For example:

class MyClass
 def myMethod
  @n=7
  @b=binding
  @n=8
  return @b
 end
end

Notice that the binding object is created when @n is 7.

If you now try:

myObject=MyClass.new
b1=myObject.myMethod
eval("puts @n")
eval("puts @n",b1)

the first eval reveals that @n is nil because the method has ended and its local instance variables are out of scope, i.e. in the normal way of things @n doesn't exist. The second eval displays @n as not only in existence but with a value of 8, i.e. the last value that was assigned to the variable.

Notice that bindings aren't snapshots of the values in a variable, they really are the set of variables used by an object.

To see this more clearly try:

class MyClass
 def myMethod x
  @b=binding
  @n=x
  return @b
 end
end

myObject=MyClass.new
b1=myObject.myMethod 8
b2=myObject.myMethod 9
eval("puts @n")
eval("puts @n",b1)
eval("puts @n",b2)

 

The result is nil, 9, 9 as both binding objects refer to the last state of myObject.



Last Updated ( Wednesday, 03 April 2013 )
 
 

   
Copyright © 2014 i-programmer.info. All Rights Reserved.
Joomla! is Free Software released under the GNU/GPL License.