In Combinatory Logic, a Kestrel is a function that returns a constant function, normally written Kxy = x
. In Ruby, it might look like this:
# for *any* x,
kestrel.call(:foo).call(x)
=> :foo
Although its formal name is the "K Combinator," it is more popularly named a Kestrel following the lead established in Raymond Smullyan's amazing book To Mock a Mockingbird. In this book, Smullyan explains combinatory logic and derives a number of important results by presenting the various combinators as songbirds in a forest. Since the publication of the book more than twenty years ago, the names he gave the birds have become standard nicknames for the various combinators.
Kestrels are to be found in Ruby. You may be familiar with their Ruby 1.9 name, #tap
. Let's say you have a line like address = Person.find(...).address
and you wish to log the person instance. With tap
, you can inject some logging into the expression without messy temporary variables:
address = Person.find(...).tap { |p| logger.log "person #{p} found" }.address
tap
is a method in all objects that passes self
to a block and returns self, ignoring whatever the last item of the block happens to be. Ruby on Rails programmers will recognize the Kestrel in slightly different form:
address = returning Person.find(...) do |p|
logger.log "person #{p} found"
end.address
Again, the result of the block is discarded, it is only there for side effects. This behaviour is the same as a Kestrel. Remember kestrel.call(:foo).call(x)
? If I rewrite it like this, you can see the similarity:
Kestrel.call(:foo) do
x
end
=> :foo
Both returning
and tap
are handy for grouping side effects together. Methods that look like this:
def registered_person(params = {})
person = Person.new(params.merge(:registered => true))
Registry.register(person)
person.send_email_notification
person
end
Can be rewritten using returning
:
def registered_person(params = {})
returning Person.new(params.merge(:registered => true)) do |person|
Registry.register(person)
person.send_email_notification
end
end
It is obvious from the first line what will be returned and it eliminates an annoying error when the programmer neglects to make person
the last line of the method.
object initializer blocks
The Kestrel has also been sighted in the form of object initializer blocks. Consider this example using Struct:
Contact = Struct.new(:first, :last, :email) do
def to_hash
Hash[*members.zip(values).flatten]
end
end
The method Struct#new
creates a new class. It also accepts an optional block, evaluating the block for side effects only. It returns the new class regardless of what happens to be in the block (it happens to evaluate the block in class scope, a small refinement).
You can use this technique when writing your own classes:
class Bird < Creature
def initialize(*params)
# do something with the params
yield self if block_given?
end
end
Forest.add(
Bird.new(:name => 'Kestrel) { |k| combinators << k }
)
The pattern of wanting a Kestrel/returning/tap when you create a new object is so common that building it into object initialization is useful. And in fact, it's built into ActiveRecord
. Methods like new
and create
take optional blocks, so you can write:
class Person < ActiveRecord::Base
# ...
end
def registered_person(params = {})
Person.new(params.merge(:registered => true)) do |person|
Registry.register(person)
person.send_email_notification
end
end
In Rails, returning
is not necessary when creating instances of your model classes, thanks to ActiveRecord's built-in object initializer blocks.
a variation on the kestrel
When we discussed Struct
above, we noted that its initializer block has a slightly different behaviour than tap
or returning
. It takes an initializer block, but it doesn't pass the new class to the block as a parameter, it evaluates the block in the context of the new class.
Putting this into implementation terms, it evaluates the block with self
set to the new class. This is not the same as returning
or tap
, both of which leave self
untouched. We can write our own version of returning
with the same semantics. We will call it inside
:
module Kernel
def inside(value, &block)
value.instance_eval(&block)
value
end
end
You can use this variation on a Kestrel just like returning
, only you do not need to specify a parameter:
inside [1, 2, 3] do
uniq!
end
=> [1, 2, 3]
This isn't particularly noteworthy. Of more interest is your access to private methods and instance variables:
sna = Struct.new('Fubar') do
attr_reader :fu
end.new
inside(sna) do
@fu = 'bar'
end
=> <struct Struct::Fubar >
sna.fu
=> 'bar'
inside
is a Kestrel just like returning
. No matter what value its block generates, it returns its primary argument. The only difference between the two is the evaluation environment of the block.
So what have we learned?
tap
,returning
, andinside
are useful;- "Impractical" Computer Science isn't, and;
- To Mock a Mockingbird belongs on your bookshelf if it isn't there already. The Kestrel is just one bird. Imagine what code you could write with a forest of them at your fingertips!
post scriptum
returning
is part of Ruby on Rails.tap
is part of Ruby 1.9. It is available for Ruby 1.8 as part of the andand gem.sudo gem install andand
.- inside.rb: If you are using Rails, drop it in
config/initializers
to make it available in your project. - You keep using that idiom. I do not think it means what you think it means.
More on combinators: Kestrels, The Thrush, Songs of the Cardinal, Quirky Birds and Meta-Syntactic Programming, Aspect-Oriented Programming in Ruby using Combinator Birds, The Enchaining and Obdurate Kestrels, Finding Joy in Combinators, Refactoring Methods with Recursive Combinators, Practical Recursive Combinators, The Hopelessly Egocentric Blog Post, Wrapping Combinators, and Mockingbirds and Simple Recursive Combinators in Ruby.
My recent work:
- "JavaScript Allongé,CoffeeScript Ristretto", "Kestrels, Quirky Birds, and Hopeless Egocentricity" and my other books.
- allong.es, practical function combinators and decorators for JavaScript.
- Method Combinators, a CoffeeScript/JavaScript library for writing method decorators, simply and easily.
- jQuery Combinators, what else? A jQuery plugin for writing your own fluent, jQuery-like code.
(Spot a bug or a spelling mistake? This is a Github repo, fork it and send me a pull request!)