StormKrow
StormKrow

Reputation: 985

Adding numbers in succession

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 method call 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

Answers (2)

Eric Duminil
Eric Duminil

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

ndnenkov
ndnenkov

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

Related Questions