I was reading one of Steve Yegge's old posts lately: "Choosing languages". This article introduced me to the pattern matching capabilities of a language such as Haskell. Wow,... Yes we can't do that in Ruby. But can't we really?
Indeed, this triggered my memory like a mantra: "if it's not nailed down, steal it... if it's not nailed down, steal it,..." Here's the article: If it's not nailed down, steal it. The author presents a "theft" in Ruby: the implementation of pattern matching.
So using the 'multi' gem, you are able to express the Fibonacci function as:
multi (:fib, 0) { 1 }This may not be very impressive here, but later on in Steve's article, Steve tells us that pattern matching can really be a huge win when dealing with complex structured data. Here's an example from a contest in the Amazon's developer journal.
multi (:fib, 1) { 1 }
multi (:fib, Integer) { |n| fib(n-1) + fib(n-2) }
=============================================================
bad [[_,fs],[_,ds],[_,cs],[_,gs]] = (cs /= fs) && (cs == ds || cs == gs)
Here, the bad function gets a list as its argument. But instead of naming the list, it specifies a pattern that it expects the list to match: a list of 2-item sublists. It ignores the first element of each sublist, and assigns the second element to fs (farmer side), ds (dog side), etc. The variables then become available for use in the body of the function, after the "=" sign, where we check if if the chicken's alone with the dog or the grain.
=============================================================
For sure, writing the same thing in Java, or even in plain Ruby would be a pain. So I rolled up my sleeves and tried to enhance the 'multi' gem with the ability to use Arrays and variables in Arrays. So I allowed to write things such as:multi(:foo, [String, Integer]) {|x, y| x; y}I also added some support for a 'multi' block, which is closer to a Haskell definition (though the 'let' name will be certainly shock a lisper,...):
multi(:foo, [:p1, :p2]) { |symbols| symbols[:p1]; symbols[:p2]}
multi(:foo, [:p1, [Integer, :p3]]) { |symbols, x| symbols[:p1]; symbols[:p3]; x}
multi(:foo, [:p1, [:_, :p2]]) { |symbols| symbols[:p1]; symbols[:p2]}
multi(:foo) doMy conclusion on that little experiment was:
let(String) {|x| x.should eql "hello"}
let(String, String) {|x, y| x.should eql "hello"; y.should eql "world"}
end
- The use of a "symbols" map is a bit awkward, even if it uses the symbols declared in the pattern. It looks like a trick compared to a Haskell definition
- It is possible to reproduce Steve's examples in an easy way (I didn't say efficient!)
- The limitations mentioned in the article regarding the use of 'multi' methods in classes still hold: they are not inherited. 'multi' is not the 'def' keyword
Now, let's say I start seeing it ("Yes, I got it! I am in the Matrix!!"). Should I use it in my ruby code? And should I use the rest? Let's see:
- we can do some pattern matching in ruby
- we can compose functions
- we can have lazy lists (and list comprehension)
- we can use statemachines
- we can do high-order messaging
: [0, 1, 2].where.nonzero? => [1, 2]
Hey, why not? I am truly amazed by the Ruby's capacities to be benefit from ideas from other languages or paradigms. Indeed, I think it still feels quite Ruby-like with functions and blocks, and those features allow me to write more concise code, at the right abstraction level.
However, capturing an animal and putting him in a zoo is not the same as watching him in its environment. Haskell, Lisp, Erlang and others are quite unique and they each provide different ways to blow your mind.
By the way, is it possible to do concurrency programming "a la" Erlang in Ruby? The challenge is opened,...
2 comments:
Some time ago I've showed the concept of slightly another way for pattern-matching. With my "library" you example with let's should be rewritten as
def foo(*arg)
arg.pmatch(String){|x| x.should eql "hello"} ||
arg.pmatch(String, String) {|x, y| x.should eql "hello"; y.should eql "world"} ||
arg.nomatch!
end
which a bit more verbose, but seems to be more "rubyish" (it doesn't hides function definition with "multi" syntax).
Note that the blocks saved by the "multi" method should be "instance_eval"'d against the original object at dispatch time.
Then there would be no problems accessing instance variables, and you could define the methods in the class body.
Post a Comment