ネットの海の片隅で

技術ネタの放流、あるいは不法投棄。

Ruby で 1 < 2 < 3 みたいな比較演算を書けるようにする試み

TL;DR

華麗に失敗した。

発端

  • そういえば Python ってたしか 1 < 2 && 2 < 3 じゃなく 1 < 2 < 3 みたいに書けるよな
  • Ruby でもいろいろ魔改造すれば似たようなことできないかな?

こんなモチベーションなので実用は目的にしていない。

何番煎じかもわからないけど、考えてみたかったので考えてみた。

問題をシンプルにする

仕様を絞る。

  • 対象クラスを絞って Integer だけにする
  • 境界値を含まない場合だけを考える(<, > だけ)
  • () による演算の優先順位変更とかも考えない

イデア

「左から順番に比較して、その結果を boolean じゃなく以前の 比較結果を保持するいい感じのオブジェクト で返せば良いのでは?」

左から順番に比較していくだけなら、

  • 前回の比較が true だったのかどうか
  • 前回の比較における右辺

が取れればいけるだろうという感じ。

初めに考えていた方針

  • Comparable をいい感じに魔改造すればいけそう
  • いい感じのオブジェクトTrueClass, FalseClass あたりを継承すれば良さそう

とぼんやり考えていたところ、

  • Comparable を変更しても Integer の挙動が変わらない
  • TrueClass, FalseClass が Singleton なので継承すると new できない

あたりの問題が起こったので、Comparable じゃなく Integer を直接触るようにしつつ、比較結果オブジェクトをそのまま条件式で使えるようにするのは諦めて to_b を呼んで boolean に変換するアプローチに変更。

実装

比較結果を保持するオブジェクトComparisonResult::TrueClass, ComparisonResult::FalseClass を作って、Integer#<, Integer#> がそれを返すように変更。

コードとしてはこんな感じ。

require 'singleton'

module ComparisonResult
  class TrueClass
    def initialize(left, right)
      @left = left
      @right = right
    end

    def to_b
      true
    end

    def <(other)
      @right < other
    end

    def >(other)
      @right > other
    end
  end

  class FalseClass
    include Singleton

    def to_b
      false
    end

    def <(_)
      self
    end

    def >(_)
      self
    end
  end
end

class Integer
  def <(other)
    if (self <=> other) == -1
      ComparisonResult::TrueClass.new(self, other)
    else
      ComparisonResult::FalseClass.instance
    end
  end

  def >(other)
    if (self <=> other) == 1
      ComparisonResult::TrueClass.new(self, other)
    else
      ComparisonResult::FalseClass.instance
    end
  end
end

# <
raise '1 < 2' unless (1 < 2).to_b == true
raise '2 < 1' unless (2 < 1).to_b == false
raise '1 < 2 < 3' unless (1 < 2 < 3).to_b == true
raise '1 < 3 < 2' unless (1 < 3 < 2).to_b == false
raise '2 < 1 < 3' unless (2 < 1 < 3).to_b == false
raise '2 < 3 < 1' unless (2 < 3 < 1).to_b == false
raise '3 < 1 < 2' unless (3 < 1 < 2).to_b == false
raise '3 < 2 < 1' unless (3 < 2 < 1).to_b == false

# >
raise '1 > 2' unless (1 > 2).to_b == false
raise '2 > 1' unless (2 > 1).to_b == true
raise '1 > 2 > 3' unless (1 > 2 > 3).to_b == false
raise '1 > 3 > 2' unless (1 > 3 > 2).to_b == false
raise '2 > 1 > 3' unless (2 > 1 > 3).to_b == false
raise '2 > 3 > 1' unless (2 > 3 > 1).to_b == false
raise '3 > 1 > 2' unless (3 > 1 > 2).to_b == false
raise '3 > 2 > 1' unless (3 > 2 > 1).to_b == true

感想

一応動く何かはできたけど、思っていたことは全然できなかった。

対象クラス・境界値・優先順位あたりはゴリゴリやればいけるはずだけど、 to_b が生えているのが「これはない……」という感じ。

Ruby を書きはじめて結構経ってるけど、Ruby 全然わかってないなということがわかった。