ruby Blocks Can Be Closures(摘自programming ruby)
Let’s get back to our jukebox for a moment (remember the jukebox?). At some point
we’ll be working on the code that handles the user interface—the buttons that people
press to select songs and control the jukebox. We’ll need to associate actions with
those buttons: press START and the music starts. It turns out that Ruby’s blocks are
a convenient way to do this. Let’s start by assuming that the people who made the
hardware implemented a Ruby extension that gives us a basic button class. (We talk
about extending Ruby beginning on page 261.)
What happens when the user presses one of our buttons? In the Button class, the hardware
folks rigged things so that a callback method, button_pressed, will be invoked.
The obvious way of adding functionality to these buttons is to create subclasses of
Button and have each subclass implement its own button_pressed method.
This has two problems. First, this will lead to a large number of subclasses. If the
interface to Button changes, this could involve us in a lot of maintenance. Second, the
actions performed when a button is pressed are expressed at the wrong level; they are
not a feature of the button but are a feature of the jukebox that uses the buttons.We can
fix both of these problems using blocks.
The key to all this is the second parameter to JukeboxButton#initialize. If the last
parameter in a method definition is prefixed with an ampersand (such as &action),
Ruby looks for a code block whenever that method is called. That code block is converted
to an object of class Proc and assigned to the parameter. You can then treat
the parameter as any other variable. In our example, we assigned it to the instance
variable @action. When the callback method button_pressed is invoked, we use the
Proc#call method on that object to invoke the block.
So what exactly do we have when we create a Proc object? The interesting thing is that
it’s more than just a chunk of code. Associated with a block (and hence a Proc object)
is all the context in which the block was defined: the value of self and the methods,
variables, and constants in scope. Part of the magic of Ruby is that the block can still
use all this original scope information even if the environment in which it was defined
would otherwise have disappeared. In other languages, this facility is called a closure.
Let’s look at a contrived example. This example uses the method lambda, which converts
a block to a Proc object.
The method n_times returns a Proc object that references the method’s parameter,
thing. Even though that parameter is out of scope by the time the block is called, the
parameter remains accessible to the block.
we’ll be working on the code that handles the user interface—the buttons that people
press to select songs and control the jukebox. We’ll need to associate actions with
those buttons: press START and the music starts. It turns out that Ruby’s blocks are
a convenient way to do this. Let’s start by assuming that the people who made the
hardware implemented a Ruby extension that gives us a basic button class. (We talk
about extending Ruby beginning on page 261.)
1start_button = Button.new("Start")
2pause_button = Button.new("Pause")
3#
4
2pause_button = Button.new("Pause")
3#
4
What happens when the user presses one of our buttons? In the Button class, the hardware
folks rigged things so that a callback method, button_pressed, will be invoked.
The obvious way of adding functionality to these buttons is to create subclasses of
Button and have each subclass implement its own button_pressed method.
1class StartButton < Button
2def initialize
3super("Start") # invoke Button's initialize
4end
5def button_pressed
6# do start actions
7end
8end
9start_button = StartButton.new
10
2def initialize
3super("Start") # invoke Button's initialize
4end
5def button_pressed
6# do start actions
7end
8end
9start_button = StartButton.new
10
This has two problems. First, this will lead to a large number of subclasses. If the
interface to Button changes, this could involve us in a lot of maintenance. Second, the
actions performed when a button is pressed are expressed at the wrong level; they are
not a feature of the button but are a feature of the jukebox that uses the buttons.We can
fix both of these problems using blocks.
1songlist = SongList.new
2class JukeboxButton < Button
3def initialize(label, &action)
4super(label)
5@action = action
6end
7def button_pressed
8@action.call(self)
9end
10end
11start_button = JukeboxButton.new("Start") { songlist.start }
12pause_button = JukeboxButton.new("Pause") { songlist.pause }
13
2class JukeboxButton < Button
3def initialize(label, &action)
4super(label)
5@action = action
6end
7def button_pressed
8@action.call(self)
9end
10end
11start_button = JukeboxButton.new("Start") { songlist.start }
12pause_button = JukeboxButton.new("Pause") { songlist.pause }
13
The key to all this is the second parameter to JukeboxButton#initialize. If the last
parameter in a method definition is prefixed with an ampersand (such as &action),
Ruby looks for a code block whenever that method is called. That code block is converted
to an object of class Proc and assigned to the parameter. You can then treat
the parameter as any other variable. In our example, we assigned it to the instance
variable @action. When the callback method button_pressed is invoked, we use the
Proc#call method on that object to invoke the block.
So what exactly do we have when we create a Proc object? The interesting thing is that
it’s more than just a chunk of code. Associated with a block (and hence a Proc object)
is all the context in which the block was defined: the value of self and the methods,
variables, and constants in scope. Part of the magic of Ruby is that the block can still
use all this original scope information even if the environment in which it was defined
would otherwise have disappeared. In other languages, this facility is called a closure.
Let’s look at a contrived example. This example uses the method lambda, which converts
a block to a Proc object.
1def n_times(thing)
2return lambda {|n| thing * n }
3end
4p1 = n_times(23)
5p1.call(3) #->69
6p1.call(4) #->92
7p2 = n_times("Hello ")
8p2.call(3) #->"Hello Hello Hello "
2return lambda {|n| thing * n }
3end
4p1 = n_times(23)
5p1.call(3) #->69
6p1.call(4) #->92
7p2 = n_times("Hello ")
8p2.call(3) #->"Hello Hello Hello "
The method n_times returns a Proc object that references the method’s parameter,
thing. Even though that parameter is out of scope by the time the block is called, the
parameter remains accessible to the block.