The Generalized Greenspun Rule: Any sufficiently complicated platform contains an ad hoc, informally-specified, bug-ridden, slow implementation of half of a functional programming language.
While Ruby provides an ad hoc, informally-specified, bug-ridden, slow implementation of half of higher-order functional programming, it lacks an ad hoc, informally-specified, bug-ridden, slow implementation of half of Monads.
Thus, the Invocation Construction Kit, or “Ick!” Ick provides the tools needed to easily build your own execution abstractions like the “Maybe” monad or the four canonical block evaluators, as well as providing some sugar so you can write things like:
please(sir) { may.i.have.some.more }
sudo gem install ick
Although Ruby borrows many of its features from Lisp and its syntax from Algol, it does not have block-local variables. In other words, if you declare a variable anywhere inside of a method, that variable is visible everywhere in that method. This is a problem, because it encourages writing methods where the instance variables create lot of dependencies between different expressions. Those methods can be hard to understand and refactor.
Ick solves this problem by providing a block structure method: #let. #let takes an expression and binds it to a variable inside of a block. For example, 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. Ick provides three other block structure methods (#returning, #my, and #inside) that are detailed on the Inside Ick page.
The example above is a common one. Sometimes we want to evaluate a chain of method calls without throwing a NoMethodError if one of the recipients is nil. Sometimes we want to send something a message if and only if it handles the method. There are lots of ad-hoc solutions, including Object#andand. What if you don’t want to install lots of different gems, one for each use?
Ick solves this problem by providing a structure for rolling your own guarded evaluation. You can check for nil, #respond_to?, custom permissions, whatever you like. It looks like this:
class Try < Ick::Guard guard_with { |value, sym| value.respond_to?(sym) == true } evaluates_in_calling_environment and returns_result belongs_to Object end try(...) { |sir| sir.may.i.have.some.more }
(Try is built into Ick and was inspired by Chris Wanstrath’s try and Chalain’s Turtles)
Maybe does exactly the same thing with checking nil rather than whether an object responds to a message:
maybe(...) { |person| person.manager.authority_level.permissions }
Both #try and #maybe are contagious: everything in the chain inside the block is guarded.
If you just want to call a method by name without parameters, the existing blocks work well with Symbol#to_proc:
maybe(Person.find(:first, ...), &:manager)
But you can also use these methods the way Object#andand works:
Person.find(:first, ...). maybe.time_cards. maybe.map(&:hours_worked). maybe.inject(0, &:+)
When you do that, you have to keep calling the method in order to chain them all together, so you might prefer:
maybe(Person.find(:first, ...)) { |p| p.time_cards.map(&:hours_worked).inject(0, &:+) }
The Object#andand-style syntax is most useful when you’re just using it for a single method invocation, such as:
Person.find(:first, ...).maybe.salary = 42,000
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.'" }
Have no fear of that. Ick will not modify any classes without permission. Out of the box, you cannot call any of Ick’s built in methods the way you see them in these examples. Instead of please(sir) {...}
you actually have to call Ick::Please.instance.invoke(sir) {...}
. If you want to install one or more of the built-in methods in to the Object class, you call #belongs_to. For example, to install the Maybe and Let methods but no others:
Ick::Maybe.belongs_to Object Ick::Let.belongs_to Object
You could also install some or all of the methods into a single class where you think you’ll be using them a lot but nowhere else:
class MyAwesomeImplementationOfAsteroids [Ick::Let, Ick::Returning, Ick::My, Ick::Inside].each do |clazz| clazz.belongs_to self end end
If you simply want everything you see here working exactly as it’s shown, simply call Ick.sugarize
once and all of the built-in methods will be installed into Object for you. You can put that in your environment.rb file if you’re using Rails.
The point is, try(program) { responsibly }
. You choose which classes to open and which methods to add. All I’m saying is this: before re-opening a class, did you go through the rest of your toolbox first?
Ick does provide a number of very convenient methods for abstracting evaluation. If you adopt them for your project, your code will be more readable, more succinct, and easier to refactor. That being said, it will not make your teeth whiter or help you sleep if you have a newborn.
Every programming problem can be solved with another layer of abstraction, except the problem of too many layers of abstraction
The important caveat about Ick is that its implementation adds abstraction. If all you want are two or three specific methods, writing them as simply and as directly as possible makes for a very simple implementation. Ick uses classes and templates instead of simple methods so that when you are ready to start writing your own abstractions, you can easily use the pieces that Ick has built-in. If Ick was written as a collection of cool methods, you would not be able to extend it without copying and pasting.
Ick raises how you handle things to the level of first-class objects in Ruby, so you can mix and match and separate concerns as you see fit. Logging, permissions, error handling… These are some of the places you can take Ick. Have fun.
No problem, I get that Ick isn’t exactly what you need. Why not have a look at andand? The andand gem gives you a very specialized version of Object#maybe and an enhanced Object#tap: it does a lot less but tries to do it very, very well. Have a look and let me know what you think.
https://github.com/raganwald/ick
Read the 8 steps for fixing other people’s code.
The trunk repository is git://github.com/raganwald/ick.git
for anonymous access.
This code is free to use under the terms of the MIT license.
Mobile Commons. Still Huge After All These Years.
Comments are welcome. Send an email to Reginald Braithwaite. And you can always visit weblog.raganwald.com to see what’s cooking.