Reputation: 5370
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
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
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
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
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