Ruby 中的 + 方法到底做了什么?

What does the + method in Ruby do, exactly?

假设我想编写自己的数学运算符,如“+”

简单版本为:

def plus(a,b)
    return a+b
end

但这不是真正的“+”所做的。

我要3.add(4) # =>7 但是我如何告诉 ruby 获取我使用我的方法的对象?

我试过了

def add(c)
    return self+c
end

但我收到错误消息:

:in <main>': private methodadd' 要求 3:Fixnum (NoMethodError)

ruby 中的加号 (+) 可以像任何其他方法一样被覆盖(您可以查找运算符重载):

class MyOperator
  attr_accessor :text
  def initialize(text)
    @text = text
  end
  def +(operand)
     "#{self.text} #{operand.text}"
  end

  def to_s
    self.text
  end
end


a = MyOperator.new "Hello"
b = MyOperator.new "World"


puts (a+b)

所以它并没有什么神奇之处。但是如果重载运算符在您的上下文中有意义,您必须小心。

问题

您定义了方法:

def add(c)
  return self + c
end

并试图这样使用它:

3.add(4) #=> NoMethodError: private method `add' called for 3:Fixnum

了解此错误消息

此错误消息准确地告诉您问题出在哪里。我认为您的问题只是您不了解 Ruby 如何在对象上调用方法。

当Ruby看到3.add(4)时,它首先查看接收器3,并确定:

3.class #=> Fixnum

这告诉它定义方法 add 的位置:在 class Fixnum 中或在 Fixnum 的祖先的 class 中或模块。

所以它在那里寻找它,没有找到它,并发出一条错误消息。我们可以确认它不存在:

Fixnum.instance_methods.include?(:add)
  #=> false

那么add是在哪里定义的呢?

虽然你确实定义了它,但它在哪里?让我们找出答案:

method(:add).owner
  #=> Object 

Object.instance_methods.include?(:add)
  #=> false

Object.instance_methods returns 是在 Object 和 [=35= 上定义的所有 public 实例方法的数组]的祖先。 add 不在其中,因此我们得出结论 addprotectedprivate 方法:

Object.protected_instance_methods.include?(:add)
  #=> false

Object.private_instance_methods.include?(:add)
  #=> true

让我们尝试在 Object:

的实例上调用该方法
Object.new.add(4)
  #=> NoMethodError: 
  #   private method `add' called for #<Object:0x007fdb6a27fa68>

考虑到 Object#add 是私有的,这是有道理的。但是,我们可以使用 Object#send:

调用私有方法
Object.new.send(:add,4)
  #NoMethodError: undefined method `+' for #<Object:0x007fdb6a28e068>

作为练习,请确保您了解 Ruby 采取的导致她引发此异常的步骤(实例方法 + 未在 Object 上定义,或等效, Object 的实例没有方法 +).

对了,你是在哪里定义add的?我的意思是,当您定义 self 时,它的值是多少?让我们看看:

self       #=> main
self.class #=> Object

我们看到 add 必须定义在 class 上,它的接收者是一个实例。 (一口,是的,但这很重要,因此请确保您理解这一点)。

为什么 Object#add 是私有的而不是 public?

考虑:

def greet
  puts 'hi'
end

class A
end

A.private_instance_methods.include?(:add)
  #=> true 
A.new.send(:greet)
  #=> 'hi'

这是因为 AObject 继承了 greet:

A.ancestors.include?(Object) #=> true

如果 Object#greet 是 public,每个内置 class 和您定义的每个 class 都会有一个 public 实例方法 greet.那将导致极大的痛苦。 (假设您有一个方法 great 并且输入错误 greet!)即使是私有 greet 也会造成麻烦。)

add应该定义在哪里?

由于add.class => Fixnum,我们这样定义它:

class Fixnum
  def add(other)
    self + other
  end
end

Fixnum.instance_methods.include?(:add) #=> true
3.add(4)                               #=> 7

如果我在 class Fixnum 之后包含 puts "self#{self}" 行,它会打印 "Fixnum"。使用显示 self 值的 puts 语句对代码进行加盐通常有助于理解正在发生的事情。

最后一件事:

method(:add).owner
  #=> NameError: undefined method `add' for class `Object'

为什么这个没有returnFixnum?由于 method 没有明确的接收者(即没有 xx.method),Ruby 假设接收者是 self,这里是:

self #=> main 

所以她在 self.class => Object 中寻找方法 method,你知道她找到了什么(或者,我应该说,没有找到)。相反,我们需要写:

Fixnum.instance_method(:add).owner #=> Fixnum

3.method(:add).owner #=> Fixnum

这里的3当然可以替换为Fixnum.

的任意实例

注意我已经稍微简化了这个解释。在搜索方法时,Ruby 还会查看接收者的单例 class。这对于直接对象(数字、符号、truefalsenil)不是问题,但是,因为它们没有单例 classes:

3.singleton_class     #=> TypeError: can't define singleton

对比,例如:

[1,2].singleton_class #=> #<Class:#<Array:0x007fbcf18c01a8>>