Inside the Invocation Construction Kit

Get Version

0.3.1

More about the four block structures

Ick provides #let, a method for block-structuring Ruby code. As already shown, if you want someone’s phone number only if they are a friend: let(Person.find(:first, ...)) { |person| person.phone_number if person.friend? }

This code makes it clear that you only need the person variable inside the block. If you want to refactor this code, you know that the entire expression can move without breaking another piece of code.

#let, #returning, #my, and #inside

As shown above, #let does the obvious thing: it passes a value to a block, it evaluates the block in the caller’s environment, and it returns the result of the block. Hmmm…

There are two binary decisions to be made about every block: First, do you want to evaluate the block in the calling environment (which is how almost every block is evaluated in Ruby), or do you want to evaluate the block in the value’s context. In other words, does self stay the same, or does it become the value in the block?

If you want something that behaves like #let but evaluates in the value’s environment just like #please, you can use #my:

my(Person.find(:first, ...)) do
	first_name = 'Charles'
	last_name = 'Babbage'
	friends << 'Ada Lovelace'
end

This will return Charles Babbage’s friends. On the surface, evaluates_in_value_environment is about the syntactic sugar of dropping an instance variable. But with a little thought, you can come up with some really cool way to (mis)use this capability.

So #let and #my both pass an expression to a block and return the result. Given that they both ‘declare’ returns_result, this is not surprising. But there is another choice: returns_value instead of returns_result. Ruby on Rails includes the popular #returning method, and it works the same in Ick:

returning(Person.find(:first, ...)) do |p|
	p.first_name = 'Charles'
	p.last_name = 'Babbage'
	p.friends << 'Ada Lovelace'
end

This returns the person record, not the list of friends. The block is evaluated strictly for side effects. And what happens if we want to return the value and also evaluate in the value’s environment?

inside(Person.find(:first, ...)) do
	first_name = 'Charles'
	last_name = 'Babbage'
	friends << 'Ada Lovelace'
end

The method #inside returns the value and evaluates the block in the value’s environment.

(The four methods were inspired by Michiel de Mare’s post on the same subject, although Ick’s nomenclature is not compatible with Michiel’s. Michiel’s #rsss, #rrss, #rsds, and #rrds are called #returning, #let, #inside, and #my in Ick.)

lets

Ick version 0.3 includes an experiemental form of #let, #lets, that allows you to bind multiple variables:

lets(
    :person => Person.find(:first, ...),
    :place  => City.select { ... },
    :thing  => %w(ever loving blue eyed)) {
  "#{person.name} lives in #{place} where he is known as the '#{thing} thing.'"
}

You must have the ruby2ruby gem installed to use #lets. You know the drill:

sudo gem install ruby2ruby

Like #let, #lets evaluates_in_value_environment and returns_result. You can roll your own variations if you want something else. If you’re familiar with Lisp, #lets is a lot like Lisp’s simplest let macro. But not like letrec or let*. If you don’t know the difference, then #lets will not suprise you. Basically, the binding of values to names works a lot like you would expect a hash to behave.

For example, if I wrote:

a = nil
a = { :foo => call_something_else(), :bash => a[:foo] }

You would consider this bad code, wouldn’t you? There’s no expectation that the espressions are evaluated in any particular order. It’s the same with #lets. This is probably a bad idea:

foo = nil
lets(:foo => call_something_else(), :bash => foo) { ... }

bash is not going to be bound to the return value of call_something_else, it’s going to be bound to nil. The variables you bind are only visible inside the block.

What about #try and #maybe?

The methods #try and #maybe are both implemented as evaluates_in_calling_environment, because that is least surprising. But when you’re rolling your own, you might want to change that to make things more sugary. For example, here is a different version of #try:

class Please < Ick::Guard
  guard_with { |value, sym| value.respond_to?(sym) == true }
  evaluates_in_value_environment and returns_result
  belongs_to Object
end

please(...) { may.i.have.some.more }

The method #please executes in the value’s environment, and thus it can call methods directly. Here is the key point about Ick as compared to rolling your own methods directly: you can combine and recombine the parts to make new kinds of methods. As you just saw, we can make #try and #please out of the same building blocks. This is why Ick is a “contruction kit,” not just a collection of handy syntactic short cuts.

Wrap Music

The four methods #let, #returning, #my, and #inside are fairly simple. There’s some finagling with what they return and their evaluation environment, but evaluation within the environment is 100% standard Ruby. But things get really interesting when we want to actually change Ruby’s evaluation behaviour. Specifically, when we want to change what it means to call a method on the value we provide.

That’s what #maybe, #please, and #try all do: when a method is called on the value, they intercept it and decide whether to call the method or prematurely return nil. Ick can’t actually change the behaviour of the Ruby interpreter, so what it does is as close to evil metaprogramming as possible. Ick doesn’t do any method redefinition or method chain manipulation, instead it performs its juju by wrapping the value in a delegator. Ick’s delegators are called Wrappers.

Ick’s built-in methods that use wrappers inherit from Ick::Wrap. When the result is returned from the block, it is unwrapped if it is wrapped.

Hand Rolling

You can use Ick::Wrap along with your own wrappers by calling #invoke_wrapped:

class MyWrapper < Ick::Wrapper
  def initialize(value)
    # ...
  end
  # ...
end

Ick::Wrap.instance.invoke_wrapped(Person.find(:first, ...), MyWrapper)

If you need additional parameters for your wrapper, you can declare them in the initializer and pass them to #invoke_wrapped:

class MyWrapper < Ick::Wrapper
  def initialize(value, extra1, extra2)
    # ...
  end
  # ...
end

Ick::Wrap.instance.invoke_wrapped(Person.find(:first, ...), MyWrapper, :foo, :bar)

The methods #maybe, #please, and #try are all implemented as subclasses of Ick:Guard. Have a look at guard.rb to see how to hide the extra wrapping syntax that #invoke_wrapped requires.

ArrayWrapper

Ick::ArrayWrapper wraps a collection of receivers. Any message sent to the wrapper is dispatched to each of the receivers in the collection. Here’s a simplified implementation:

class ArrayWrapper < Wrapper
  
  def __invoke__(sym, *args, &block)
    @value.map { |_| _.__send__(sym, *args, &block) }
  end
  
  is_contagious
  
end

What on Earth is it for? Specifically, what’s wrong with just using Enumerable#map if we want to send the same message(s) to each receiver in a collection?

The answer is that when we use Enumerable#map, it executes the entire block once for each receiver. So if there are side effects in the block, they are executed once for each receiver as well. Whereas ArrayWrapper dispatches the messages to each receiver without executing side effects more than once.

Ick::Tee provides a method for using an ArrayWrapper with return_value semantics. When you use #tee, it evaluates the block once, but dispatches methods to each of the values you pass in. It returns the first value you pass in. This can be handy for things like sending logging information to more than one log.

Here’s one of the test cases you can find in the gem:

def test_tee_semantics
  array_logger_one = []
  array_logger_two = []
  line_number = 0
  [array_logger_one, array_logger_two].map do |log|
    line_number += 1 
    log << "A#{line_number}"
    line_number += 1 
    log << "B#{line_number}"
  end
  assert_equal(%w(A1 B2), array_logger_one)
  assert_equal(%w(A3 B4), array_logger_two)
  array_logger_one = []
  array_logger_two = []
  line_number = 0
  tee(array_logger_one, array_logger_two) do |log|
    line_number += 1 
    log << "A#{line_number}"
    line_number += 1 
    log << "B#{line_number}"
  end
  assert_equal(%w(A1 B2), array_logger_one)
  assert_equal(%w(A1 B2), array_logger_two)
end

When we used Enumerable#map, the side effect line_number += 1 is evaluated four times, twice for each receiver. However when we use #tee, the side effect line_number += 1 is only evaluated twice, even though we have two receivers. Now that you know about the four execution possibilities encapsulated by #let, #returning, #my, and #inside, you can probably guess what #fork does when I tell you that its implementation returns_result.

Beware!

Because Ick::Wrap is passing a wrapper into the block instead of the original value, you can get unexpected results under certain circumstances. For example, if you assign the value something else within the block such as an instance variable, when the block exits the value will still have whatever behaviour the wrapper creates.

Therefore, wrappers are unidirectional: they only work when you are calling methods on your value, not when you are passing the wrapped value as a parameter to any other method. This is hideous: why should there be any difference between { |value| value + 1 } and { |value| 1 + value }?

Also, wrappers are external, they only affect messages sent to the wrapped value in your block. For example, if you are using #try to avoid NoMethodErrors, any messages you send to the wrapped value will handled by the wrapper. But what happens if the wrapped value sends itself a message that it doesn’t handle? Aha, that will raise a NoMethodError!

Wrappers are a very leaky abstraction, which is why I try to name things a little closer to what they actually do than what they purport to do but don’t really do. For example, we write try { something.or.other } to emphasize that we are only trying the things you see in front of you.

Why Ick?

As just mentioned, Ick is a construction kit. By all means install the gem and go wild with #let, #returning, #my, #inside, #try, and #maybe. But have a look under the hood. Ick uses classes and template methods to replicate what can be done in a few lines of explicit code. For example, Object#returning is implemented in Rails as:

class Object
  def returning(value)
    yield(value)
    value
  end
end

So why bother with Ick? Well, if you want to make a method just like Object#returning, only X (for some value of X), you can’t do that without copying, pasting, and modifying. Ick’s classes are included specifically so you can subclass things and make your own new kinds of methods that are variations of the existing methods.

Thus, the extra abstraction is appropriate if you want to use the built-in methods as a starting point for your own exploratory programming. And if you don’t care, you just want the methods, by all means install the gem and just use them. Don’t worry about the implementation unless you identify it as a performance problem.

Where do you want to go today?

The point behind abstracting invocation and evaluation is that you can separate concerns. For example, which methods to chain is one concern. How to handle nil or an object that does not respond to a method is a separate concern. Should you raise and handle and exception? Return nil? log an error? Why should error handling and logging be intermingled with your code?

With Ick, you can separate the two issues. You can even make the handling pluggable. For example, if instead of calling #let you call your own method, you could sometimes invoke Ick::Let with a block and other times invoke your own handler, perhaps one that logs every method called.

Have fun!

Administrivia

Home Sweet Home

ick.rubyforge.org

How to submit patches

Read the 8 steps for fixing other people’s code.

The trunk repository is svn://rubyforge.org/var/svn/ick/trunk for anonymous access.

License

This code is free to use under the terms of the MIT license.

Shout Out

Mobile Commons. Still Huge After All These Years.

Contact

Comments are welcome. Send an email to Reginald Braithwaite. And you can always visit weblog.raganwald.com to see what’s cooking.

Reginald Braithwaite, 30th May 2008
Theme extended from Paul Battley