Iteration and Nested Blocks
I recently ran into an interesting design challenge involving the Resource Acquisition Is Initialization (RAII) idiom and the Composite design pattern.
Consider a simple Switch
class that models an on/off switch:
class Switch
def initialize
@on = false
end
def on?
@on
end
def turn_on
@on = true
end
def turn_off
@on = false
end
end
Let’s say we have some actions that must be performed when the switch is off. We could write the code explicitly every time we perform any of the actions:
switch = Switch.new
switch.turn_off
perform_action
This works, but forces the switch off and leaves it that way. That is very likely to mess up other code, so it would be better to make sure we restore the original state of the switch when we’re done with it:
switch = Switch.new
was_on = switch.on?
switch.turn_off
perform_action
switch.turn_on if was_on
That’s a bit better, but still leaves the switch off if
perform_action
raises an exception, and it doesn’t return the switch
to the off state if something in perform_action
turns it on, so we’d
really want to use an ensure block and yet more code. That’s a lot of
duplication every time we need to temporarily turn off the switch.
Instead, we can use the RAII idiom that
I wrote about earlier
and
spoke about at MWRC 2014
and add a method to Switch
to help us with this:
class Switch
#...
def be_off_while
was_on = on?
turn_off
yield
ensure
was_on ? turn_on : turn_off
end
#...
end
#...
switch = Switch.new
switch.be_off_while { perform_action }
That’s better.
Now, what if we had a bank of switches, and we needed all of the switches to be off while performing the action?
switch1 = Switch.new
switch2 = Switch.new
switch3 = Switch.new
#...
switch1.be_off_while do
switch2.be_off_while do
switch3.be_off_while do
perform_action
end
end
end
I guess that works, but it’s pretty fragile if we decide to change how many switches are in the bank. If we’re going to be managing banks of switches a lot, we can turn to the Composite pattern:
class SwitchBank
def initialize(switch_count)
@switches = Array.new(switch_count) { Switch.new }
end
def on?
@switches.any?(&:on?)
end
def turn_on
@switches.each(&:turn_on)
end
def turn_off
@switches.each(&:turn_off)
end
def be_off_while
# ???
end
end
Most of SwitchBank
is easy to write; we have to decide whether the
bank is on or off if only a few of the switches are on (I chose “on”
here). But how do we write be_off_while
for this case?
One option is to duplicate the implementation from Switch
:
class SwitchBank
#...
def be_off_while
was_on = on?
turn_off
yield
ensure
was_on ? turn_on : turn_off
end
#...
end
Duplication is often a code smell, and this case is no different, but
here there’s not much code, so it’s borderline. What if
be_off_while
was a much more complex method? Duplication would be
worse in that case. Is there a way we can implement this by
delegating to Switch
’s be_off_while
method? How can we marry an
iteration of SwitchBank
’s switches with nested block calls?
Here’s a way I found to solve the problem:
class SwitchBank
#...
def be_off_while
nester = ->(switches) do
return yield if switches.empty?
switches.shift.be_off_while { nester.call(switches) }
end
nester.call(@switches.dup)
end
#...
end
Here we’re using a lambda that calls itself recursively. With
recursion, there’s always a danger of running out of stack space. For
this situation we’ll assume that a SwitchBank
won’t have enough
switches to cause a problem, but it is something to be aware of.
The lambda takes a collection of switches starting with a copy of the original array. The lambda destructively modifies the collection, so we need to make sure we’re working on a copy.
If the collection is empty (the base case of the recursion), we simply
yield
to the block, performing the desired action.
Otherwise, we remove the first element from the array and call its
be_off_while
method, nesting a recursive call to the lambda inside.
This recursive call is made on a collection that is now one element
shorter than it was (because we just shift
ed an element out of it).
Because the collection is always one element shorter, we can guarantee
that the recursion will eventually terminate.
The net result is that we nest calls to each element’s be_off_while
method and ultimately perform the desired action at the innermost
level of nesting.
I found this to be an interesting problem to solve. A functional programming expert could probably come up with a better solution than this.
I originally solved this problem in Smalltalk, but translated to Ruby for this post. In both of these languages, I find this solution a little too clever and I probably wouldn’t keep this code around for long.
For my original case, the surrounding context suggested a different way of attacking the problem, so I ended up not keeping this code.