Руби attr_reader и +=

Недавно меня озадачил следующий код:

class Foo
  attr_accessor :n

  def initialize(i)
    @n = i
  end

  def val
    n
  end

  def bump!
    n += 1
  end
end

f = Foo.new(0)

puts f.val
f.bump!

puts f.val завершается успешно и выводит 0, как и ожидалось. f.bump! вызывает следующее NoMethodError

foo.rb:13:in `bump!': undefined method `+' for nil:NilClass (NoMethodError)
        from foo.rb:20:in `<main>'

Любая идея, почему n является nil в выражении n += 1?

Использование вместо этого n = 1 + n вызывает TypeError (nil cannot be coerced into Fixnum), так что n на самом деле равно nil.


person Nickolay Kolev    schedule 21.11.2013    source источник
comment
FWIW, я не думаю, что использование attr_accessor внутри класса имеет смысл, может быть, лучше просто везде использовать @foo? Может быть, он даже не поддерживается.   -  person Smar    schedule 21.11.2013


Ответы (2)


Несмотря на то, что вы определили метод n= для Foo, Ruby не позволит вам вызвать его из внутри класса без явного получателя, т. е. self.n=

Итак, когда вы пишете n += 1, это преобразуется в n = n + 1. n= не имеет явного получателя, поэтому Ruby создает локальную переменную n (то есть nil). Таким образом, n в n + 1 относится к локальной переменной nil, что дает вам NoMethodError.

К вашему сведению, вам не нужен attr_accessor, если только вы не хотите, чтобы n был доступен за пределами класса! Даже тогда, когда вы пишете методы экземпляра, вы должны просто использовать обычную переменную экземпляра @n.

person Max    schedule 21.11.2013
comment
Отличное объяснение! Спасибо! Я бы не стал писать что-то подобное, просто наткнулся на это при рефакторинге класса, который экспортирует методы доступа по уважительной причине. - person Nickolay Kolev; 21.11.2013
comment
Я думаю, что также стоит упомянуть, что причина, по которой Ruby требует получателя, заключается в том, чтобы разрешить подобное создание локальных переменных. В противном случае было бы невозможно создать локальную переменную после определения attr_writer. - person Max; 21.11.2013

Ваша ошибка там:

def bump!
  n += 1
end

Используйте self.n. Или @n.

Когда вы делаете:

attr_accessor :n

На самом деле вы делаете:

def n
  @n
end

def n=(value)
  @n= value
end

И когда вы выполняете n += 1, вы используете локальную переменную (которой является niL) вместо двух методов, созданных attr_accessor.

person Dominic Goulet    schedule 21.11.2013
comment
@n += 1 и self.n += 1 работают. Хотя вопрос был не в этом. @Dominic: ошибается часть читателя, а не задание. - person Nickolay Kolev; 21.11.2013
comment
Да, я только что понял это. Спасибо ;-) - person Dominic Goulet; 21.11.2013
comment
› вы используете локальную переменную (которая равна niL) Это вопрос. :-) Почему n nil внутри #bump!, а не nil внутри #val? - person Nickolay Kolev; 21.11.2013