Cam
Cam

Reputation: 15234

Why can't a class method have the same name as a non-class method?

I'm learning ruby, and noticed that I cannot create a class method called puts:

class Printer
    def initialize(text="")
        @text = text
    end
    def puts
        puts @text
    end
end

The error is:

`puts': wrong number of arguments (given 1, expected 0)

My expectation was that I could use the code like this:

p = Printer.new("hello")
p.puts

It's not just because puts is a built-in method, though. For instance, this code also gives a syntax error:

def my_puts(text)
    puts text 
end

class Printer
    def initialize(text="")
        @text = text
    end
    def my_puts
        my_puts @name
    end
end

Upvotes: 3

Views: 579

Answers (2)

user2864740
user2864740

Reputation: 61865

tldr; within the scope of the instance, the puts resolves to self.puts (which then resolves to the locally defined method, and not Kernel#puts). This method overriding is a form of shadowing.

Ruby has an 'implicit self' which is the basis for this behavior and is also how the bare puts is resolved - it comes from Kernel, which is mixed into every object.

The Kernel module is included by class Object, so its methods [like Kernel#puts] are available in every Ruby object. These methods are called without a receiver and thus can be called in functional form [such as puts, except when they are overridden].

To call the original same-named method here, the super keyword can be used. However, this doesn't work in the case where X#another_method calls X#puts with arguments when it expects to be calling Kernel#puts. To address that case, see Calling method in parent class from subclass methods in Ruby (either use an alias or instance_method on the appropriate type).

class X
  def puts
    super "hello!"
  end
end

X.new.puts

P.S. The second example should trivially fail, as my_puts clearly does not take any parameters, without any confusion of there being another "puts". Also, it's not a syntax error as it occurs at run-time after any language parsing.

Upvotes: 2

Christian Bruckmayer
Christian Bruckmayer

Reputation: 2187

To add to the previous answer (https://stackoverflow.com/a/62268877/13708583), one way to solve this is to create an alias of the original puts which you use in your new puts method.

class Printer
  alias_method :original_puts, :puts
  attr_reader :text

  def initialize(text="")
    @text = text
  end

  def puts
    original_puts text
  end
end

Printer.new("Hello World").puts

You might be confused from other (static) programming languages in which you can overwrite a method by creating different signatures.

For instance, this will only create one puts method in Ruby (in Java you would have two puts methods (disclaimer: not a Java expert).

def puts(value)
end

def puts
end

If you want to have another method with the same name but accepting different parameters, you need to use optional method parameters like this:

def value(value = "default value")
end

Upvotes: 1

Related Questions