Nate Dalo

February 5, 2025

Ruby's "spaceship" operator <=> and the Comparable mixin

Ruby's "spaceship" operator (<=>), AKA the three-way comparison operator, lets you compare two values. If the value on the left-hand side is less than the value on the right-hand side, it will return -1; if the value is greater, it will return 1; and if the values are the same, it will return 0:

1 <=> 2   # => -1
2 <=> 1   # => 1
1 <=> 1   # => 0

When you combine this concept with the Comparable mixin, you can unlock some nice behavior in your own classes.

Let's jump right into an example:

class Student
  include Comparable
  attr_reader :first_name, :last_name, :grade

  def initialize(first_name, last_name, grade)
    @first_name = first_name
    @last_name = last_name
    @grade = grade
  end

  def <=>(other_student)
    grade <=> other_student.grade
  end
end

Implementing the spaceship operator allows us to do this:

s1 = Student.new("John", "Smith", 89)
s2 = Student.new("Jane", "Smith", 92)

s1 <=> s2   # => -1

However, since we included the Comparable module and implemented the spaceship operator, we automatically gain access to the following:

<, <=, >, >=, ==, between?, clamp

And if you put these in a container that implements Enumerable, you also unlock things like:

sort, min, max, minmax, etc.

Let's play around with some of this:

s1 = Student.new("John", "Smith", 89)
s2 = Student.new("Jane", "Smith", 92)
s3 = Student.new("Bob", "Liu", 93)
s4 = Student.new("Ana", "Lee", 74)

students = [s1, s2, s3, s4]
students.sort

# => [
    #<Student:0x00000001531aaa28 @first_name="Ana", @last_name="Lee", @grade=74>,
    #<Student:0x00000001531aac30 @first_name="John", @last_name="Smith", @grade=89>,
    #<Student:0x00000001531aab68 @first_name="Jane", @last_name="Smith", @grade=92>,
    #<Student:0x00000001531aaac8 @first_name="Bob", @last_name="Liu", @grade=93>
]

s1.between?(s4, s3) # 89 is between 74 and 93
# => true

s1 > s2 # 89 is smaller than 92
# => false

students.minmax # returns the min and the max

# => [
    #<Student:0x00000001531aaa28 @first_name="Ana", @last_name="Lee", @grade=74>,
    #<Student:0x00000001531aaac8 @first_name="Bob", @last_name="Liu", @grade=93>
]

In summary, by implementing the <=> operator and incorporating the Comparable mixin, you gain access to a variety of useful features!