8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

UFO演算子をメソッドとして定義する

Last updated at Posted at 2019-08-28

概要

最近、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.idother.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.idother.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.idother.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

最後まで読んでくださり、ありがとうございました!

8
2
4

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
8
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?