The 2 main things of interest tackling chapter 4 in ruby are the use of recursion and the impedance mismatch handling functional arguments.
First, writing recursive methods is less common in ruby than scheme or haskell [citation needed]. I think there are a couple of reasons for that.First is the rich use of blocks in ruby – lots of recursive procedures can be replaced with blocks using an Enumerable object, and there are plenty of those in ruby. Compare the range_product method:
rangeProduct :: Int -> Int -> Int
rangeProduct x y
| x > y = error "rangeProduct start is greater than end"
| x == y = y
| otherwise = x * rangeProduct (x+1) y
def range_product(x, y)
raise "range_product start is greater than end value" if x > y
(x..y).inject(1) {|prod, i| prod * i }
end
Creating an Enumerable range object and using inject – ruby’s fold equivalent is much more idiomatic than writing a recursive method.
Secondly there is no tail call optimisation in ruby – at least not in 1.8.6. (I think the 1.9 vm may introduce tail call optimisation). This means every recursive call builds up the call stack – which is fine on small levels of recursion but scheme guarantees tail call optimisation and the haskell compiler internally uses such optimisation where it can identify the need – I seem to remember reading that if the last line of code is a recursive call it will be optimised.
The use of functional arguments is pretty painful and inconsistent in ruby compared to both scheme and haskell. For example if I have an id method, I can’t just write 1 + sum_fum(id, n) in the way that is possible in scheme and haskell – which feels natural and intuitive. As far as I can tell there are 3 options:
- Wrap the method invocation in a new Proc object –
Proc.new {|x| id(x) } - Wrap the method invocation using a lambda generated Proc ojbject
lambda {|x| id(x) } - Pass a block into the method invocation and have the method convert the block into a Proc object
The problem with these is they all behave differently Proc and lambda have different semantic behaviour when the invoked method uses a return statement. Using a block means the method being invoked has to generate a Proc object from the block passed in – admittedly that’s easy to do but the problem is having to remember all the subtle differences rather than just being able to use a single consistent mechanism for dealing with functional arguments. Here ruby is definitely lacking, obtuse and obscure.