Apa perbedaan antara sama ?, Persamaan ?, ===, dan ==?

552

Saya mencoba memahami perbedaan antara keempat metode ini. Saya tahu secara default yang ==memanggil metode equal?yang mengembalikan true ketika kedua operan merujuk ke objek yang sama persis.

===secara default juga memanggil panggilan ==mana equal?... oke, jadi jika ketiga metode ini tidak diganti, maka saya kira ===, ==dan equal?melakukan hal yang persis sama?

Sekarang tiba eql?. Apa yang dilakukan ini (secara default)? Apakah itu membuat panggilan ke hash / id operan?

Mengapa Ruby memiliki begitu banyak tanda kesetaraan? Apakah mereka seharusnya berbeda dalam semantik?

denniss
sumber
Aku baru saja mulai irb dan memiliki hasil sebagai berikut yang bertentangan Anda ... Semua 3 ini benar: "a" == "a", "a" === "a"dan "a".eql? "a". Tapi ini salah: "a".equal? "a"(Milik saya ruby ​​1.9.2-p180)
PeterWong
7
@ Peter: Itu karena string menimpa semua operator kesetaraan. Mencoba menggunakan a = Object.new; b = Object.newmaka semua ==, ===, .equal?, .eql?akan kembali trueuntuk avs adan palsu untuk avs b.
Nemo157

Jawaban:

785

Saya akan sangat mengutip dokumentasi Obyek di sini, karena saya pikir ia memiliki beberapa penjelasan yang bagus. Saya mendorong Anda untuk membacanya, dan juga dokumentasi untuk metode ini karena mereka ditimpa di kelas lain, seperti String .

Catatan: jika Anda ingin mencobanya sendiri pada objek yang berbeda, gunakan sesuatu seperti ini:

class Object
  def all_equals(o)
    ops = [:==, :===, :eql?, :equal?]
    Hash[ops.map(&:to_s).zip(ops.map {|s| send(s, o) })]
  end
end

"a".all_equals "a" # => {"=="=>true, "==="=>true, "eql?"=>true, "equal?"=>false}

== - "kesetaraan" generik

Pada level Objek, ==mengembalikan true hanya jika objdan otheradalah objek yang sama. Biasanya, metode ini ditimpa dalam kelas turunan untuk memberikan makna khusus kelas.

Ini adalah perbandingan yang paling umum, dan dengan demikian tempat paling mendasar di mana Anda (sebagai penulis kelas) dapat memutuskan apakah dua objek "sama" atau tidak.

=== - kesetaraan kasus

Untuk Object class, secara efektif sama dengan pemanggilan #==, tetapi biasanya ditimpa oleh keturunan untuk memberikan semantik yang bermakna dalam pernyataan kasus.

Ini sangat berguna. Contoh hal-hal yang memiliki ===implementasi yang menarik :

  • Jarak
  • Regex
  • Proc (dalam Ruby 1.9)

Jadi Anda dapat melakukan hal-hal seperti:

case some_object
when /a regex/
  # The regex matches
when 2..4
  # some_object is in the range 2..4
when lambda {|x| some_crazy_custom_predicate }
  # the lambda returned true
end

Lihat jawaban saya di sini untuk contoh rapi tentang bagaimana case+ Regexdapat membuat kode lebih bersih. Dan tentu saja, dengan menyediakan ===implementasi Anda sendiri , Anda bisa mendapatkan casesemantik khusus .

eql?- Hashkesetaraan

The eql?Metode mengembalikan true jika objdan othermengacu pada kunci hash yang sama. Ini digunakan oleh Hashuntuk menguji anggota untuk kesetaraan. Untuk objek kelas Object, eql?identik dengan ==. Subkelas biasanya meneruskan tradisi ini dengan eql?menggunakan ==metode yang diganti , tetapi ada pengecualian. Numerictipe, misalnya, melakukan konversi tipe lintas ==, tetapi tidak lintas eql?, jadi:

1 == 1.0     #=> true
1.eql? 1.0   #=> false

Jadi Anda bebas untuk menimpa ini untuk penggunaan Anda sendiri, atau Anda dapat menimpa ==dan menggunakan alias :eql? :==sehingga kedua metode berperilaku dengan cara yang sama.

equal? - perbandingan identitas

Tidak seperti ==, equal?metode ini tidak boleh ditimpa oleh subclass: ia digunakan untuk menentukan identitas objek (yaitu, a.equal?(b)jika iff aadalah objek yang sama dengan b).

Ini adalah perbandingan pointer secara efektif.

jtbandes
sumber
32
Seperti yang saya mengerti dari jawaban Anda, ketegasannya adalah: sama? <eql? <== <===. Biasanya, Anda menggunakan ==. Untuk beberapa tujuan yang longgar, Anda menggunakan ===. Untuk situasi yang ketat, Anda menggunakan eql ?, dan untuk identitas lengkap, Anda menggunakan equal ?.
sawa
21
Gagasan tentang ketegaran tidak ditegakkan atau bahkan disarankan dalam dokumentasi, kebetulan yang Numericmenanganinya lebih ketat daripada ==. Itu benar-benar terserah penulis kelas. ===jarang digunakan di luar casepernyataan.
jtbandes
4
== Apakah persamaan dalam hal lebih besar / kecil juga. Yaitu, jika Anda memasukkan Sebanding, itu akan didefinisikan dalam hal <=> mengembalikan 0. Inilah sebabnya mengapa 1 == 1.0 mengembalikan true.
apeiros
5
@sawa Saya biasanya menganggap ===sebagai "cocok" (kira-kira). Seperti pada, "apakah regexp cocok dengan string" atau "apakah rentang cocok dengan (termasuk) nomor".
Kelvin
7
Fakta menyenangkan: dokumen resmi sekarang terhubung ke jawaban ini (lihat ruby-doc.org/core-2.1.5/… ).
Mark Amery
46

Saya suka jawaban jtbandes, tetapi karena cukup panjang, saya akan menambahkan jawaban ringkas saya sendiri:

==, ===, eql?,equal?
4 pembanding, yaitu. 4 cara membandingkan 2 objek, di Ruby.
Karena, di Ruby, semua pembanding (dan sebagian besar operator) sebenarnya adalah pemanggilan metode, Anda dapat mengubah, menimpa, dan menentukan sendiri semantik dari metode perbandingan ini. Namun, penting untuk dipahami, ketika bahasa internal Ruby menggunakan pembanding:

==(perbandingan nilai)
Ruby menggunakan: == di mana-mana untuk membandingkan nilai 2 objek, mis. Nilai hash:

{a: 'z'}  ==  {a: 'Z'}    # => false
{a: 1}    ==  {a: 1.0}    # => true

===(perbandingan kasus)
Ruby menggunakan: === dalam kasus / saat konstruksi. Cuplikan kode berikut identik secara logis:

case foo
  when bar;  p 'do something'
end

if bar === foo
  p 'do something'
end

eql?(Perbandingan hash-kunci)
Ruby menggunakan: eql? (dalam kombinasi dengan metode hash) untuk membandingkan kunci hash. Di sebagian besar kelas: eql? identik dengan: ==.
Pengetahuan tentang: eql? hanya penting, ketika Anda ingin membuat kelas khusus Anda sendiri:

class Equ
  attr_accessor :val
  alias_method  :initialize, :val=
  def hash()           self.val % 2             end
  def eql?(other)      self.hash == other.hash  end
end

h = {Equ.new(3) => 3,  Equ.new(8) => 8,  Equ.new(15) => 15}    #3 entries, but 2 are :eql?
h.size            # => 2
h[Equ.new(27)]    # => 15

Catatan: Set kelas Ruby yang umum digunakan juga bergantung pada perbandingan tombol-Hash.

equal?(perbandingan identitas objek)
Ruby menggunakan: sama? untuk memeriksa apakah dua objek identik. Metode ini (dari kelas BasicObject) tidak seharusnya ditimpa.

obj = obj2 = 'a'
obj.equal? obj2       # => true
obj.equal? obj.dup    # => false
Andreas Rayo Kniep
sumber
30
Ini jawaban yang bagus, tetapi hampir sepanjang jtbandes. :)
odigity
2
@odigity, sekitar 70% selama. Saya bisa memikirkan banyak hal untuk menghabiskan 30%.
Cary Swoveland
Saya pikir contohnya eql?sangat menyesatkan. eql?adalah perbandingan kesetaraan yang konsisten dengan bagaimana hash dihitung, yaitu a.eql?(b)menjamin itu a.hash == b.hash. Itu tidak hanya membandingkan kode hash.
Andrey Tarantsov
Apakah perbandingan kasus benar-benar setara bar === foodan tidak foo === bar? Saya berharap yang terakhir ini benar dan ini penting karena kompiler memanggil sisi kiri: === `'
Alexis Wilke
Sejauh yang saya tahu, itu adalah bar === foo: Ruby menggunakan nilai case di sisi kiri dan variabel case di sisi kanan. Ini mungkin ada hubungannya dengan menghindari NPE (Null Pointer Exception).
Andreas Rayo Kniep
34

Operator kesetaraan: == dan! =

Operator ==, juga dikenal sebagai kesetaraan atau dobel sama, akan mengembalikan true jika kedua objek sama dan salah jika tidak.

"koan" == "koan" # Output: => true

Operator! =, Juga dikenal sebagai ketimpangan, adalah kebalikan dari ==. Ini akan mengembalikan true jika kedua objek tidak sama dan salah jika keduanya sama.

"koan" != "discursive thought" # Output: => true

Perhatikan bahwa dua array dengan elemen yang sama dalam urutan yang berbeda tidak sama, versi huruf besar dan kecil dari huruf yang sama tidak sama dan seterusnya.

Saat membandingkan angka dari tipe yang berbeda (mis. Integer dan float), jika nilai numeriknya sama, == akan mengembalikan true.

2 == 2.0 # Output: => true

sama?

Tidak seperti operator == yang menguji apakah kedua operan sama, metode yang sama memeriksa apakah kedua operan merujuk ke objek yang sama. Ini adalah bentuk kesetaraan paling ketat di Ruby.

Contoh: a = "zen" b = "zen"

a.object_id  # Output: => 20139460
b.object_id  # Output :=> 19972120

a.equal? b  # Output: => false

Dalam contoh di atas, kami memiliki dua string dengan nilai yang sama. Namun, mereka adalah dua objek yang berbeda, dengan ID objek yang berbeda. Jadi, sama saja? metode akan mengembalikan false.

Mari kita coba lagi, hanya saja kali ini b akan menjadi referensi ke a. Perhatikan bahwa ID objek sama untuk kedua variabel, karena mereka menunjuk ke objek yang sama.

a = "zen"
b = a

a.object_id  # Output: => 18637360
b.object_id  # Output: => 18637360

a.equal? b  # Output: => true

EQL?

Di kelas Hash, persamaannya? Metode ini digunakan untuk menguji kunci untuk kesetaraan. Diperlukan latar belakang untuk menjelaskan hal ini. Dalam konteks umum komputasi, fungsi hash mengambil string (atau file) dari ukuran apa pun dan menghasilkan string atau integer ukuran tetap yang disebut kode hash, biasanya disebut sebagai hash saja. Beberapa jenis kode hash yang umum digunakan adalah MD5, SHA-1, dan CRC. Mereka digunakan dalam algoritma enkripsi, pengindeksan basis data, pemeriksaan integritas file, dll. Beberapa bahasa pemrograman, seperti Ruby, menyediakan jenis koleksi yang disebut tabel hash. Tabel hash adalah koleksi seperti kamus yang menyimpan data berpasangan, terdiri dari kunci unik dan nilainya. Di bawah tenda, kunci-kunci itu disimpan sebagai kode hash. Tabel hash biasanya disebut sebagai hash saja. Perhatikan bagaimana kata hash dapat merujuk ke kode hash atau tabel hash.

Ruby menyediakan metode bawaan yang disebut hash untuk menghasilkan kode hash. Pada contoh di bawah ini, dibutuhkan string dan mengembalikan kode hash. Perhatikan bagaimana string dengan nilai yang sama selalu memiliki kode hash yang sama, meskipun mereka adalah objek yang berbeda (dengan ID objek yang berbeda).

"meditation".hash  # Output: => 1396080688894079547
"meditation".hash  # Output: => 1396080688894079547
"meditation".hash  # Output: => 1396080688894079547

Metode hash diimplementasikan dalam modul Kernel, termasuk dalam kelas Object, yang merupakan root default dari semua objek Ruby. Beberapa kelas seperti Simbol dan Integer menggunakan implementasi default, yang lain seperti String dan Hash menyediakan implementasi mereka sendiri.

Symbol.instance_method(:hash).owner  # Output: => Kernel
Integer.instance_method(:hash).owner # Output: => Kernel

String.instance_method(:hash).owner  # Output: => String
Hash.instance_method(:hash).owner  # Output: => Hash

Di Ruby, ketika kita menyimpan sesuatu dalam hash (koleksi), objek yang disediakan sebagai kunci (misalnya, string atau simbol) dikonversi menjadi dan disimpan sebagai kode hash. Kemudian, ketika mengambil elemen dari hash (koleksi), kami menyediakan objek sebagai kunci, yang dikonversi menjadi kode hash dan dibandingkan dengan kunci yang ada. Jika ada kecocokan, nilai item yang sesuai dikembalikan. Perbandingan dibuat dengan menggunakan eql? metode di bawah tenda.

"zen".eql? "zen"    # Output: => true
# is the same as
"zen".hash == "zen".hash # Output: => true

Dalam kebanyakan kasus, persamaannya? metode berperilaku serupa dengan metode ==. Namun, ada beberapa pengecualian. Misalnya, eql? tidak melakukan konversi tipe implisit ketika membandingkan integer dengan float.

2 == 2.0    # Output: => true
2.eql? 2.0    # Output: => false
2.hash == 2.0.hash  # Output: => false

Operator kesetaraan kasus: ===

Banyak kelas bawaan Ruby, seperti String, Range, dan Regexp, menyediakan implementasi mereka sendiri dari operator ===, juga dikenal sebagai kesetaraan kasus, sama dengan tiga atau tiga sama. Karena diimplementasikan secara berbeda di setiap kelas, ia akan berperilaku berbeda tergantung pada jenis objek yang dipanggil. Secara umum, ia mengembalikan true jika objek di sebelah kanan "milik" atau "adalah anggota" objek di sebelah kiri. Sebagai contoh, ini dapat digunakan untuk menguji apakah suatu objek adalah turunan dari kelas (atau salah satu dari subkelasnya).

String === "zen"  # Output: => true
Range === (1..2)   # Output: => true
Array === [1,2,3]   # Output: => true
Integer === 2   # Output: => true

Hasil yang sama dapat dicapai dengan metode lain yang mungkin paling cocok untuk pekerjaan itu. Biasanya lebih baik menulis kode yang mudah dibaca dengan menjadi sejelas mungkin, tanpa mengorbankan efisiensi dan keringkasan.

2.is_a? Integer   # Output: => true
2.kind_of? Integer  # Output: => true
2.instance_of? Integer # Output: => false

Perhatikan contoh terakhir yang dikembalikan false karena integer seperti 2 adalah instance dari kelas Fixnum, yang merupakan subkelas dari kelas Integer. ===, is_a? dan instance_of? Metode mengembalikan true jika objek adalah turunan dari kelas yang diberikan atau setiap subclass. Metode instance_of lebih ketat dan hanya mengembalikan true jika objek adalah turunan dari kelas yang tepat, bukan subkelas.

Is_a? dan kind_of? metode diimplementasikan dalam modul Kernel, yang dicampur oleh kelas Object. Keduanya alias dengan metode yang sama. Mari kita verifikasi:

Kernel.instance_method (: kind_of?) == Kernel.instance_method (: is_a?) # Output: => true

Rentang Implementasi ===

Ketika operator === dipanggil pada objek rentang, ia mengembalikan true jika nilai di sebelah kanan jatuh dalam rentang di sebelah kiri.

(1..4) === 3  # Output: => true
(1..4) === 2.345 # Output: => true
(1..4) === 6  # Output: => false

("a".."d") === "c" # Output: => true
("a".."d") === "e" # Output: => false

Ingat bahwa operator === memanggil metode === dari objek sebelah kiri. Jadi (1..4) === 3 setara dengan (1..4). === 3. Dengan kata lain, kelas operan kiri akan menentukan implementasi metode === yang akan menjadi dipanggil, sehingga posisi operan tidak dapat dipertukarkan.

Implementasi Regexp ===

Mengembalikan nilai true jika string di sebelah kanan cocok dengan ekspresi reguler di sebelah kiri. / zen / === "berlatih zazen hari ini" # Output: => true # sama dengan "berlatih zazen hari ini" = ~ / zen /

Penggunaan implisit operator === pada pernyataan case / when

Operator ini juga digunakan di bawah kap pada pernyataan kasus / saat. Itu adalah penggunaannya yang paling umum.

minutes = 15

case minutes
  when 10..20
    puts "match"
  else
    puts "no match"
end

# Output: match

Dalam contoh di atas, jika Ruby secara implisit menggunakan operator ganda sama dengan (==), kisaran 10..20 tidak akan dianggap sama dengan bilangan bulat seperti 15. Mereka cocok karena operator sama dengan tiga (===) adalah secara implisit digunakan dalam semua kasus / kapan pernyataan. Kode dalam contoh di atas setara dengan:

if (10..20) === minutes
  puts "match"
else
  puts "no match"
end

Operator pencocokan pola: = ~ dan! ~

Operator = ~ (equal-tilde) dan! ~ (Bang-tilde) digunakan untuk mencocokkan string dan simbol dengan pola regex.

Implementasi metode = ~ di kelas String dan Simbol mengharapkan ekspresi reguler (turunan dari kelas Regexp) sebagai argumen.

"practice zazen" =~ /zen/   # Output: => 11
"practice zazen" =~ /discursive thought/ # Output: => nil

:zazen =~ /zen/    # Output: => 2
:zazen =~ /discursive thought/  # Output: => nil

Implementasi di kelas Regexp mengharapkan string atau simbol sebagai argumen.

/zen/ =~ "practice zazen"  # Output: => 11
/zen/ =~ "discursive thought" # Output: => nil

Di semua implementasi, ketika string atau simbol cocok dengan pola Regexp, itu mengembalikan bilangan bulat yang merupakan posisi (indeks) dari pertandingan. Jika tidak ada kecocokan, mengembalikan nihil. Ingat bahwa, di Ruby, nilai integer apa pun adalah "benar" dan nil adalah "salah", sehingga operator = ~ dapat digunakan dalam pernyataan if dan operator ternary.

puts "yes" if "zazen" =~ /zen/ # Output: => yes
"zazen" =~ /zen/?"yes":"no" # Output: => yes

Operator pencocokan pola juga berguna untuk menulis pernyataan yang lebih pendek jika. Contoh:

if meditation_type == "zazen" || meditation_type == "shikantaza" || meditation_type == "kinhin"
  true
end
Can be rewritten as:
if meditation_type =~ /^(zazen|shikantaza|kinhin)$/
  true
end

Operator! ~ Adalah kebalikan dari = ~, ia mengembalikan true ketika tidak ada kecocokan dan salah jika ada kecocokan.

Info lebih lanjut tersedia di posting blog ini .

BrunoFacca
sumber
6
Saya menemukan ini jawaban yang lebih baik daripada jawaban yang saat ini diterima, karena memberikan contoh yang bagus dan kurang ambigu tentang apa arti berbagai jenis kesetaraan dan mengapa mereka ada / di mana mereka digunakan.
Qqwy
1
Jawaban yang sangat terperinci, tetapi pada irb saya (ruby v 2.2.1) :zen === "zen"mengembalikan false
Mike R
@ MikeR Terima kasih telah memberi tahu saya. Saya sudah mengoreksi jawabannya.
BrunoFacca
Saya pikir maksud Anda type_of? "Perhatikan contoh terakhir yang dikembalikan false karena integer seperti 2 adalah instance dari kelas Fixnum, yang merupakan subkelas dari kelas Integer. ===, is_a? Dan instance_of? (TYPE_OF?)"?
user1883793
1
Saya suka jawaban ini. Terima kasih
Abdullah Fadhel
9

Ruby memaparkan beberapa metode berbeda untuk menangani kesetaraan:

a.equal?(b) # object identity - a and b refer to the same object

a.eql?(b) # object equivalence - a and b have the same value

a == b # object equivalence - a and b have the same value with type conversion.

Lanjutkan membaca dengan mengklik tautan di bawah, itu memberi saya pemahaman yang diringkas jelas.

https://www.relishapp.com/rspec/rspec-expectations/v/2-0/docs/matchers/equality-matchers

Semoga ini bisa membantu orang lain.

kalibbala
sumber
8

=== # --- kesetaraan kasus

== # --- kesetaraan umum

keduanya bekerja serupa tetapi "===" bahkan melakukan pernyataan kasus

"test" == "test"  #=> true
"test" === "test" #=> true

di sini bedanya

String === "test"   #=> true
String == "test"  #=> false
Kishore Mohan
sumber
3
Mereka tidak bekerja sama, meskipun cenderung benar bahwa ketika a==bkemudian a===b. Tetapi a===bjauh lebih kuat. ===tidak simetris, dan a===bberarti hal yang sangat berbeda b===a, apalagi a==b.
mwfearnley
8

Saya ingin memperluas ===operator.

=== bukan operator kesetaraan!

Tidak.

Mari kita selesaikan hal itu.

Anda mungkin akrab dengan ===sebagai operator kesetaraan dalam Javascript dan PHP, tetapi ini bukan operator kesetaraan di Ruby dan memiliki semantik yang berbeda secara fundamental.

Jadi apa fungsinya ===?

=== adalah operator pencocokan pola!

  • === cocok dengan ekspresi reguler
  • === memeriksa rentang keanggotaan
  • === memeriksa menjadi instance dari sebuah kelas
  • === panggilan ekspresi lambda
  • === terkadang memeriksa kesetaraan, tetapi sebagian besar tidak

Jadi bagaimana kegilaan ini masuk akal?

  • Enumerable#grepgunakan secara ===internal
  • case whenpernyataan digunakan secara ===internal
  • Fakta menyenangkan, rescuegunakan secara ===internal

Itu sebabnya Anda bisa menggunakan ekspresi reguler dan kelas dan rentang dan bahkan ekspresi lambda dalam sebuah case whenpernyataan.

Beberapa contoh

case value
when /regexp/
  # value matches this regexp
when 4..10
  # value is in range
when MyClass
  # value is an instance of class
when ->(value) { ... }
  # lambda expression returns true
when a, b, c, d
  # value matches one of a through d with `===`
when *array
  # value matches an element in array with `===`
when x
  # values is equal to x unless x is one of the above
end

Semua contoh ini juga berfungsi dengan baik pattern === value, serta dengan grepmetode.

arr = ['the', 'quick', 'brown', 'fox', 1, 1, 2, 3, 5, 8, 13]
arr.grep(/[qx]/)                                                                                                                            
# => ["quick", "fox"]
arr.grep(4..10)
# => [5, 8]
arr.grep(String)
# => ["the", "quick", "brown", "fox"]
arr.grep(1)
# => [1, 1]
akuhn
sumber
-8

Saya menulis tes sederhana untuk semua hal di atas.

def eq(a, b)
  puts "#{[a, '==',  b]} : #{a == b}"
  puts "#{[a, '===', b]} : #{a === b}"
  puts "#{[a, '.eql?', b]} : #{a.eql?(b)}"
  puts "#{[a, '.equal?', b]} : #{a.equal?(b)}"
end

eq("all", "all")
eq(:all, :all)
eq(Object.new, Object.new)
eq(3, 3)
eq(1, 1.0)
Tom Phan
sumber