「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つと同じ
◎ひとこと
今回、全く知らなかった「宇宙船演算子 <=>」を調べてみて、表にはあまり出てこない「縁の下の力持ち感」があって、とても好きになりました。
なにより「宇宙船演算子」という<=>にピッタリなネーミングセンスに感動しました。
◎最後にお願い
記事の内容に誤りがあった場合、ご指摘いただけるとありがたいです。都度、加筆・修正させていただきます。