Okay, stupid ruby trick time, who can figure out why this works in Rails? (I honestly don’t know myself, can’t quite figure it out).(It will only work in Rails, since it uses the Symbol#to_proc method. You can get an interactive environment with ruby classes loaded by executing ./script/console in any ruby app directory)
>> adder = :+.to_proc => #<Proc:0x00002ad583802090@/usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.4/lib/active_support/core_ext/symbol.rb:11> >> adder.call(2,3) => 5
I understand the pieces. But I don’t understand how they result in a two-argument Proc object there.
Here are the pieces. Rails Symbol#to_proc — I thought perhaps incorrectly — is a shortcut for method(:symbol).to_proc, that is self.method(:symbol).to_proc. Any ruby object has a method named #method which returns a Method object representing the named method in the instance.
And then any Method object has a #to_proc which turns it into a Proc. And then any Proc has a #call method, where you give it’s arguments, and it gives you the result.
So I entirely understand why this works:
two_plusser = 2.method(:+).to_proc two_plusser.call(10) => 12
I get that. 2 is an Fixnum, and any Fixnum has a “+” method (operators are just methods in ruby). So we by get a Method object for 2’s + method, turn it into a Proc, and now we can call it with an argument, no problem.
But when you do:
:+.to_proc => #<Proc:0x00002ad583802090@/usr/lib64/ruby/gems/1.8/gems/activesupport-2.3.4/lib/active_support/core_ext/symbol.rb:11>
Where the heck is that Proc coming from, to come up with a two-argument + method? Apparently ” :symbol.to_proc ” is not entirely a synonym for ” self.method(:symbol).to_proc “, because look at this:
>> self.method(:+).to_proc NameError: undefined method `+' for class `Object' from (irb):23:in `method' from (irb):23 >> self.method(:+) NameError: undefined method `+' for class `Object' from (irb):24:in `method' from (irb):24 >>
So… where the heck is the Proc object returned by ” :+.to_proc ” coming from?
The trick is that the definition of Symbol#to_proc is:
def to_proc
Proc.new { |*args| args.shift.__send__(self, *args) }
end
This does a little shuffle and you end up with the following derivation:
proc.call(2, 3) => 2.__send__(:+, 3)
Thanks Josh, that makes sense!
Sort of… that simple one liner definition of to_proc is something AWFULLY tricky to get one’s head around, it uses some unusual tricks. It’s actually kind of like an automatic “curry and invoke”. Sort of.
I was going to ask if there were any examples of somehow using to_proc with a _three argument_ call… but looking at the implementation, I don’t think this is possible, since it doesn’t use a loop, but just a _single_ shift on *args. Which is probably for the best, it would get even MORE confusing if it did that!
I guess the two-argument-capable version ends up doing what people intuitively feel like it should in some convenient cases, even without having to completely understand what it’s doing. It’s still approaching my own limits of tolerance for ‘magic’. But I may be unusually magic-intolerant. And ‘magic’ is in the eye of the beholder, it’s only “too much ruby magic” when you don’t really understand what the heck it’s doing, which is a different point for everyone.
There’s no reason you can’t make a three-argument call. The `shift` is just to get an object to call the method on. The rest of the arguments get called.
class ButterLettuce
def party(name, elf)
puts “Partying with #{name} and #{elf}”
end
end
lettuce = ButterLettuce.new
party_method = :party.to_proc
party_method.call(lettuce, “Wallow”, “Wankershim”) #=> “Partying with Wallow and Wankershim”