Saturday, April 29, 2006
Twisting and Shaping DSLs using Ruby Metaprogramming
[..] while reading your article I was thinking about using the recipe name string as an object to route the messages coming from the ingredients and then catching them by implementing missing_method in the string object...
So, for:
recipe "italian breakfast" do
caffe = 1, :macchiato
end
is there a way to translate it into:
"italian breakfast".caffe(1, :macchiato) ?
[..]
Let's see:
"italian breakfast".caffe 1, :macchiato
First of all, I removed the brackets because I don't believe in brackets :-)
What Federico proposes can be done, but I am not sure I would do it this way, unless I am working only within the context of a DSL. If you want your recipe-string to accept the caffe method, then you have to override the method_missing method of the String class, which can be a dangerous thing to do if you have loaded other meta-code that overrides this method on a String or Object level.
I would rather go for a Concept Wrap Up:
Recipe("italian breakfast").caffe 1, :macchiato
where I make explicit the meaning of 'italian breakfast' by wrapping it into a Recipe object. Note that I did not use Recipe. new. I am launching a campaign against the use of new in application code, which I consider at the same level of badly used hungarian notation (because there is such a thing as good hungarian notation).
Another way I would do it is by using what I call a Conjunction:
"italian breakfast".consists_of.caffe 1, :macchiato
You will have to inject a consists_of method in the String class, which will act as a conjunction between the String and the method call. The conjunction will take the String and wrap it up in a Recipe object. The cafe method is then invoked on the Recipe object.
In both cases we have moved the handling of the method_missing from String to a custom made Recipe class.
The conjunction will be:
class String
#Recipe conjuction
def consists_of
Recipe .new self
end
end
The Recipe will start as:
class Recipe
def initialize recipe_name
@name = recipe_name
@ingredients = {}
register_with_global_repository
end
def method_missing method, *args
@ingredients[method .to_s] = args
end
...
The method_missing is catching the ingredients and it stacks them nicely on an hashtable. Why do we need the register_with_global_repository call, though?
Since we are working with a DSL and we are not explicitly storing a reference to a created recipes in a variable, then we get the Recipe to self-register itself with a global centralized repository. I could have made it a @@all_recipes class attribute, but I think that in this simple example it looks clearer as an external separate entity:
$RECIPES_REPOSITORY = {}
and finally the code to print out the recipe:class Recipe
…
private
def register_with_global_repository
$RECIPES_REPOSITORY[ @name] = self
end
end
class Recipe
…
def report
puts "to make #{@name} you should buy:"
@ingredients.each_pair do
|ingredient,description|
puts " * #{Array(description).join ' '} #{ingredient}"
end
end
…
end
What is still missing is a method to print out the recipe directly from the DSL. We would like our call to look like this:
recipe? "italian breakfast"
This snippet fully satisfies our language choice:
Our new DSL now allows us to write:def recipe? recipe_name
$RECIPES_REPOSITORY[recipe_name].report
end
and to obtain:"italian breakfast".consists_of.caffe 1, :macchiato
recipe? "italian breakfast"
to make italian breakfast you should buy:
* 1 macchiato caffe
The one big limitation is that at the moment you can define recipes with only one ingredient.
We get around this limitation by making the method_missing a Follow Up Method Chain:
def allow_method_chaining() self end
The trick here is that after having defined an ingredients we still have the original Recipe object ready to accept new ingredients.def method_missing method, *args
@ingredients [method.to_s] = args
allow_method_chaining
end
You can now do:
"italian breakfast".consists_of.
caffe(1 , :macchiato ).
cornetto(1, :cioccolato)
recipe? "italian breakfast"
and get:
to make italian breakfast you should buy:
* 1 cioccolato cornetto
* 1 macchiato caffe
That was long! I think I will now go for a 1 cioccolato cornetto!
Friday, April 28, 2006
The Way of Meta - Part IV - Hijacking Local Variables in DSLs
The Way of Meta
~
V. 0.0.4
In the article there was a point that was left somewhat open: how to allow developers using a DSL to assign values to new concepts that they define on the spot, and then get access to both these concepts and their values from within the DSL.
I realize I have been a bit vague here, so I'll give you a straightforward example. Let's say we want to define a DSL to describe the shopping list with all the ingredients needed to make a certain recipe..
Ideally we would like to write something like:
shopping_list_for "BLT sandwiches for the picnic" do
sliced_bread = 3
lettuce = 2
tomatoes = 6, :red
bacon = 4
end
The do..end block delimits a block context that gets passed directly to the shopping_list_for method.
This method should then extract the various ingredients and their quantity and description.
How can it do that? One way is to run the block within a context where those ingredients already exist as methods. This context would be a class with -for example- the sliced_bread= method overridden to accept the DSL input.
What about new ingredients that have not been specified a priori ? Your metaprogramming instincts will tell you to go for a method_missing based technique. Wrong. Unless you specify a self (or other object) in front of sliced_bread, ruby will simply assume that it is a local variable rather than a missing method. Highly annoying if you ask me, but it is necessary to avoid even worst problems and ambiguities when dealing with stuff like mixins.
You are left with these three choices:
shopping_list_for "BLT sandwiches for the picnic" do
self.sliced_bread = 3
self.lettuce = 2
self.tomatoes = 6, :red
self.bacon = 4
end
Argh!
shopping_list_for "BLT sandwiches for the picnic" do |recipe|
recipe.sliced_bread = 3
recipe. lettuce = 2
recipe. tomatoes = 6, :red
recipe. bacon = 4
end
Just a tad annoying
shopping_list_for "BLT sandwiches for the picnic" do
@sliced_bread = 3
@lettuce = 2
@tomatoes = 6, :red
@bacon = 4
end
The best so far.. you can visualize the @ as bullet points ant it is kind of ok..
Kind of.
Jim, at this point, proposes to drop the equal sign to force ruby to recognize the new keyword as a method that gets routed through method_missing.
shopping_list_for "BLT sandwiches for the picnic" do
sliced_bread 3
lettuce 2
tomatoes 6, :red
bacon 4
end
I'll admit it, this is not bad at all. Yet it annoys me to no end that I cannot just get those variables that I need from another context.
Thinking about there is a way to get variables, and that is the local_variables method, but you have to execute it within a specific context. There is also a method to pass around contexts, and that is binding. What we have to do is to get the binding of the block and to get the local variables within the context underlying the binding.
I tried to call the block and then get the binding of the block, but it didn't work. The block gets executed and the variables fall out of scope and become unavailable.
I had to get the block itself to return the binding. This can be done and it works, but the binding looks totally out of place..
shopping_list_for "BLT sandwiches for the picnic" do
sliced_bread = 3
lettuce = 2
tomatoes = 6, :red
bacon = 4
binding
end
Here it hit me that I could alias the word binding to something that would look good with end. What about the end ? I know it's tacky, but in its own tacky way it works and sounds credible:
shopping_list_for "BLT sandwiches for the picnic" do
sliced_bread = 3
lettuce = 2
tomatoes = 6, :red
bacon = 4
the end
This is the code that allows the DSL to work and to print out the shopping list:
def report recipe, ingredients
puts "to make #{recipe} you should buy:"
ingredients.each_pair do
|ingredient,description|
puts " * #{Array(description).join ' '} #{ingredient}"
end
puts
end
def shopping_list recipe
shopping_binding = yield
ingredients = {}
eval("local_variables" , shopping_binding).
each do |var|
ingredients[var] = eval "#{var}" , shopping_binding
end
report recipe, ingredients
end
alias the binding
shopping_list "english breakfast" do
tomatoes = 2, :green
sausages = 3
eggs = 2, :big
bacon = 4
the end
shopping_list "banana milkshake" do
milk = 1
bananas = 2
the end
When you run it you get:
to make english breakfast you should buy:
* 2 green tomatoes
* 3 sausages
* 4 bacon
* 2 big eggs
to make banana milkshake you should buy:
* 2 bananas
* 1 milk
Wednesday, April 26, 2006
Pandora Music Genome
It models what you like in music and makes very very sensible suggestions.
For example I like mellow rock with female vocal-centric aesthetics in minor key tonalities! Now I know what to answer when I am asked what music I like!
This Pandora thing is scary scary..
Ruby for Javists Presentation
Do you have any advice on how to present it and what to show them?
I am not going to do the Ruby is Better than Java number, since that would feel offensive, but I would like to make them want to have a taste of ruby... I would like them to leave with a desire to explore new things.
Any advice is well appreciated!
Friday, April 14, 2006
Software Superheroes
It's fun to be a superhero in the software world
(image by Chip Kidd)
Thursday, April 13, 2006
Lisp, the 'Why' way
Wednesday, April 12, 2006
CodeSushi: Covering the Italian Scene
Using CodeSushi as a platform and meeting point I am going to push directly the italian software development scene and see what comes up from there.
The Liquid Development blog will continue as usual.
And remember.. Code Sushi, not Spaghetti Code ;-)
Tuesday, April 11, 2006
Software Management as Development of a Business Abstraction
It is too often the case that developers -and employees in general- have to pay with extra working hours for management failures..
Monday, April 10, 2006
Concrete Patterns Example
Considering how hard it can be to explain some of the esoteric development stuff, this can be an inspiration for us all.
Saturday, April 08, 2006
The Book is Out (?!)
What can I say?
I think that Kumo has got far too much time on his hands!
Thanks Kumo, much appreciated :-)
Let's say it's a late April Fool..
Friday, April 07, 2006
Upcoming Development Events in Italy
Just a few links that make me proud:
Style and Stanzas
Jean-Charles Carelli recently asked on the rubytalk mailing list how to improve on the following style from the pickaxe book:
# 1 Book example
songs.append(Song.new(title, name, mins.to_i * 60 + secs.to_i))
# 2 Alternate version.
duration = mins.to_i * 60 + secs.to_i
songs.append(Song.new(title, name, duration))
I like a multi-line nested format, that I call Stanza - you know, like in poetry.
It looks like the following:
songs.append Song.new(
title,
name,
duration = ( mins.to_i * 60 + secs.to_i )
)
It clearly shows you what you are appending as a chunk of code on the right. the 'duration =' idiom makes the intent explicit. I use brackets to nest conceptual entities (Song, duration), so that they stand out as visually striking, which is useful if you are doing some Gestalt Code Reading.
Sometimes, using metaprogramming I also get rid of the explicit new by wrapping up object instantiation with a Song(..) method on the top level. I use this Implicit Constructor because I don't want to be concerned with object creation in my code, just with the meaning of the entities that I am manipulating.
songs.append Song(
title,
name,
duration = ( mins.to_i * 60 + secs.to_i )
)
Wednesday, April 05, 2006
Perceptual Stances: Start Small
Presentation Formats are one way to do this, forcing you to express some content within an artificially imposed presentation frame. The unusual 'shape' of the container can force you to reshape the content and discover some previously hidden properties.
Perceptual Stances (Thinking Formats?) do the same thing by forcing us to focus only on certain aspects of our subject matter. Business Pundit wrote an interesting post that points out how you can unleash your creativity and get started by focusing on small thing rather than on grand theories and stereotypical thinking.
It strikes me that some of the examples he uses seem almost related to techniques I found in Drawing On The Right Side of Your Brain: Drawing Negative Spaces and Semantic-Neutral Drawing. These are not the names the author would have used, but if you are interested I can expand on them.
Presentation Formats: Lightning Talks
Have a look at the Lightning Talk format:
* Giving Lightning Talks
* Lightning Talks
Tuesday, April 04, 2006
Gestalt Code Reading
If you have been writing code for some time I am sure that from time to time you also experience the weird psycognostic power of gestalt-reading code. When I say gestalt-reading I mean that feeling of absorbing the main points and relationships of code by simply giving it a quick glance, just flicking through several pages of code.
You don't know exactly what you have read, but you somehow have an overall impression of what the code does and how the different abstractions are related.
What do you think this is due to?
I believe it is a mix of code indentation, code conventions, general domain culture and the ability of visualizing code on the fly via iconic images. Next time you use your superpowers pay attention to what is happening within your perception process. If your perceptual habits are even a little bit similar to mine, you will probably notice that when you read code you see little boxes and arrows, popping into existence on the backstage of your counscius awareness, connecting together according to well-known patterns.
The knowledge of this process may turn out to be quite important when deciding what code should look like. Proper alignment of braces and indentations.. the feeling of un-closure that I sometimes feel when looking at python code (it feels more like a flow than like legos). My mind looks for visual patterns and simplifies them into icons and boxes, probably also relying on subtler cues and unconscious processes to build meaning.
It's interesting to see what information we can get from this kind of code reading. I can get the main conceptual entities because I can visually spot classes and attributes. I can get the broader relationships too, probably because of a good rational choice of class names, sometimes due to the naming of attributes, occasionally by seeing how some object instances are built and attached to each other. I rarely get the algorithms at a first glance, although I seem to remember that I used to be able to, before doing OOP.. but I am not sure if that really was the case, in the same way as I can do it now with objects. One thing is for sure: much of the metaprogramming cleverness is lost.
However, if the metaprogramming could be kept simple and regular and visually striking, then it could be absorbed much more easily. Think about ActiveRecord. Maybe it doesn't matter so much how we say something, as long as we can make it visually recognisable and compelling, so that it promotes easy visual parsing.
I was almost giving all sorts of non-visual code reading now, and I was forgetting test code. Test code is a conspicuous exception to what I have just said about visual parsing. If a project has tests I prefer to read the tests rather than the project code. Tests are better at capturing the intent of the code and the way to interact with it. They also clearly capture the dynamics of instances, providing not only a compositional/structural view of code, but also the modes of interaction with the code. However tests must be read and cannot be simply absobed using gestalt-reading. Maybe that's because they tend to specify behaviour rather than structure...
Monday, April 03, 2006
Minimal Descriptions and Metaprogramming
While reading his code, that describes a Struct-like class (more an holder of attributes than an holder of behaviours) I thought back to a discussion that we had at the Ruby Social Club meeting about metaprogramming tricks. My point was that through metaprogramming you can redefine the language in such a way that you can also provide stereotypical descriptions for classes and methods, so that you don't have to explicitly describe initializer, accessors, and so on.
For example, this code written by Pilu
could easily become something like:class Artist
attr_reader :name
def initialize(name)
@name = name
end
end
Artist = Struct.new :name
This second form is no doubt more compact, but is it better?
Forget the fact that the full class definition already provides the 'space' to add in new methods and attributes. Assume that the code is done and you are simply reading it. What is better? and why?
Something tells me that I would like the more terse and compact form, but somehow I still find that the first form has got something that the compact form lacks. I just can't put my finger on it.
Following on from Pilu's example I would like to have a compact formalism that allows me to say something like:
Artist defined by name
Guitarist is an Artist also defined by guitar
Singer is an Artist also defined by microphone
Using metaprogramming is not too hard to generate this kind of Minimal Descriptions, although they would probably look like this:
Artist = defined_by :name
Guitarist = Artist.also_defined_by :guitar
Singer = Artist.also_defined_by :microphone
I will leave the metaprogramming as an exercise for the reader :-D
The 'defined_by' is just wrapping a call to ' Struct.new', probably also adding a few extras such as a comparison operator and a prettier to_s method. The 'also_defined_by' is a method that has been injected into the Class class. It creates a new anonymous subclass, adding a few extra accessors and also overriding the initializer by adding some extra parameters.
We have seen how this is technically feasible, I just wonder if this is what we want.
The standard way of doing things shows us explicitly that we are dealing with a class. The 'class' and the 'end' words and the indentation highlight clearly a chunk of code and identifies it as being a class, an important concept within the narrative of our system.
The Minimal Description approach tries to hide away the whole concept of class. It simply cares about concepts and meaning and it abstracts away a big body of knowledge. The body of knowledge that is abstracted away is that we are defining classes and that these classes are related by inheritance. It is also assuming that we know that if something is defined by a certain characteristic, then this characteristic will have to be used when creating an instance of that concept.
What do you think? Is this a rightful use of metaprogramming and of Shaping code After Language, or is it a blatant abuse of metaprogramming power?