perl.gg / secret-operators

Spaceship Operator

2026-01-17

Sorting. You'll do a lot of it. And when you do, you'll need this:
<=>
The spaceship operator. Three characters that tell you how two numbers relate to each other.
5 <=> 10 # -1 (less than) 10 <=> 5 # 1 (greater than) 7 <=> 7 # 0 (equal)
Three-way comparison. Returns -1, 0, or 1. Perfect for sort.

Part 1: THE BASICS

Spaceship compares two numbers and returns:
-1 if left is less than right 0 if they're equal 1 if left is greater than right
That's exactly what sort needs:
my @nums = (5, 2, 9, 1, 7); my @sorted = sort { $a <=> $b } @nums; # (1, 2, 5, 7, 9)
Without <=> you get string sorting:
my @wrong = sort @nums; # (1, 2, 5, 7, 9) - looks right... my @nums = (5, 2, 19, 1, 7); my @wrong = sort @nums; # (1, 19, 2, 5, 7) - WRONG! String "19" < "2"

Part 2: ASCENDING VS DESCENDING

Swap $a and $b to reverse the order:
# Ascending (smallest first) my @asc = sort { $a <=> $b } @nums; # Descending (largest first) my @desc = sort { $b <=> $a } @nums;
The trick is remembering which is which. Think of it like subtraction:
$a <=> $b # $a - $b: positive means a is bigger, goes later $b <=> $a # $b - $a: positive means b is bigger, b goes later .--. |o_o | |:_/ | // \ \ (| | ) /'\_ _/`\ \___)=(___/

Part 3: THE STRING VERSION

Spaceship is for numbers. For strings, use cmp:
'apple' cmp 'banana' # -1 (a before b) 'banana' cmp 'apple' # 1 (b after a) 'apple' cmp 'apple' # 0 (equal)
Same return values, but compares as strings.
my @words = qw(cherry apple banana); my @sorted = sort { $a cmp $b } @words; # (apple, banana, cherry)
Actually, for simple string sorting, you can skip the block:
my @sorted = sort @words; # Same result
Perl's default sort is string-based cmp.

Part 4: MULTI-LEVEL SORTING

The real power shows up in complex sorts. Chain comparisons with ||:
my @people = ( { name => 'Alice', age => 30 }, { name => 'Bob', age => 25 }, { name => 'Carol', age => 30 }, ); my @sorted = sort { $a->{age} <=> $b->{age} # First: by age || $a->{name} cmp $b->{name} # Then: by name } @people;
Result:
Bob (25), Alice (30), Carol (30)
The || short-circuits: if age comparison returns 0 (equal), it falls through to the name comparison.

Part 5: THREE-LEVEL AND BEYOND

Keep chaining for more levels:
my @sorted = sort { $a->{department} cmp $b->{department} || $b->{salary} <=> $a->{salary} # Descending salary || $a->{hire_date} cmp $b->{hire_date} || $a->{name} cmp $b->{name} } @employees;
Each level only matters when previous levels are equal.

Part 6: SORTING HASH KEYS BY VALUE

Classic pattern - sort keys by their corresponding values:
my %scores = ( alice => 85, bob => 92, carol => 78, ); # Sort names by score (highest first) my @ranking = sort { $scores{$b} <=> $scores{$a} } keys %scores; # (bob, alice, carol)
The sort block compares values, but you're sorting the keys.

Part 7: CUSTOM COMPARISONS

Spaceship works with any numeric expression:
# Sort by string length my @by_length = sort { length($a) <=> length($b) } @words; # Sort by last character my @by_last = sort { ord(substr($a,-1)) <=> ord(substr($b,-1)) } @words; # Sort IPs numerically my @ips = sort { my @a = split /\./, $a; my @b = split /\./, $b; $a[0] <=> $b[0] || $a[1] <=> $b[1] || $a[2] <=> $b[2] || $a[3] <=> $b[3] } @ip_addresses;

Part 8: THE SCHWARTZIAN TRANSFORM

When sorting requires expensive computation:
# Slow: computes length every comparison my @sorted = sort { length($a) <=> length($b) } @strings; # Fast: compute once, sort, extract my @sorted = map { $_->[0] } # 3. Extract original sort { $a->[1] <=> $b->[1] } # 2. Sort by cached value map { [ $_, length($_) ] } # 1. Cache expensive calc @strings;
Named after Randal Schwartz. The spaceship sorts the middle step.

Part 9: GOTCHAS

Undefined values:
my @nums = (5, undef, 3, undef, 1); my @sorted = sort { $a <=> $b } @nums; # Warnings! undef used in numeric comparison
Fix with defined checks:
my @sorted = sort { ($a // 0) <=> ($b // 0) } @nums;
Non-numeric strings:
my @mixed = ('5', 'ten', '3'); my @sorted = sort { $a <=> $b } @mixed; # 'ten' becomes 0 numerically, with warnings
Spaceship is for numbers. Know your data.

Part 10: VS OTHER LANGUAGES

Many languages stole Perl's spaceship:
Ruby: 5 <=> 10 PHP: 5 <=> 10 (as of PHP 7) Groovy: 5 <=> 10
Python uses cmp() functions differently.

Perl had it first. Version 5, 1994.

Part 11: THE NAME

Look at it:
<=>
It's a spaceship. Viewed from above. The < and > are wings, the = is the body.
<=> / \ < = > \ /
Okay, maybe you need to use your imagination. But it stuck, and now every language that copied it uses the same name.
<=> / \ < = > \ / / \ / \ Three-way comparison since 1994
perl.gg