5

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?

4 Answers 4

10

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
Sign up to request clarification or add additional context in comments.

Comments

3

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

Comments

1

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.

1 Comment

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.
0

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.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.