Pull to refresh

Равенство в Ruby

Reading time3 min
Views18K
Для начинающих рубистов четыре вида проверки равенства в языке являются чем-то пугающим.
И это тот хваленый Ruby, который славится своей простотой и элегантностью. Давайте попробуем разобраться что к чему, и зачем понадобилось столько функций. Для проверки будем использовать следующие объекты.

string = "some string"
same_string = string
copy_string = "some string"

int = 7
same_int = int
copy_float = 7.0
new_int = 7

class TestClass
  def initialize(content)
    @content = content
  end
end

class SubTestClass < TestClass
  def initialize(content)
    super(content)
  end
end

test_obj = TestClass.new("something")
same_obj = test_obj
new_obj = TestClass.new("something")
sub_obj = SubTestClass.new("something")



1. Метод equal?()

Пожалуй, самый простой метод. Он определен у класса Object и не может быть переписан в дочерних классах(как оказалось, может быть, но крайне не рекомендуется!). Все, что он делает — это проверка, указывают ли переменные на один и тот же объект.
Проверяем:
puts string.equal?(same_string) #true, все правильно, указывают на один объект
puts string.equal?(copy_string) #false, уже разные объекты

puts int.equal?(same_int) #true, идем дальше
puts int.equal?(copy_float) #false 7 и 7.0 - разные объекты
puts int.equal?(new_int) #true, для каждого числа Fixnum существует только один экземпляр, поэтому и #свойства object_id у них одинаковые. Однако, это не значит, что при изменении new_int, изменится int

puts test_obj.equal?(same_obj) #true, все правильно
puts test_obj.equal?(new_obj) #false, конечно, разные объекты
puts test_obj.equal?(sub_obj) #false, вообще разные классы, о чем речь

Ну здесь все предельно ясно, двигаемся к следующему методу.

2. Метод eql?()

Так же определен в классе Object. Для него работает так же, как и equal?(), однако переопределяется в подклассах(в частности всех Numeric и String) — возвращает true, если объекты имеют одинаковые значения, однако проверяется еще и принадлежность к одному классу. Используется классом Hash.

puts string.eql?(same_string) #true, правильно, они ведь указывают на один объект
puts string.eql?(copy_string) #true, содержимое-то одинаковое

puts int.eql?(same_int) #true, если уж equal? сработал, то eql? тем более
puts int.eql?(copy_float) #false, нелады, разные классы
puts int.eql?(new_int) #true, все верно, значения одинаковые, классы одинаковые

puts test_obj.eql?(same_obj) #true, пфф, еще бы
puts test_obj.eql?(new_obj) #false, внимание!, метод eql? здесь наследовался прямо из Object, поэтому #работает как equal? 
puts test_obj.eql?(sub_obj) #false, очевидно, если нет, см выше


3. ==
На уровне класса Object делает то же самое, что и equal?(опять!) то есть проверяет указывают ли переменные на один объект, однако поведение в подклассах(в частности числовых и строках) переписано иначе — проверяются только значения, и не требуется принадлежность одному классу(для числовых), в чем и состоит разница с eql?..

puts string == (same_string) #true, одинаковые значения
puts string == (copy_string) #true, опять одинаковые

puts int == (same_int) #true, указывают на один объект
puts int == (copy_float) #true, значения-то одинаковые, хотя классы разные
puts int == (new_int) #true, еще бы

#внимание! для экземпляров созданного класса == не переопределено, поэтому наследуется вновь из #Object, то есть опять работает как equal?
puts test_obj == (same_obj) #true
puts test_obj == (new_obj) #false
puts test_obj == (sub_obj) #false


4. ===
Результаты для === точно такие же, как и для ==(с одним «но» для строк и регулярных выражений). Наиболее частое использование тройного равно — это в компоненте case при сравнении, как вы уже могли догадаться, строк и регулярных выражений.
Единственный пример, который я приведу здесь, будет:

puts /p.*cock/ == 'peacock' #false, потому что == не умеет делать то,
puts /p.*cock/ === 'peacock' #true, что умеет делать ===


В этом единственная маленькая разница.

Вывод:

Чаще всего нами используются лишь два метода — equal?() и ==. Главное — это помнить, что equal?() всегда(!) и повторюсь, всегда, проверяет лишь то, указывают ли переменные на один объект.

== делает то же самое. Однако на чисто интуитивном уровне понятно, что двойное равно для простых типов данных, как Fixnum, String, Float — тех, которые мы используем чаще всего, должно лишь проверять равенство значений. Именно поэтому == было переписано для них именно так. Да и при разработке своих классов лучше переопределить == для проверки лишь содержимого.

eql? используется классом Hash, но в нем нет особой необходимости для обычных программистов. Единственное, если вы хотите расширить функционал == для простых типов(для которых эта функция переопределена) еще и проверкой на принадлежностью одному классу, то можно использовать eql?

Что же касается ===, то его вообще редко доводится использовать, так как эта функция была реализована по большей части для управляющего элемента case. Однако если есть необходимость сравнить регулярное выражение и строку(чем-то, кроме =~, но зачем?), то здесь и пригодится тройное равно.
Tags:
Hubs:
+27
Comments26

Articles