обезьяна исправляет оператор * - PullRequest
0 голосов
/ 26 апреля 2020

я новичок в ruby и пытаюсь разобраться с исправлениями обезьян.

поэтому во время практики над старым проектом, который представляет собой простой калькулятор Vektor.

Я написал следующий код, который позволит мне умножить Vektor на число и два вектора друг на друга:

  class Vektor 
    attr_accessor :x #instead of getter and setter!-

    def initialize (*vektors)
      if vektors.length==0
        @x=Array.new(3,0)
      else
        @x=vektors
      end
    end

    def size
      @x.length
    end


    def *(other)
      case other
      when Vektor
        if (self.size != other.size)
          return "the vektors don't have the same length!"
        else
          result=0
          for i in 0 ... other.size
            result += (self.x[i] * other.x[i])
          end
          return result
        end
      when Numeric
        for i in 0 ... self.size
          self.x[i]*=other
        end
        return self
      end
    end
  end

и вот целочисленный класс, поэтому я мог бы выполнить умножение другим способом для каждого примера, подобного этому -> 5*vektor

  class Integer
    def mul(other)
      for i in 0 ... other.size
        other.x[i]*=self # this is the cause of the error!
      end
      return other
    end
    alias_method :* , :mul
  end

  obj1=Vektor.new(2,2,2)
  puts 3*obj1

Моя проблема в том, что я получаю ошибку, и я не знаю почему, но я могу предположить, что это потому, что я использую * в alias_method, в то время как он используется внутри метода mul.

вывод следующий:

undefined method `x' for 3:Integer (NoMethodError)

Отредактировано:

текст назначения:

Расширьте класс Integer патчем обезьяны, чтобы вы могли напишите 5 * vector вместо vector * 5.

Проверьте, работает ли этот патч с помощью подходящего модульного теста.

Совет: вам понадобится ключевое слово alias или метод alias_method для достижения цель.

Я полностью понимаю Он является причиной проблемы, но я не могу найти обходной путь, где я мог бы предотвратить возникновение ошибки!.

Это то, что я в настоящее время, который работает, но неправильно :(

class Integer
  def mul(other)
    case (other)
    when Numeric
      return self*other
    when Vektor
      for i in 0 ... other.size
        other.x[i]*=self # this is the cause of the error!
      end
      return other
    end
  end
  alias_method :**,:mul
end

obj1=Vektor.new(2,2,2)

puts obj1*3
puts 3**Vektor.new(3,3,3)

пожалуйста, обратите внимание, что все, что меня волнует на данный момент, это знать, чтобы расширить свои знания, а не получить одну глупую записку. :)

1 Ответ

0 голосов
/ 26 апреля 2020
other.x[i]*=self

- это то же самое, что и

other.x[i] = other.x[i] * self

или, чтобы сделать его действительно явным

other.x().[]=(i)(other.x().[](i).*(self))

other.x[i] является Integer, и self также является Integer (в данном случае 3). В вашем Integer методе вы вызываете other.x, но когда вы умножаете some_integer * 3, тогда в вашем Integer#* методе other равен 3, поэтому вы вызываете 3.x, которого на самом деле не существует .

Обратите внимание, что ваш метод Vektor#* теперь также не работает, потому что ему тоже нужно умножить два Integer s, но вы просто переписали метод Integer#*, который знает, как умножить два * 1027. * s (и на самом деле также знает, как умножить Integer на любой объект , который правильно реализует протокол Numeric#coerce) на тот, который знает только, как умножить Integer и Vektor, и, следовательно, больше не знает, как умножить два Integer с.

Другая проблема заключается в том, что в вашем методе Vektor#* вы проверяете, является ли other экземпляром Numeric , но тогда наоборот вы можете реализовать умножение только для Integer с. Это делает вашу асимметрию умножения c, например, some_vektor * 1.0 будет работать, но 1.0 * some_vektor не будет.

Правильная реализация будет не касаться Integer вообще , и просто реализуйте протокол принуждения Numberri c. Это решит все ваши проблемы: вам не нужно ничего исправлять что-нибудь , и ваши Vektor будут автоматически работать с any Numeric в основной библиотеке, стандартная библиотека, экосистема Ruby и даже те, которые еще не были написаны. Примерно так:

class Vektor
  def self.inherited(*)
    raise TypeError, "#{self} is immutable and cannot be inherited from."
  end

  def initialize(*vektors)
    self.x = if vektors.size.zero?
      Array.new(3, 0)
    else
      vektors
    end.freeze
  end

  singleton_class.alias_method :[], :new

  alias_method :length, def size
    x.size
  end

  def coerce(other)
    [self, other]
  end

  def *(other)
    case other
    when Vektor
      raise ArgumentError, "the vektors don't have the same length!" if size != other.size
      x.zip(other.x).map {|a, b| a * b }.sum
    when Numeric
      self.class.new(*x.map(&other.method(:*)))
    else
      a, b = other.coerce(self)
      a * b
    end
  end

  protected

  attr_reader :x # `x` should not be writeable by anybody!

  private

  attr_writer :x

  freeze
end

Вы заметите, что я внес некоторые изменения в ваш код:

  • Vektor s теперь неизменны и Vektor#* возвращает новый Vektor вместо того, чтобы мутировать self. Как правило, хорошей идеей является сохранение неизменяемости ваших объектов, насколько это возможно, но это особенно важно (и ожидается, действительно) для "числовых" объектов, таких как Vektor s. Вы были бы ужасно удивлены, если бы 2 * 3 не вернул 6, а сделал бы 2 значение 6, не так ли? Но это именно то, что ваш код делал с Vektor s!
  • x, больше не доступен всем, и, самое главное, больше не доступный для записи всем.
  • Я заменил все ваши циклы итерационными конструкциями более высокого уровня. Как правило, если вы пишете oop в Ruby, вы делаете что-то не так. В Enumerable есть так много мощных методов, что вам никогда не понадобится al oop.

Я также написал несколько тестов, чтобы продемонстрировать, что даже не касаясь любого класс за пределами Vektor, теперь мы поддерживаем умножение int * vek, vek * int, float * vek, vek * float, rational * vek, vek * rational, complex * vek и vek * complex, просто на реализация Numeric#coerce протокола принуждения.

require 'test/unit'
class VektorTest < Test::Unit::TestCase
  def test_that_creating_an_empty_vektor_actually_creates_a_zero_vektor_of_dimension_3
    v = Vektor.new
    assert_equal 3, v.size
  end

  def test_that_square_brackets_is_an_alias_for_new
    v = Vektor[]
    assert_equal 3, v.size
  end


  def test_that_we_can_multiply_two_trivial_vektors
    v1 = Vektor[2]
    v2 = Vektor[3]
    assert_equal 6, v1 * v2
  end

  def test_that_we_can_multiply_two_nontrivial_vektors
    v1 = Vektor[2, 3, 4]
    v2 = Vektor[5, 6, 7]
    assert_equal 56, v1 * v2
  end


  def test_that_we_can_multiply_a_trivial_vektor_with_an_integer
    v = Vektor[2]
    assert_equal Vektor[6], v * 3 # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_a_trivial_vektor_with_an_integer_at_least_does_not_raise_an_exception
    v = Vektor[2]
    assert_nothing_raised { v * 3 }
  end

  def test_that_we_can_multiply_a_nontrivial_vektor_with_an_integer
    v = Vektor[2, 3, 4]
    assert_equal Vektor[6, 9, 12], v * 3 # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_a_nontrivial_vektor_with_an_integer_at_least_does_not_raise_an_exception
    v = Vektor[2, 3, 4]
    assert_nothing_raised { v * 3 }
  end

  def test_that_we_can_multiply_an_integer_with_a_trivial_vektor
    v = Vektor[2]
    assert_equal Vektor[6], 3 * v # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_an_integer_with_a_trivial_vektor_at_least_does_not_raise_an_exception
    v = Vektor[2]
    assert_nothing_raised { 3 * v }
  end

  def test_that_we_can_multiply_an_integer_with_a_nontrivial_vektor
    v = Vektor[2, 3, 4]
    assert_equal Vektor[6, 9, 12], 3 * v # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_an_integer_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
    v = Vektor[2, 3, 4]
    assert_nothing_raised { 3 * v }
  end


  def test_that_we_can_multiply_a_trivial_vektor_with_a_float
    v = Vektor[2]
    assert_equal Vektor[6.0], v * 3.0 # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_a_trivial_vektor_with_a_float_at_least_does_not_raise_an_exception
    v = Vektor[2]
    assert_nothing_raised { v * 3.0 }
  end

  def test_that_we_can_multiply_a_nontrivial_vektor_with_a_float
    v = Vektor[2, 3, 4]
    assert_equal Vektor[6.0, 9.0, 12.0], v * 3.0 # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_a_nontrivial_vektor_with_an_float_at_least_does_not_raise_an_exception
    v = Vektor[2, 3, 4]
    assert_nothing_raised { v * 3.0 }
  end

  def test_that_we_can_multiply_a_float_with_a_trivial_vektor
    v = Vektor[2]
    assert_equal Vektor[6.0], 3.0 * v # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_a_float_with_a_trivial_vektor_at_least_does_not_raise_an_exception
    v = Vektor[2]
    assert_nothing_raised { 3.0 * v }
  end

  def test_that_we_can_multiply_a_float_with_a_nontrivial_vektor
    v = Vektor[2, 3, 4]
    assert_equal Vektor[6.0, 9.0, 12.0], 3.0 * v # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_a_float_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
    v = Vektor[2, 3, 4]
    assert_nothing_raised { 3.0 * v }
  end


  def test_that_we_can_multiply_a_trivial_vektor_with_a_rational
    v = Vektor[2]
    assert_equal Vektor[6r], v * 3r # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_a_trivial_vektor_with_a_rational_at_least_does_not_raise_an_exception
    v = Vektor[2]
    assert_nothing_raised { v * 3r }
  end

  def test_that_we_can_multiply_a_nontrivial_vektor_with_a_rational
    v = Vektor[2, 3, 4]
    assert_equal Vektor[6r, 9r, 12r], v * 3r # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_a_nontrivial_vektor_with_an_rational_at_least_does_not_raise_an_exception
    v = Vektor[2, 3, 4]
    assert_nothing_raised { v * 3r }
  end

  def test_that_we_can_multiply_a_rational_with_a_trivial_vektor
    v = Vektor[2]
    assert_equal Vektor[6r], 3r * v # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_a_rational_with_a_trivial_vektor_at_least_does_not_raise_an_exception
    v = Vektor[2]
    assert_nothing_raised { 3r * v }
  end

  def test_that_we_can_multiply_a_rational_with_a_nontrivial_vektor
    v = Vektor[2, 3, 4]
    assert_equal Vektor[6r, 9r, 12r], 3r * v # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_a_rational_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
    v = Vektor[2, 3, 4]
    assert_nothing_raised { 3r * v }
  end


  def test_that_we_can_multiply_a_trivial_vektor_with_a_complex_number
    v = Vektor[2]
    assert_equal Vektor[6i], v * 3i # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_a_trivial_vektor_with_a_complex_number_at_least_does_not_raise_an_exception
    v = Vektor[2]
    assert_nothing_raised { v * 3i }
  end

  def test_that_we_can_multiply_a_nontrivial_vektor_with_a_complex_number
    v = Vektor[2, 3, 4]
    assert_equal Vektor[6i, 9i, 12i], v * 3i # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_a_nontrivial_vektor_with_an_complex_number_at_least_does_not_raise_an_exception
    v = Vektor[2, 3, 4]
    assert_nothing_raised { v * 3i }
  end

  def test_that_we_can_multiply_a_complex_number_with_a_trivial_vektor
    v = Vektor[2]
    assert_equal Vektor[6i], 3i * v # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_a_complex_number_with_a_trivial_vektor_at_least_does_not_raise_an_exception
    v = Vektor[2]
    assert_nothing_raised { 3i * v }
  end

  def test_that_we_can_multiply_a_complex_number_with_a_nontrivial_vektor
    v = Vektor[2, 3, 4]
    assert_equal Vektor[6i, 9i, 12i], 3i * v # this will fail because you haven't implemented equality!
  end

  def test_that_multiplying_a_complex_number_with_a_nontrivial_vektor_at_least_does_not_raise_an_exception
    v = Vektor[2, 3, 4]
    assert_nothing_raised { 3i * v }
  end
end

Давайте также добавим некоторые методы, которые вам обычно нужно всегда реализовывать, чтобы ваши объекты работали с остальной частью экосистемы Ruby:

class Vektor
  def ==(other)
    x == other.x
  end

  def eql?(other)
    other.is_a?(Vektor) && self == other
  end

  def hash
    x.hash
  end

  def to_s
    "(#{x.join(', ')})"
  end

  def inspect
    "Vektor#{x.inspect}"
  end
end

Если вы абсолютно должны использовать обезьянь-патчирование, тогда важно, чтобы вы сохранили доступ к старой версии метода, который вы используете для обезьян-патчирования. Инструмент для этого - метод Module#prepend. Это выглядело бы примерно так:

class Vektor
  def self.inherited(*)
    raise TypeError, "#{self} is immutable and cannot be inherited from."
  end

  attr_reader :x # `x` should not be writeable by anybody!

  def initialize(*vektors)
    self.x = if vektors.size.zero?
      Array.new(3, 0)
    else
      vektors
    end.freeze
  end

  singleton_class.alias_method :[], :new

  alias_method :length, def size
    x.size
  end

  def *(other)
    case other
    when Vektor
      raise ArgumentError, "the vektors don't have the same length!" if size != other.size
      x.zip(other.x).map {|a, b| a * b }.sum
    when Numeric
      self.class.new(*x.map(&other.method(:*)))
    end
  end

  private

  attr_writer :x

  freeze
end

(в основном идентично, но без метода coerce и без предложения else в выражении case, а читатель x должен быть public.)

module IntegerTimesVektorExtension
  def *(other)
    return Vektor[*other.x.map(&method(:*))] if other.is_a?(Vektor)
    super
  end
end

И поскольку базовые классы для monkey-patching действительно опасны, мы используем уточнение , чтобы убедиться, что monkey-patch исправна только там, где вы явно активировать уточнение с помощью using IntegerTimesVektorRefinement.

module IntegerTimesVektorRefinement
  refine Integer do
    prepend IntegerTimesVektorExtension
  end
end

Теперь мы можем сделать что-то вроде этого:

v = Vektor[2, 3, 4]

5 * v
# `*': Vektor can't be coerced into Integer (TypeError)

using IntegerTimesVektorRefinement

5 * v
#=> <#<Vektor:0x00007fcc88868588 @x=[10, 15, 20]>

В старые темные времена, до того, как Module#prepend существовал, нам пришлось прибегнуть к другим грязным уловкам , чтобы иметь возможность сохранить метод исправления обезьян. Однако по состоянию на выпуск Ruby 2. 0 24 февраля 2013 года, который включает Module#prepend, эти трюки больше не нужны, и не следует ни использовать, ни обучать . Сюда входит цепной трюк alias_method, который выглядит следующим образом:

class Integer
  alias_method :original_mul, :*

  def *(other)
    return Vektor[*other.x.map(&method(:*))] if other.is_a?(Vektor)
    original_mul(other)
  end
end

Но, как уже упоминалось: вам не следует делать это .

Лучшее решение - это реализовать протокол coerce. Действительно не «просто» лучшее решение, а только правильное решение .

Если по какой-то причине вы не хотите реализовать протокол coerce, тогда лучшее решение - Module#prepend. В идеале с уточнением, но имейте в виду, что не все реализации Ruby реализуют уточнения.

Если вы действительно, действительно, действительно должны делать исправления обезьян, и используете десятилетнюю неподдерживаемую неподдерживаемую поддержку Устаревшая, устаревшая версия Ruby и, следовательно, не может использовать Module#prepend, тогда есть все еще лучшие решения, чем alias_method, такие как захват метода экземпляра как Method объект и сохранение его в локальной переменной, которую вы закрываете.

Использование alias_method здесь не обязательно, если не прямо неправильно , и плохая, устаревшая, устаревшая практика.

...