5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

「知らず知らず使っていた<=>」

Last updated at Posted at 2025-12-19

「Ruby/Rails Advent Calendar 2025」の20日目の記事です。

◎はじめに

Rubyに関連する書籍を何冊か読んでいて、ふと<=>が気になったので調べてみました。以下はそのまとめです。

◎そもそも <=> は何?

初めてこの演算子<=>を見たとき、「なんと呼べばいいのか?」「そもそも何なのか?」が気になったため、調べてみました。

● 呼称

呼称としては「宇宙船演算子」「三方比較演算子」「UFO演算子」があるようです。
この記事では、Ruby用語集 (Ruby 3.4 リファレンスマニュアル)に倣い「宇宙船演算子」の呼称を使います。

● どういう演算子?

今までRubyを学んできた中で、大小比較する演算子といえば、次の4つは馴染みがありました。
> < <= >=

p 1 > 2 # => false
p 1 < 2 # => true
p 1 <= 2 # => true
p 1 >= 2 # => false

では、本題である「宇宙船演算子<=>」を見ていきましょう。
module Comparable (Ruby 3.4 リファレンスマニュアル)を参考にしました。
言葉で説明するより、コードを見ながらのほうが実際の動きがイメージしやすいと思います。

p 1 <=> 2 # => -1 「左辺 < 右辺」なら-1が返る
p 2 <=> 1 # => 1  「左辺 > 右辺」なら1が返る
p 1 <=> 1 # => 0  「左辺 = 右辺」なら0が返る
p 1 <=> "1" # =>nil 「左辺と右辺が比較できない場合」 nilが返る

「宇宙船演算子<=>」がどういう挙動なのかは理解できました。
ここで自分に新たな疑問が浮かびました。
「今まで宇宙船演算子は使ったことないけど、何に使うんだろう?」

◎ 「宇宙船演算子 <=>」は何に使う?

リファレンスマニュアルに下の記述を見つけました。

他の比較演算子は、<=>演算子を利用して定義されます。
module Comparable (Ruby 3.4 リファレンスマニュアル)より一部引用

基本的な比較演算子。ほかの比較演算子はこの演算子を元に Comparable モジュールで定義されています。
Rubyで使われる記号の意味(正規表現の複雑な記号は除く) (Ruby 3.4 リファレンスマニュアル)より一部引用

自分が知らなかっただけで「宇宙船演算子<=>」は、すべての比較演算子の元になる存在みたいです。

コメントをいただいての追記:

このモジュールをインクルードするクラスは、基本的な比較演算子である <=> 演算子を定義している必要があります。
module Comparable (Ruby 3.4 リファレンスマニュアル)より一部引用

「Comparable を include していないが、 < <= などの比較演算子は持っている」というクラスも存在しているので、すべての比較演算子が<=>を元にしているわけではないようです。
一例として「class Hash」「Hash#<」が挙げられます。


「宇宙船演算子<=>」は、多くのメソッドにも利用されているようです。下にいくつか列挙します。

◎「Arrayクラスのsort」と「宇宙船演算子 <=>」で遊んでみる

みんな大好き(?) 「Arrayクラスのsortメソッド」と「宇宙船演算子<=>」を使って、少しだけ遊んでみたいと思います。

# ブロックなしsort
[2, 5, 4, 3, 1, 0].sort
# => [0, 1, 2, 3, 4, 5]

# ブロックに <=> を使ったsort
[2, 5, 4, 3, 1, 0].sort { |a, b| a <=> b }
# => [0, 1, 2, 3, 4, 5]

出力結果は変わりませんが、内部処理は違うようです。内部処理に関しては、ほとんど知識がないため、説明は割愛させていただきます。一応、パーサーにかけた結果だけを載せておきます。

 [2, 5, 4, 3, 1, 0].sortの結果
irb(main):001> RubyVM::AbstractSyntaxTree.parse('[2, 5, 4, 3, 1, 0].sort')
=>
(SCOPE@1:0-1:23
 tbl: []
 args: nil
 body:
   (CALL@1:0-1:23
      (LIST@1:0-1:18 (INTEGER@1:1-1:2 2) (INTEGER@1:4-1:5 5) (INTEGER@1:7-1:8 4)
         (INTEGER@1:10-1:11 3) (INTEGER@1:13-1:14 1) (INTEGER@1:16-1:17 0) nil)
      :sort nil))
 [2, 5, 4, 3, 1, 0].sort{ |a, b| a <=> b }の結果
irb(main):002> RubyVM::AbstractSyntaxTree.parse('[2, 5, 4, 3, 1, 0].sort{ |a, b| a <=> b }')
=>
(SCOPE@1:0-1:41
 tbl: []
 args: nil
 body:
   (ITER@1:0-1:41 (CALL@1:0-1:23 (LIST@1:0-1:18 (INTEGER@1:1-1:2 2) (INTEGER@1:4-1:5 5) (INTEGER@1:7-1:8 4) (INTEGER@1:10-1:11 3) (INTEGER@1:13-1:14 1) (INTEGER@1:16-1:17 0) nil) :sort nil)
      (SCOPE@1:23-1:41
       tbl: [:a, :b]
       args: (ARGS@1:26-1:30 pre_num: 2 pre_init: nil opt: nil first_post: nil post_num: 0 post_init: nil rest: nil kw: nil kwrest: nil block: nil)
       body: (OPCALL@1:32-1:39 (DVAR@1:32-1:33 :a) :<=> (LIST@1:38-1:39 (DVAR@1:38-1:39 :b) nil)))))

# <=> の a と b の位置を入れ替えてみる
[2, 5, 4, 3, 1, 0].sort{ |a, b| b <=> a }
# => [5, 4, 3, 2, 1, 0] 並びが降順になる

# ブロック引数 a, b の位置を入れ替え
[2, 5, 4, 3, 1, 0].sort{ |b, a| a <=> b }
# => [5, 4, 3, 2, 1, 0] 並びが降順になる

# reverse メソッド
[2, 5, 4, 3, 1, 0].sort.reverse
# => [5, 4, 3, 2, 1, 0] 出力結果は上2つと同じ

◎ひとこと

今回、全く知らなかった「宇宙船演算子 <=>」を調べてみて、表にはあまり出てこない「縁の下の力持ち感」があって、とても好きになりました。
なにより「宇宙船演算子」という<=>にピッタリなネーミングセンスに感動しました。

◎最後にお願い

記事の内容に誤りがあった場合、ご指摘いただけるとありがたいです。都度、加筆・修正させていただきます。

5
1
2

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
5
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?