Книга: Metaprogramming Ruby 2
Назад: instance_eval()
Дальше: Writing a Domain-Specific Language

Callable Objects

Where you learn how blocks are just part of a larger family, and Bill shows you how to set code aside and execute it later.

If you get to the bottom of it, using a block is a two-step process. First, you set some code aside, and second, you call the block (with yield) to execute the code. This “package code first, call it later” mechanism is not exclusive to blocks. There are at least three other places in Ruby where you can package code:

Procs and lambdas are the big ones to talk about here. We’ll start with them and bring methods back into the picture later.

Proc Objects

Although most things in Ruby are objects, blocks are not. But why would you care about that? Imagine that you want to store a block and execute it later. To do that, you need an object.

To solve this problem, Ruby provides the standard library class Proc. A Proc is a block that has been turned into an object. You can create a Proc by passing the block to Proc.new. Later, you can evaluate the block-turned-object with Proc#call:

 
inc = Proc.new {|x| x + 1 }
 
# more code...
 
inc.call(2) ​# => 3

This technique is called a Spell: .

There are a few more ways to create Procs in Ruby. Ruby provides two Kernel Methods () that convert a block to a Proc: lambda and proc. In a short while, you’ll see that there are subtle differences between creating Procs with lambda and creating them in any other way, but in most cases you can just use whichever one you like best:

 
dec = lambda {|x| x - 1 }
 
dec.class ​# => Proc
 
dec.call(2) ​# => 1

Also, you can create a lambda with the so-called “stabby lambda” operator:

 
p = ->(x) { x + 1 }

Notice the little arrow. The previous code is the same as the following:

 
p = lambda {|x| x + 1 }

So far, you have seen not one, but four different ways to convert a block to a Proc. There is also a fifth way, which deserves its own section.

The & Operator

A block is like an additional, anonymous argument to a method. In most cases, you execute the block right there in the method, using yield. In two cases, yield is not enough:

In both cases, you need to point at the block and say, “I want to use this block”—to do that, you need a name. To attach a binding to the block, you can add one special argument to the method. This argument must be the last in the list of arguments and prefixed by an & sign. Here’s a method that passes the block to another method:

 
def​ math(a, b)
 
yield​(a, b)
 
end
 
 
def​ do_math(a, b, &operation)
 
math(a, b, &operation)
 
end
 
 
do_math(2, 3) {|x, y| x * y} ​# => 6

If you call do_math without a block, the &operation argument is bound to nil, and the yield operation in math fails.

What if you want to convert the block to a Proc? As it turns out, if you referenced operation in the previous code, you’d already have a Proc object. The real meaning of the & is this: “I want to take the block that is passed to this method and turn it into a Proc.” Just drop the &, and you’ll be left with a Proc again:

 
def​ my_method(&the_proc)
 
the_proc
 
end
 
 
p = my_method {|name| ​"Hello, ​#{name}​!"​ }
 
p.class ​# => Proc
 
p.call(​"Bill"​) ​# => "Hello, Bill!"

You now know a bunch of different ways to convert a block to a Proc. But what if you want to convert it back? Again, you can use the & operator to convert the Proc to a block:

 
def​ my_method(greeting)
 
"​#{greeting}​, ​#{​yield​}​!"
 
end
 
 
my_proc = proc { ​"Bill"​ }
 
my_method(​"Hello"​, &my_proc)

When you call my_method, the & converts my_proc to a block and passes that block to the method.

Now you know how to convert a block to a Proc and back again. Let’s look at a real-life example of a callable object that starts its life as a lambda and is then converted to a regular block.

The HighLine Example

The HighLine gem helps you automate console input and output. For example, you can tell HighLine to collect comma-separated user input and split it into an array, all in a single call. Here’s a Ruby program that lets you input a comma-separated list of friends:

 
require ​'highline'
 
 
hl = HighLine.new
 
friends = hl.ask(​"Friends?"​, lambda {|s| s.split(​','​) })
 
puts ​"You're friends with: ​#{friends.inspect}​"
<= 
Friends?
=> 
Ivana, Roberto, Olaf
<= 
You're friends with: ["Ivana", " Roberto", " Olaf"]

You call HighLine#ask with a string (the question for the user) and a Proc that contains the post-processing code. (You might wonder why HighLine requires a Proc argument rather than a simple block. Actually, you can pass a block to ask, but that mechanism is reserved for a different HighLine feature.)

If you read the code of HighLine#ask, you’ll see that it passes the Proc to an object of class Question, which stores the Proc as an instance variable. Later, after collecting the user’s input, the Question passes the input to the stored Proc.

If you want to do something else to the user’s input—say, change it to uppercase—you just create a different Proc:

 
name = hl.ask(​"Name?"​, lambda {|s| s.capitalize })
 
puts ​"Hello, ​#{name}​"
<= 
Name?
=> 
bill
<= 
Hello, Bill

This is an example of Deferred Evaluation ().

Procs vs. Lambdas

You’ve learned a bunch of different ways to turn a block into a Proc: Proc.new, lambda, the & operator…. In all cases, the resulting object is a Proc.

Confusingly, though, Procs created with lambda actually differ in some respects from Procs created any other way. The differences are subtle but important enough that people refer to the two kinds of Procs by distinct names: Procs created with lambda are called lambdas, while the others are simply called procs. (You can use the Proc#lambda? method to check whether the Proc is a lambda.)

One word of warning before you dive into this section: the difference between procs and lambdas is probably the most confusing feature of Ruby, with lots of special cases and arbitrary distinctions. There’s no need to go into all the gory details, but you need to know, at least roughly, the important differences.

There are two differences between procs and lambdas. One has to do with the return keyword, and the other concerns the checking of arguments. Let’s start with return.

Procs, Lambdas, and return

The first difference between lambdas and procs is that the return keyword means different things. In a lambda, return just returns from the lambda:

 
def​ double(callable_object)
 
callable_object.call * 2
 
end
 
 
l = lambda { ​return​ 10 }
 
double(l) ​# => 20

In a proc, return behaves differently. Rather than return from the proc, it returns from the scope where the proc itself was defined:

 
def​ another_double
 
p = Proc.new { ​return​ 10 }
 
result = p.call
 
return​ result * 2 ​# unreachable code!
 
end
 
 
another_double ​# => 10

If you’re aware of this behavior, you can steer clear of buggy code like this:

 
def​ double(callable_object)
 
callable_object.call * 2
 
end
 
 
p = Proc.new { ​return​ 10 }
 
double(p) ​# => LocalJumpError

The previous program tries to return from the scope where p is defined. Because you can’t return from the top-level scope, the program fails. You can avoid this kind of mistake if you avoid using explicit returns:

 
p = Proc.new { 10 }
 
double(p) ​# => 20

Now on to the second important difference between procs and lambdas.

Procs, Lambdas, and Arity

The second difference between procs and lambdas concerns the way they check their arguments. For example, a particular proc or lambda might have an arity of two, meaning that it accepts two arguments:

 
p = Proc.new {|a, b| [a, b]}
 
p.arity ​# => 2

What happens if you call this callable object with three arguments, or one single argument? The long answer to this question is complicated and littered with special cases. The short answer is that, in general, lambdas tend to be less tolerant than procs (and regular blocks) when it comes to arguments. Call a lambda with the wrong arity, and it fails with an ArgumentError. On the other hand, a proc fits the argument list to its own expectations:

 
p = Proc.new {|a, b| [a, b]}
 
p.call(1, 2, 3) ​# => [1, 2]
 
p.call(1) ​# => [1, nil]

If there are too many arguments, a proc drops the excess arguments. If there are too few arguments, it assigns nil to the missing arguments.

Procs vs. Lambdas: The Verdict

You now know the differences between procs and lambdas. But you’re wondering which kind of Proc you should use in your own code.

Generally speaking, lambdas are more intuitive than procs because they’re more similar to methods. They’re pretty strict about arity, and they simply exit when you call return. For this reason, many Rubyists use lambdas as a first choice, unless they need the specific features of procs.

Method Objects

For the sake of completeness, you might want to take one more look at the last member of the callable objects’ family: methods. If you’re not convinced that methods, like lambdas, are just callable objects, look at this code:

 
class​ MyClass
 
def​ initialize(value)
 
@x = value
 
end
 
def​ my_method
 
@x
 
end
 
end
 
 
object = MyClass.new(1)
 
m = object.method :my_method
 
m.call ​# => 1

By calling Kernel#method, you get the method itself as a Method object, which you can later execute with Method#call. In Ruby 2.1, you also have Kernel#singleton_method, which converts the name of a Singleton Method () to a Method object. (What are you saying? You don’t know what a Singleton Method is yet? Oh, you will, you will…)

A Method object is similar to a block or a lambda. Indeed, you can convert a Method to a Proc by calling Method#to_proc, and you can convert a block to a method with define_method. However, an important difference exists between lambdas and methods: a lambda is evaluated in the scope it’s defined in (it’s a closure, remember?), while a Method is evaluated in the scope of its object.

Ruby has a second class that represents methods—one you might find perplexing. Let’s have a look at it first, and then we’ll see how it can be used.

Unbound Methods

UnboundMethods are like Methods that have been detached from their original class or module. You can turn a Method into an UnboundMethod by calling Method#unbind. You can also get an UnboundMethod directly by calling Module#instance_method, as in the following example:

 
module​ MyModule
 
def​ my_method
 
42
 
end
 
end
 
 
unbound = MyModule.instance_method(:my_method)
 
unbound.class ​# => UnboundMethod

You can’t call an UnboundMethod, but you can use it to generate a normal method that you can call. You do that by binding the UnboundMethod to an object with UnboundMethod#bind. UnboundMethods that come from a class can only be bound to objects of the same class (or a subclass), while UnboundMethods that come from a module have no such limitation from Ruby 2.0 onward. You can also bind an UnboundMethod by passing it to Module#define_method, as in the next example:

 
String.class_eval ​do
 
define_method :another_method, unbound
 
end
 
 
"abc"​.another_method ​# => 42

UnboundMethods are used only in very special cases. Let’s look at one of those.

The Active Support Example

The Active Support gem contains, among other utilities, a set of classes and modules that automatically load a Ruby file when you use a constant defined in that file. This “autoloading” system includes a module named Loadable that redefines the standard Kernel#load method. If a class includes Loadable, then Loadable#load gets lower than Kernel#load on its chain of ancestors—so a call to load will end up in Loadable#load.

In some cases, you might want to remove autoloading from a class that has already included Loadable. In other words, you want to stop using Loadable#load and go back to the plain vanilla Kernel#load. Ruby has no uninclude method, so you cannot remove Loadable from your ancestors once you have included it. Active Support works around this problem with a single line of code:

 
module​ Loadable
 
def​ self.exclude_from(base)
 
base.class_eval { define_method(:load, Kernel.instance_method(:load)) }
 
end
 
 
# ...

Imagine that you have a MyClass class that includes Loadable. When you call Loadable.exclude_from(MyClass), the code above calls instance_method to get the original Kernel#load as an UnboundMethod. Then it uses that UnboundMethod to define a brand-new load method directly on MyClass. As a result, MyClass#load is actually the same method as Kernel#load, and it overrides the load method in Loadable. (If that sounds confusing, try drawing a picture of MyClass’s ancestors chain, and everything will be clear.)

This trick is an example of the power of UnboundMethods, but it’s also a contrived solution to a very specific problem—a solution that leaves you with a confusing chain of ancestors that contains three load methods, two of which are identical to each other (Kernel#load and MyClass#load), and two of which are never called (Kernel#load and Loadable#load). It’s probably good policy not to try this kind of class hacking at home.

Callable Objects Wrap-Up

Callable objects are snippets of code that you can evaluate, and they carry their own scope along with them. They can be the following:

Different callable objects exhibit subtly different behaviors. In methods and lambdas, return returns from the callable object, while in procs and blocks, return returns from the callable object’s original context. Different callable objects also react differently to calls with the wrong arity. Methods are stricter, lambdas are almost as strict (save for some corner cases), and procs and blocks are more tolerant.

These differences notwithstanding, you can still convert from one callable object to another, such as by using Proc.new, Method#to_proc, or the & operator.

Назад: instance_eval()
Дальше: Writing a Domain-Specific Language