liveBook · Manning

Spaceship operator

As explained in The Well-Grounded Rubyist, Fourth Edition

The spaceship operator is Ruby's three-way comparison operator that returns an ordering result indicating whether its left operand is less than, equal to, or greater than its right operand.

Suppose bids should be ordered by their estimates. Define `Bid#<=>` to compare one bid with another: return a negative number when the left bid is lower, zero when they are equal, and a positive number when the left bid is higher. Ruby conventionally uses `-1`, `0`, and `1` for these results. section 7.6.2

Listing 7.2 Example of a class that mixes in the Comparable module
class Bid
  include Comparable
  attr_accessor :estimate
  def <=>(other_bid)                            1
    if self.estimate < other_bid.estimate
      -1
    elsif self.estimate > other_bid.estimate
      1
    else
      0
    end
  end
end

The `Bid` class spells out all three outcomes and includes `Comparable` to gain operators such as `<` and `>`. section 7.6.2

When the attribute already supports `<=>`, delegate to it instead: `def <=>(other_bid); estimate <=> other_bid.estimate; end`. A class needs its own `<=>` to give its instances an ordering, but it does not need `Comparable` merely to let an array sort them. Include `Comparable` when you also want to write comparisons such as `bid1 < bid2` or use `between?`. section 7.6.2 section 10.8.3

The same comparison logic appears in several settings:

  • Strings use character-value order: `"a" <=> "b"` returns `-1`, while `"a" > "A"` is true. This ordering is distinct from the question of whether two strings are the same object. section 8.1.4
  • `min` and `max` use `<=>` to choose collection elements. Their blocks can supply another criterion; `min_by` and `max_by` compare derived values instead. section 10.4.3
  • `sort` can use each element’s `<=>`, or a two-argument block can supply comparison logic for that call—even when the elements lack a suitable shared ordering. section 10.8 section 10.8.1

Override the ordering for one sort

A painting class can define `price <=> other_painting.price` so an array sorts its paintings by price. To sort those same paintings by year for one operation, use `paintings.sort { |a, b| a.year <=> b.year }`. The block supplies the comparisons without changing the class’s price-based method. section 10.8 section 10.8.1

The three outcomes can encode a rule other than a numeric ranking. In rock–paper–scissors, `<=>` returns zero for matching moves, positive or negative for a win or loss, and raises an error for an unrecognized pairing. With `Comparable` included, the game can then use `>` to select a winner. section 14.5.5

How The Well-Grounded Rubyist, Fourth Edition covers it

How deep it goes
Works through three return outcomes, delegation to attribute comparisons, and the distinction between sorting and including `Comparable`.
Examples
Uses bids and paintings ordered by attributes, string character order, and rock–paper–scissors outcomes.
What it assumes
Assumes familiarity with Ruby classes, methods, and arrays; teaches the three comparison outcomes through concrete implementations.
What you can do after
Build it from scratch by defining `<=>` for a class, then choose whether sorting alone or `Comparable` operators are needed.
How it connects
Connects to `Comparable`, `Enumerable`, `sort`, `min`, `max`, and string comparison.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of The Well-Grounded Rubyist, Fourth Edition. The section links point to the parts of the book it draws on.