Ruby Notes 5 Methods, Procs, Lambdas, and Closures
Defining Simple Methods
# Define a method named 'factorial' with a single parameter 'n'
def factorial(n)
if n < 1 # Test the argument value for validity
raise "argument must be > 0"
elsif n == 1 # If the argument is 1
1 # then the value of the method invocation is 1
else # Otherwise, the factorial of n is n times
n * factorial(n-1) # the factorial of n-1
end
end
-
Method Return Value
If a method terminates normally, then the value of the method invocation expression is the value of the last expression evaluated within the method.
-
return
The keyword
return
is used to force a return prior to the end of the method.If an expression follows the
return
keyword, then the value of that expression is returned.If no expression follows, then the return value is
nil
.
-
-
Invoking a Method on an Object
Methods are always invoked on an object.
Within the body of a method, the keyword
self
refers to the object on which the method was invoked.If we don’t specify an object when invoking a method, then the method is implicitly invoked on
self
. -
Defining Singleton Methods
global functions, instance methods, module methods, class methods
It is also possible to use
def
statement to define a singleton method that is avaiable only on a single object.The expression to define a singleton method should be followed by a period and the name of the method to be defined.
o = "message" # A string is an object def o.printme # Define a singleton method for this object puts self end o.printme # Invoke the singleton *Ruby implementations typically treat Fixnum and Symbol values as immediate values rather than as true object references. For this reason, singleton methods may not be defined on Fixnum and Symbol objects. For consistency, singletons are also prohibited on other Numeric objects.*
-
Undefing Method
Methods are defined with the
def
statement and may be undefined with theundef
statement.def sum(x,y); x+y; end # Define a method puts sum(1,2) # Use it undef sum # And undefine it
Interestingly,
undef
can be used to undefine inherited methods, without affecting the definition of the method in the class from which it is inherited.undef
cannot be used to undefine a singleton method.Within a class or module, you can also use
undef_method
(a private method ofModule
) to undefine methods. -
Method Names
By convention, method names begin with a lowercase letter.
When a method name is longer than one word, the usual convention is to separate the words with underscores
like_this
rather than using mixed caselikeThis
.-
=
,?
,!
Method names may (but are not required to) end with an equals sign, a question mark, or an exclamation point.
-
setter
An equals sign suffix signifies that the method is a setter that can be invoked using assignment syntax.
-
predicates
Any method whose name ends with a question mark are called predicates returns one the Boolean value
true
orfalse
, but not required, as any value other thanfalse
ornil
works liketrue
when a Boolean value is required that answers the question posed by the method invocation. -
mutators
Any method whose name ends with an exclamation mark should be used with caution.
Often, methods that end with an exclamation mark are mutators, which alter the internal state of an object.
-
-
Operator Methods
Many of Ruby’s operators, sucn as +,*, and even the array operator [], are implemented with methods that you can define in your own classes.
-
Method Aliases
The keyword
alias
serves to define a new name for an existing method.alias aka also_known_as # alias new_name existing_name
Aliasing is not overloading.
def hello # A nice simple method puts "Hello World" # Suppose we want to augment it... end alias original_hello hello # Give the method a backup name def hello # Now we define a new method with the old name puts "Your attention please" # That does some stuff original_hello # Then calls the original method puts "This has been a test" # Then does some more stuff end
-
-
Methods and Parentheses
Ruby allows parentheses to be omitted from most method invocation.
puts "Hello World" puts("Hello World") greeting = "Hello" size = greeting.length size = greeting.length() x = 3 x.between? 1,5 x.between?(1,5) f g x, y # f((g(x),y) not f(g(x,y)) !!! square(2+2)*2 # square(4)*2 = 16*2 = 32 square (2+2)*2 # square(4*2) = square(8) = 64
-
Method Arguments
-
Parameter Defaults
Specify a default value by following the parameter name with an equals sign and a value:
def prefix(s,len=1) s[0,len] end
Argument defaults need not be constants: they may be arbitrary expressions, and can be referred to instance variable and previous parameter in the parameter list.
# Return the last character of s or the substring from index to the end def suffix(s, index=s.size-1) s[index, s.size-index) end
Parameter defaults are evaluated when a method is invoked rather than when it is parsed.
-
Variable-Length Argument Lists and Arrays
# Return the largest of the one or more arugments passed def max(first, *rest) # Assume that the required first argument is the largest max = first # Now loop through each of the optional arguments looking for bigger ones rest.each {|x| max = x if x > max } # Return the largest one we found max end
No more than one parameter may be prefixed with an * and it should be the last parameter of the method, unless the method also has a parameter with an & prefix.
max(1) # first=1, rest=[] max(1,2) # first=1, rest=[2] max(1,2,3) # first=1, rest=[2,3]
-
Passing arrays to methods
* can also be used in a method invocation to scatter, expand, or explode the elements of an array (or range or emuerator) so that each element becomes a separate method argument.
data = [3,2,1] m = max(*data) # first=3, rest=[2,1] => 3
-
Mapping Arguments to Parameters
A little bit triky To be continued…
-
Hashes for Named Arguments
When a method requires more than two or three arguments, it can be difficult for the programmer invoking the method to remember the proper order for those argments.
Ruby does not support to write method invocations that explicitly specify a parameter name for each arugment that is passed, but it can approximated if you write a method that expects a hash as its argument or as one of its arguments.
# This method returns an array a of n numbers. For any index i, 0 <= i < n, # the value of element a[i] is m*i+c. Arguments n, m, and c are passed # as keys in a hash, so that it is not necessary to remember their order. def sequence(args) # Extract the arguments from the hash. # Note the use of the || operator to specify defaults used # if the hash does not define a key that we are interested in. n = args[:n] || 0 m = args[:m] || 1 c = args[:c] || 0 a = [] # Start with an empty array n.times {|i| a << m*i+c } # Calculate the value of each array element a # Return the array end
You might invoke this method with a hash literal argument like this:
sequence({:n=>3, :m=>5}) # => [0, 5, 10]
In order to better support this style of programming, Ruby allows you to omit the curly braces around the hash literal if it is the last argument to the method (or if the only arugment that follows it is a block argument, prefixed with &).
A hash without braces is sometinmes called a bare hash.
sequence(:m=>3, :n=>5) # => [0, 3, 6, 9, 12]
As with other ruby methods, we can omit the parentheses.
sequence c:1, m:3, n:5 # => [1, 4, 7, 10, 13]
If you omit the paretheses, then you must omit the curly braces.
# Ruby thinks you're passing a block to the method. seqence {:m=>3, :n=>5 } # Syntax error!
-
Block Arguments
Any mehtod invocation may be followed by a block, and any method that has a block associated with it may invoke the code in that block with the
yield
statement.# Generate a sequence of n numbers m*i + c and pass them to the block def sequence2(n, m, c) i = 0 while(i < n) # loop n times yield i*m + c # pass next element of the sequence to the block i += 1 end end # Here is how you might use this verison of the method sequence2(5, 2, 2) { |x| puts x } # Print numbers 2, 4, 6, 8, 10
One of the features of blocks is their anonymity.
If you prefer more explicit control over a block, add a final argument to your method, and prefix the argument name with an ampersand. The value of the argument will be a
Proc
object, and instead of usingyield
, you invoke thecall
method of theProc
.def sequence3(n, m, c, &p) # Explicit argument to get block as a Proc i = 0 while(i < n) b.call(i*m + c) # Invoke the Proc with its call method i += 1 end end # Note that the block is still passed outside of the parentheses sequence3(5, 2, 2) { |x| puts x }
Notice that using the ampersand in this way changes only the method definition.
-
Passing Proc Objects Explicityly
# This version expects an explicitly-created Proc object, not a block def sequence4(n, m, c, b) # No ampersand used for arguemnt b i = 0 while(i < n) b.call(i*m + c) # Proc is called explicitly i += 1 end end p = Proc.new { |x| puts x } # Explicity create a Proc object sequence4(5, 2, 2, p) # And pass it as an ordinary argument
Block arguments prefixed with ampersands must really the last one in the parameter list.
def sequence5(args, &b) # Pass arguments as a hash and follow with a block n, m, c = argus[:n], args[:m], args[:c] i = 0 while(i < n) b.call(i*m + c) i += 1 end end # Expects one or more arguments, followed by a block def max(first, *rest, &block) max = first rest.each { |x| max = x if x > max } block.call(max) max end
-
The
yield
satement still works in a method defined with an & parameter. Even if the block has been converted to aProc
object and passed as an argument, it can still be invoked as an anonymous block, as if the block argument was not there.-
Using & in method invocation
*
in a method definition to specify that multiple arguments should be packed into an array*
in a method invocaiton to specify that an array should be upcaked so that its elements become separate arguments&
in a method definition allows an ordinary block associated with a method invocation to be used as a namedProc
object inside the method.&
in a method invocation that before aProc
object to treat theProc
as if it was an ordinary block following the invocation.In a mehtod invocation an & typically appears before a
Proc
object. But it is accually allowed before any object with ato_proc
method (e.gMethod
andSymbol
classes).a, b = [1,2,3], [4,5] # Start with some data. summation = Proc.new { |total,x| total+x } # A Proc object for summations sum = a.inject(0, &summation) # => 6 sum = b.inject(0, &summation) # => 15
-
Procs and Lambdas
Blocks are syntactic structures in Ruby; they are not objects, and cannot be manipulated as objects.
Procs have block-like behavior and lambdas have method-like behavior, however, both are instances of class Proc
.
Creating Procs
-
Proc factory method
# This method creates a proc from a block def make_proc(&p) # Convert associated block to a Proc and store in p p # Return the Proc object end adder = make_proc { |x,y| x+y } sum = adder.call(2,2) # => 4
-
Proc.new
p = Proc.new { |x,y| x+y }
-
Kernel.lambda
is_positive = lambda { |x| x > 0 }
-
Kernel.proc
proc
is a synonym forProc.new
. -
Lambda Literals
In Ruby 1.8
succ = lambda { |x| x+1 }
In Ruby 1.9, we can convert this to a literal as follows:
- Replace the method name
lambda
with the punctuation->
. - Move the list of arguments outside of and just before the curly braces.
-
Change the argument list delimiters from
||
to()
.succ = ->(x){ x+1 }
A lambda literal uses an arrow made with a hypen, whereas a hash literal uses an arrow made with an equals sign.
# This lambada takes 2 args and declares 3 local vars f = ->(x,y; i,j,k) { ... }
Lambdas can be to declared with argument defaults.
zoom = ->(x,y,factor=2) { [x*factor, y*factor] }
As with method declarations, the parentheses in lambda literals are optional, because the parameter list and localvariable lists are completely delimited by the
->
,;
, and{
.succ = -> { x + 1 } f = -> x,y; i,j,k { ... } zoom = -> x,y,factor=2 { x*factor, y*factor }
Lambda parameters and local variables are optional.
-> {}
If you want to pass a lambda literal to a method that expects a block, prefix the literal with
&
.data.sort { |a,b| b-a } # The block version data.sort &->a,b{ b-a } # The lambda literal version
- Replace the method name
Invoking Procs and Lambdas
Procs and lambdas are objects, not methods, and they cannot be invoked in the same way that method are.
f = Proc.new { |x,y| 1.0/(1.0/x + 1.0/y) }
z = f.call(x,y)
The Proc
class also defines the array access operator to work the same as call
.
z = f[x,y]
An alternative to square brackets, you can use parentheses prefixed with a period.
z = f.(x,y)
.()
is a syntactic-sugar that invokes the call
method, and can be used with any object that defines a call
method and is not limited to Proc
objects.
How Lambdas Differ from Procs
A proc is the object form of a block, and it behaves like a block.
A lambda has slightly modified behavior and behaves more like a method than a block.
Calling a proc is like yielding to a block, whereas calling a lambda is like invoking a mehtod.
In Ruby 1.9, you can determine whether a Proc
object is a proc or a lambda with the instance method lambda?
.
-
Return in blocks, procs, and lambdas
The
return
statement in a block does not just return from the block to the invoking iterator, it reutrns from the mehtod that invoked the iterator.def test puts "entering method" 1.times { puts "entering block"; return } # Makes test method return puts "exiting method" # This line is never executed end
A proc is like a block, so if you call a proc that executes a
return
statement, it attempts to return from the method that encloses the block that was converted to the proc.def test puts "entering method" p = Proc.new { puts "entering proc"; return } p.call # Invoking the proc makes method return puts "exiting method" # This line is never executed. end
Using a
return
statement in a proc is triky. (LocalJumpError)A
return
statement in a lambda, therefore, returns from the lambda itself, not from the method that surrounds the creation site of the lambda.def test puts "entering method" p = lambda { puts "entering lambda"; return } p.call # Invoking the lambda does not make the method return puts "exiting method" # This line *is* executed now end
Closures
In Ruby, procs and lambdas are closures.
The term “closure” comes from the early days of computer science; it refers to an object that is both an invocable funciton and a varible binding for that funciton.
When you create a proc or a labmda, the resulting Proc
object holds not just the executable block but also bindings for all the varialbes used by the block.
- Closures and Shared Variables
It is important to understand that a closure does not just retain the value of the variables it refers to—it retains the actual variables and extends their lifetime.
The bindings are dynamic, and the values of the variables are looked up when the lambda or proc is executed.
As an example, the following code defines a method that returns two lambdas. Because the lambdas are defined in the same scope, they share access to the variables in that scope. When on lamda alters the values of a shared varible, the new value is available to the other lambda.
# Return a pair of lambdas that share access to a local variable.
def accessor_pair(initial_value=nil)
value = intiial_value # A local variable shared by the returned lambdas
getter = lambda { value } # Return value of local variable
setter = lambda { |x| value = x } # Change value of local variable
return getter, setter
end
getX, setX = accessor_pair(0) # Create accssor lambdas for initial value 0.
puts getX[] # Prints 0. Note square brackets instead of call.
set[10] # Change the value through one closure.
puts getX[] # Prints 10. The change is visible through the other.
-
Closures and Bindings
The
Proc
class define a method namedbinding
. Calling this method on a proc or lambda returns aBinding
object that represents the bindings in effect for that closure.A
Binding
object doesn’t have interesting methods of it own, but it can be used as the second argument to the globaleval
fuction`, providing a context in which to evaluate a string of Ruby code.In Ruby 1.9,
Binding
has its owneval
method.The use of a
Binding
object and theeval
method gives us a back door through which we can manipulate the behavior of a closure.# Return a lambda that retains or "closes over" the argument n def mulitplier(n) lambda { |data| data.collect{ |x| x*n } } end doubler = multiplier(2) # Get a lambda that knows how to double puts doubler.call([1,2,3]) # Prints 2,4,6 eval("n=3", doubler.binding) # Or doubler.binding.eval("n=3") in Ruby 1.9 puts doubler.call([1,2,3]) # Now this pritns 3,6,9!
Bindings are not only a feature of closures. The
Kernel.binding
method returns aBinding
object that represents the bindings in effect at whatever point you happen to call it.
Method Object
Ruby’s methods and blocks are executable language constructs, but they are not objects.
Procs and lambdas are object versions of blocks; they can be executed and also manipualted as data.
Ruby has powerful metaprogramming (or refleciton) capabilities, and methods can actually be represented as instance of the Method
class.
Functional Programming
Ruby is not a functional programming language in the way that languages like Lisp and Haskell are, but Ruby’s blocks, procs, and lambdas lend themselves nicely to a funcitonal programming style.