Shalmanese
Shalmanese

Reputation: 5370

passing functions as arguments in ruby

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?

Upvotes: 5

Views: 1823

Answers (4)

Nathan Shively-Sanders
Nathan Shively-Sanders

Reputation: 18399

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.

Upvotes: 1

krusty.ar
krusty.ar

Reputation: 4060

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

Upvotes: 3

Iraimbilanja
Iraimbilanja

Reputation:

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

Upvotes: 10

Brian Carper
Brian Carper

Reputation: 72926

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.

Upvotes: 0

Related Questions