Ruby中的+方法究竟做了什么?

时间:2015-04-25 14:49:30

标签: ruby math operators addition

想象一下,我想编写自己的数学运算符,如“+”

简单版本将是:

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

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

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

我试过

def add(c)
    return self+c
end

但我收到错误消息:

:在<main>': private method添加'调用3:Fixnum(NoMethodError)

2 个答案:

答案 0 :(得分:1)

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)

因此没有太大的魔力。但是如果操作符的重载在你的上下文中有意义,你必须要小心。

答案 1 :(得分:1)

问题

您定义了方法:

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的位置:在类Fixnum中或在Fixnum的祖先的类或模块之一中。

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

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

那么add定义在哪里?

你确实定义了它,所以它在哪里?我们来看看:

method(:add).owner
  #=> Object 

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

Object.instance_methods返回在ObjectObject的祖先上定义的所有 public 实例方法的数组。 add不在其中,因此我们得出结论add受保护的私有方法:

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的实例{1}}没有方法+)。

顺便问一下,你在哪里定义add?那样,我指的是self定义它时的价值是什么?我们来看看:

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

我们看到必须在其接收者为其实例的类上定义add。 (一口,是的,但这很重要,所以一定要明白这一点)。

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

考虑:

def greet
  puts 'hi'
end

class A
end

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

这是因为Agreet继承Object

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

如果Object#greet是公开的,那么您定义的每个内置类和每个类都将具有公共实例方法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

如果我在puts "self#{self}"之后包含了class Fixnum行,它就会打印出“Fixnum”。使用显示puts值的self语句对您的代码进行格式化通常有助于了解正在发生的事情。

最后一件事:

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

为什么这不会返回Fixnum?由于method没有明确的接收者(即没有xx.method),Ruby认为接收者是self,这是:

self #=> main 

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

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

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

此处3当然可以替换为Fixnum的任何实例。

注意我在某种程度上简化了这个解释。在搜索方法时,Ruby也会查看接收者的单例类。对于直接对象(数字,符号,truefalsenil),这不是问题,因为它们没有单例类:

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

相比之下,例如:

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