views:

820

answers:

4

I'm trying to wrap my head around functional programming in ruby and there doesn't seem to be much good documentation out there.

Essentially, I'm trying to write a combine function that would have a Haskell type signature of:

[a] -> [a] -> (a -> a -> a) -> [a]

So

combine([1,2,3], [2,3,4], plus_func) => [3,5,7]
combine([1,2,3], [2,3,4], multiply_func) => [2,6,12]

etc.

I found some stuff about using zip and map but that feels really ugly to use.

What would be the most "ruby" way of implementing something like this?

+3  A: 

A very naive aproach:

def combine(a1, a2)
  i = 0
  result = []
  while a1[i] && a2[i]
    result << yield(a1[i], a2[i])
    i+=1
  end
  result
end

sum = combine([1,2,3], [2,3,4]) {|x,y| x+y}
prod = combine([1,2,3], [2,3,4]) {|x,y| x*y}

p sum, prod

=>
[3, 5, 7]
[2, 6, 12]

And with arbitrary parameters:

def combine(*args)
  i = 0
  result = []
  while args.all?{|a| a[i]}
    result << yield(*(args.map{|a| a[i]}))
    i+=1
  end
  result
end

EDIT: I upvoted the zip/map solution, but here's a little improvement, what's ugly about it?

def combine(*args)
  args.first.zip(*args[1..-1]).map {|a| yield a}
end

sum = combine([1,2,3], [2,3,4], [3,4,5]) {|ary| ary.inject{|t,v| t+=v}}
prod = combine([1,2,3], [2,3,4], [3,4,5]) {|ary| ary.inject(1){|t,v| t*=v}}
p sum, prod
krusty.ar
+10  A: 

Well, you said you know about zip and map so this probably isn't helpful. But I'll post just in case.

def combine a, b
 a.zip(b).map { |i| yield i[0], i[1] }
end

puts combine([1,2,3], [2,3,4]) { |i, j| i+j }

No, I don't find it beautiful either.

edit - #ruby-lang @ irc.freenode.net suggests this:

def combine(a, b, &block)
    a.zip(b).map(&block)
end

or this, if you want to forward args:

def combine(a, b, *args, &block)
    a.zip(b, *args).map(&block)
end
A: 

You can pass the name of the method as a symbol and use Object#send (or Object#__send__) to call it by name. (Ruby doesn't really have functions, it has methods.)

You can pass a lambda or block which calls your desired method on your desired arguments. Passing blocks is probably the preferred Ruby way, when it works (i.e. when you only have a single block to pass).

You retrieve Method objects directly via Object#method and then pass them around and call them, but I have little experience doing it this way and haven't seen it done much in practice.

Brian Carper
A: 

You sound like you might also want Symbol.to_proc (code by Raganwald)

class Symbol
  # Turns the symbol into a simple proc, which is especially useful for enumerations. 
  def to_proc
    Proc.new { |*args| args.shift.__send__(self, *args) }
  end
end

Now you can do:

(1..100).inject(&:+)

Disclaimer: I am not a Rubyist. I just like functional programming. So this is likely to be un-Ruby-like.

Nathan Sanders
Your code is correct, but it just makes this part: {|x, y| x+y} faster to write (and arguably more readable). But you are still missing the combination and collection parts.
krusty.ar