Three months ago I was comparing Lisp, Smalltalk and Ruby. I was struggling to create new language syntax (for example my version of if..else) that is so easy to do in Lisp, and also possible in Smalltalk:
In Smalltalk it would be used like this:
|
My knowledge of Smalltalk is non-existing, but look at the beautiful [] block syntax and notice the keyword arguments being able to accept multiple blocks.
Implementation in Smalltalk is both straightforward and cool:
|
In Ruby I was missing the ability to have multiple blocks in method call that would enable me to evaluate only one leg. This was best I could come up with and it included (mis)using the lambdas:
|
Today I’ve read the excellent post about using Ruby Blocks as Dynamic Callbacks. I was mesmerized, but of course I didn’t get it. Than I saw really kick ass explanation here. Holding hands did have effect even on me. Still I am not sure that I would be able to pull it of on my own without watching the original code. At the end I did saw one gist in comments claiming to have similar pattern. I don’t know if it’s better, but it looks less intrusive because there is no patching of Proc class and it clicked more easily.
As an exercise I’ve decided to use technique described there to implement my version of if..else:
|
It’s relatively easy to explain how it works. Look at the definition of Custom class:
|
line 2 : Using self.if method that receives condition and the block
|
- line 3 : Creating the BlockProxy that is responding to missing method call in a way to:
- If there is passed in block then store it under method name in @callbacks variable
- If there is no passed block then raise the classic no method found exception
If you now look at usage of Custom class
|
notice that |condition| is really a BlockProxy object that you’ve tapped in on line 13.
So when you call:
|
you invoke method_missing on a BlockProxy that stores received block in @callbacks hash with :true? key:
|
It’s all great from there on. Next call to false? invokes method_missing again and stores passed in block under :false? key:
|
When all of this is set and done I get to implement my version of if..else with built in if..else (line 4..8 in Custom class) :)
Conclusion
The thing is that with this technique I can do whatever I want with my version of if..else. I may alter it to log something when condition is true or do something similarly outrageous. Also it looks as if I could easily implement switch…case idiom etc. Is it practical to replace existing syntax? I don’t think so, but it really is intriguing and I am glad that it can be pulled of in Ruby like this. I want to thank Matt Sears, @bodhi and Ryan LeCompte for taking this one off of my chest.
This technique does give you yet another tool for creating pure Ruby DSL-s.