概要
最近、Rubyについてもっと理解を深めたくて、Ruby技術者認定試験合格教本を読んでいます。
以下のようなUFO演算子(<=>)をメソッドとして定義している部分が分からず、まとめてみました。
(宇宙船演算子ともいうらしいですが、UFO演算子と呼ばせていただきます)
def <=> other
return self.id <=> other.id
end
ざっくり結論
sortメソッド等に利用する為に、<=> を再定義している記述だった。
↑ @scivola さんより以下ご指摘いただき、「再」を削除しました。
Object には <=> は定義されていないので,Employee クラスで 再定義 しているわけではないんですよね。
ですので、単純に「定義」している記述という表現が正しいです!
どうしてこの結論に至ったか、以下経緯をまとめました。
経緯
下記のような記述が登場。
パッとみて、上記のメソッド部分が全然分からなかったので調べました。
class Employee
attr_reader :id
attr_accessor :name
def initialize id, name
@id = id
@name = name
end
def to_s
return "#{@id}: #{@name}"
end
def <=> other
return self.id <=> other.id
end
end
employees = []
employees << Employee.new("3","Tanaka")
employees << Employee.new("1","Suzuki")
employees << Employee.new("2","Sato")
employees.sort!
employees.each do |employee| puts employee end
実行結果
1: Suzuki
2: Sato
3: Tanaka
分からなかったこと
employees.sort! の所で呼ばれてるメソッドっぽいけど、
どうやって呼ばれているの? 役割は?
調べてみたこと
<=> ってなんだっけ?
UFO演算子と呼ばれる比較演算子で、これは比較の結果を数値で返します。左辺の値が右辺の値より大きければ、1、等しければ0、左辺の値が小さければ-1を返します。ソート処理などで順番の決定などに使用します。
100 <=> 10 #=> 1
100 <=> 100 #=> 0
10 <=> 100 #=> -1
Ruby技術者認定試験合格教本より
ふむふむ。
sortメソッドについても改めて調べてみる
sortメソッドは、配列の要素をソートした新しい配列を返します。要素の順序の比較には<=>演算子が使われ、「要素1 <=> 要素2」の結果が-1なら要素1が先、0なら同じ、1なら要素2が先となります。
https://ref.xaio.jp/ruby/classes/array/sort
ほうほう。sort って、<=> を使っているか!
じゃあ、そのタイミングで呼ばれているっぽいな・・・
挙動確認してみた
ダメ元で、UFO演算子メソッド部分をコメントアウトして、employees.sort!
してみる。
23: employees = []
24: employees << Employee.new("3","Tanaka")
25: employees << Employee.new("1","Suzuki")
26: employees << Employee.new("2","Sato")
27: binding.pry
=> 28: employees.sort!
29: employees.each do |employee| puts employee end
[1] pry(main)> employees
=> [#<Employee:0x00007ff24eb8d9c0 @id="3", @name="Tanaka">,
#<Employee:0x00007ff24eb8d948 @id="1", @name="Suzuki">,
#<Employee:0x00007ff24eb8d8d0 @id="2", @name="Sato">]
[2] pry(main)> employees.sort!
ArgumentError: comparison of Employee with Employee failed
from (pry):3:in `sort!'
やっぱり出来ない!
比較する要素が複数あるからかな。
コメントアウトを外して、実行してみると上手くいく↓
22: employees = []
23: employees << Employee.new("3","Tanaka")
24: employees << Employee.new("1","Suzuki")
25: employees << Employee.new("2","Sato")
26: binding.pry
=> 27: employees.sort!
28: employees.each do |employee| puts employee end
[1] pry(main)> employees.sort!
=> [#<Employee:0x00007fdb013d13d0 @id="1", @name="Suzuki">,
#<Employee:0x00007fdb013d1358 @id="2", @name="Sato">,
#<Employee:0x00007fdb013d1448 @id="3", @name="Tanaka">]
次に、UFO演算子のメソッド部分でbinding.pry
して、
self.id
と other.id
の値をチェック、どんな値が返されるかを考えてみる。
16: def <=> other
17: binding.pry
=> 18: return self.id <=> other.id
19: end
[1] pry(#<Employee>)> self.id
=> "3"
[2] pry(#<Employee>)> other.id
=> "1"
3 <=> 1 だから、1が返る。
結果、 1 => 3 => 2 の順番になるかな。
exit してみると止まるので、次のself.id
と other.id
をチェック。
16: def <=> other
17: binding.pry
=> 18: return self.id <=> other.id
19: end
[1] pry(#<Employee>)> self.id
=> "3"
[2] pry(#<Employee>)> other.id
=> "2"
3 <=> 2 だから、1が返る。
結果、 1 => 2 => 3 の順番かな。
exit して、self.id
と other.id
をチェック。
16: def <=> other
17: binding.pry
=> 18: return self.id <=> other.id
19: end
[1] pry(#<Employee>)> self.id
=> "1"
[2] pry(#<Employee>)> other.id
=> "2"
1 <=> 2 だから、-1が返る。
結果変わらず、 1 => 2 => 3 の順番かな。
ここでexitしたら、最後まで実行された!
id 同士だけを比較しているので、エラーにならなかった!!
まとめ
やはり、sort! のタイミングで、UFO演算子のメソッドが呼ばれていた。
UFO演算子のメソッド内で、<=> の定義を、id同士を比較するものに再定義していた為、
エラーにならずsortメソッドが最後まで実行された!
sortメソッドは、1が返ると右辺と左辺の順序が入れ替わり、
-1が返ると順序が入れ替わらないので、1=>2=>3 の順に並び替わる。
最後に
分かってしまえばそんなに難しくないですが、最初にdef <=> other
を見た時は、正直混乱しました。
「演算子を再定義することが出来る」という概念がなかったので、勉強になりました!
sort の挙動についてもよく分かってよかった。
Qiita 初投稿です。見辛い&レベル低いかもですが、お許しください・・
「違うよ!」「ここの説明の方が分かりやすいよ!」などのアドバイスありましたら、
コメントいただけると喜びます。
補足
@scivola さんより、 <=> を定義せず idの値を sort_by を使って比較する方法も教えていただきました!
コメント欄にて詳しく解説してくださいましたので、チェックしてみてください!
@scivola さん、ありがとうございます!!
参考にさせていただいたサイト様
https://www.uosansatox.biz/entry/2018/01/25/112048
https://codeday.me/jp/qa/20190301/344992.html
https://www.buildinsider.net/language/rubytips/0018
最後まで読んでくださり、ありがとうございました!