Reputation: 985
I'm trying to learn Ruby and I came across a problem online where I need to create something similar to this :
add(1) # => 1
add(1).(2) # => 3
add(1).(2).(3) # => 6
and so on.
I've read around and the closest I can get to having this work is with the following code :
def add(n)
Proc.new { |s| s + n }
end
Which works to an extent.
I can run the following :
add(1) # => #<Proc:0x007fb8aac309f0@(pry):2>
# or
add(1).(2) # => 3
But the moment I try doing this :
add(1).(2).(3)
I get the following error :
NoMethodError
: undefined methodcall
for 3:Integer
Why am I getting this error and how would I change this method so that it will accept as many new values as I give it to add itself without giving me an error?
Upvotes: 2
Views: 997
Reputation: 54293
For reference, this problem comes from CodeWars "A Chain Adding Function".
The easiest way to achieve this is to define Integer#call
:
class Integer
def call(n)
self + n
end
end
def add(n)
n
end
puts add(1)
# 1
puts add(1).(2)
# 3
puts add(1).(2).(3)
# 6
The returned objects are plain Ruby Integers, not Proxy objects.
Note that it's probably not a good idea to modify such an important class. It's possible to use refinements in order to avoid breaking other Ruby parts:
module ChainAdding
refine Integer do
def call(n)
self + n
end
end
end
def add(n)
n
end
using ChainAdding
puts add(1)
# 1
puts add(1).(2)
# 3
puts add(1).(2).(3)
# 6
If you're learning Ruby, this kind of metaprogramming trick isn't what you should concentrate on first. It's cool to know that we can do this, it doesn't mean that we should, though.
Upvotes: 3
Reputation: 36110
You can't quite do what you are trying to do. The reason is that you have to return something and that something can't change based on whether you are going to continue the chain or not.
Note that some Rails objects give you the illusion that you can in the REPL (irb, pry), but they actually just redefine #inspect
, which is called upon visualisation by said REPL.
Instead you can do something very similar, which has an even better interface IMO:
def add(starting_number)
sum = starting_number
accumulator = proc do |next_number|
if next_number
sum += next_number
accumulator
else
sum
end
end
end
add(1) # => #<Proc:0x007fb8ae20e860@(pry):4>
add(1).(2) # => #<Proc:0x007fb8ae4894f0@(pry):4>
add(1).() # => 1
add(1).(2).() # => 3
add(1).(2).(3).(4).() # => 10
Upvotes: 2