views:

203

answers:

3

lambda is used in this example in both the compose and the hydrate methods. What does lambda do here?

def compose *lambdas
  if lambdas.empty?
    lambda { nil }
  elsif lambdas.size == 1
    lambdas.first
  else
    lambda do |n|
      lambdas.first.call(compose(*lambdas[1..-1]).call(n))
    end
  end
end

def hydrate(modulus, printable_form)
  i = 0
  lambda do |n|
    (i = (i + 1) % modulus) == 0 && printable_form || n
  end
end

print(((1..100).map
   &compose(
     hydrate(15, 'Watermelon'), 
     hydrate(5, 'Melon'), 
     hydrate(3, 'Water'))).join(' '))

My second question is - what is the need for the ampersand in front of the word compose?

+7  A: 

The function compose takes a list of functions and returns a newly allocated function that is the composition of all the functions on the list. It's been written to treat the empty list rather oddly; it may be that the first case should be ignored. (Normally composing the empty list of functions should produce the identity function, but that's not what your example does. I would have expected

lambda do |n| { n }

as the base case.)

It's necessary to use a lambda in order to create a new function You see that in the recursive case in compose: the lambda creates a new function which when given n returns the result of calling the composition of the remaining functions, then finally applies the first function. This is not good code to emulate, as the recursion over the list of functions is repeated every time the composition is called. In the example:

  • Creating the composition of functions uses constant time and space

  • Applying the composition of functions costs linear time and space

Whereas if the code were written properly

  • Creating the composition of functions should cost linear time and space

  • Applying the composition of functions should cost linear time and should require no allocations (zero space)

Unfortunately I don't know enough Ruby to write for you an example of compose the way it ought to be done. But others will.

Norman Ramsey
+3  A: 

Your second question asks what the & is doing.

Looking at The unary ampersand in Ruby, the ampersand converts a proc to a block.

Example:

irb(main):001:0> def meth1
irb(main):002:1>   yield "Hello"
irb(main):003:1> end
=> nil

Calling meth1 with a block, works as expected:

irb(main):004:0> meth1 { |s| puts s } # Calling meth1 with a block
Hello
=> nil

Calling with a Proc does not work:

irb(main):005:0> p = Proc.new { |s| puts "In proc: #{s}" }
=> #<Proc:0x13e5d60@(irb):5>
irb(main):006:0> meth1 p
ArgumentError: wrong number of arguments (1 for 0)
        from (irb):6
        from C:/Ruby19/bin/irb:12:in `<main>'

But it does work if you convert the Proc to a block:

irb(main):007:0> meth1 &p # Convert the proc to a block
In proc: Hello
=> nil

This is the same thing that is happening when you use the following code:

irb(main):001:0> def meth2(&block) # Block is converted to Proc
irb(main):002:1>   puts block.class.to_s if block_given?
irb(main):003:1> end
=> nil
irb(main):004:0> meth2 { puts "Hi There" }
Proc
=> nil
irb(main):005:0>

Here is another article on the differences between blocks, block vs lambda vs proc.

Matt Haley
+3  A: 

As a side note, I don't know where you got this exact code, but it looks like a very lightly altered version of some code from Reginald Braithwaite's blog. (The code was a deliberately over-the-top solution to the FizzBuzz question used in a serious discussion of functional programming in Ruby and functional programming more generally.)

Here are the original posts:

Telemachus
Well remembered :-)
mikej