Sorting: by natural order and by a custom key
The same task in all eight languages: sort a list of integers into ascending order, then sort a list of people by a custom key - their age, descending. Watch how each language expresses the key: a function that extracts the field to compare (Guji, Python, Rust, Raku), a two-argument comparator that returns the ordering (Go, OCaml, Perl), or both. Note too whether sorting returns a new list or rearranges the original in place.
class Person {
has $.name: Str
has $.age: Int
}
sub main() {
@nums = [3, 1, 4, 1, 5, 9, 2, 6]
@asc = @nums.sort()
print("ascending: @asc")
@people = [
Person(name: "Ada", age: 36),
Person(name: "Bo", age: 19),
Person(name: "Cy", age: 28),
]
@oldest = @people.sort_by({ $_.age }).reverse()
for $p in @oldest {
print("{ $p.name } is { $p.age }")
}
}sort() returns a new ordered copy in natural order, and sort_by takes a key function - a topic block { $_.age } whose implicit parameter is $_ (§7.4) - that extracts the value to compare. There is no comparator that returns an ordering; sorting is always by an ascending key, so descending is expressed by sorting then .reverse(), all chained data-first with . (§7.2). Bindings are immutable (§4), so every step yields a fresh list rather than rearranging @people.
package main
import (
"fmt"
"slices"
"sort"
)
type Person struct {
Name string
Age int
}
func main() {
nums := []int{3, 1, 4, 1, 5, 9, 2, 6}
slices.Sort(nums)
fmt.Println("ascending:", nums)
people := []Person{{"Ada", 36}, {"Bo", 19}, {"Cy", 28}}
sort.Slice(people, func(i, j int) bool {
return people[i].Age > people[j].Age
})
for _, p := range people {
fmt.Printf("%s is %d\n", p.Name, p.Age)
}
}slices.Sort (Go 1.21+) sorts an ordered slice in place. For the custom order, sort.Slice takes a less(i, j) predicate that returns whether element i should sort before j; returning people[i].Age > people[j].Age gives a descending sort directly, with no separate reverse step. Both mutate the slice rather than returning a copy; slices.SortFunc is the newer comparator-returning variant.
type person = { name : string; age : int }
let () =
let nums = [3; 1; 4; 1; 5; 9; 2; 6] in
let asc = List.sort compare nums in
Printf.printf "ascending: %s\n"
(String.concat " " (List.map string_of_int asc));
let people =
[ { name = "Ada"; age = 36 };
{ name = "Bo"; age = 19 };
{ name = "Cy"; age = 28 } ]
in
let oldest = List.sort (fun a b -> compare b.age a.age) people in
List.iter (fun p -> Printf.printf "%s is %d\n" p.name p.age) oldestList.sort takes a comparator returning a negative, zero, or positive int, and produces a new sorted list (the input is unchanged). The polymorphic compare gives natural order for the integers; for the people, swapping the arguments - compare b.age a.age - inverts the result and so sorts by age descending. There is no built-in key-function form, so the field extraction lives inside the comparator.
import Data.List (sortBy, sortOn)
import Data.Ord (Down (Down))
import Text.Printf (printf)
data Person = Person { name :: String, age :: Int }
main :: IO ()
main = do
let nums = [3, 1, 4, 1, 5, 9, 2, 6] :: [Int]
putStrLn ("ascending: " ++ show (sortOn id nums))
let people = [ Person "Ada" 36, Person "Bo" 19, Person "Cy" 28 ]
oldest = sortOn (Down . age) people
mapM_ (\p -> printf "%s is %d\n" (name p) (age p)) oldestSorting returns a brand-new list - everything is immutable, so there is no in-place variant. sortOn takes a key function (Schwartzian-transform style, computing each key once) and Data.Ord.Down wraps the key to reverse just that field, giving an age-descending sort with no separate reverse pass. The natural order comes from the element's Ord instance (sort/sortOn id); sortBy is the comparator-returning form when a key function is not enough.
use strict;
use warnings;
my @nums = (3, 1, 4, 1, 5, 9, 2, 6);
my @asc = sort { $a <=> $b } @nums;
print "ascending: @asc\n";
my @people = (
{ name => "Ada", age => 36 },
{ name => "Bo", age => 19 },
{ name => "Cy", age => 28 },
);
my @oldest = sort { $b->{age} <=> $a->{age} } @people;
print "$_->{name} is $_->{age}\n" for @oldest;Perl's sort takes a comparator block that compares the package globals $a and $b; <=> is the numeric three-way operator (use cmp for strings). Default sort is string order, so the explicit $a <=> $b is needed even for the integers. Reversing the operands to $b <=> $a flips it to descending, and dereferencing $b->{age} pulls the key out of each hashref inline. sort returns a new list and leaves the input alone.
class Person { has $.name; has $.age; }
my @nums = 3, 1, 4, 1, 5, 9, 2, 6;
say "ascending: ", @nums.sort.list;
my @people =
Person.new(name => "Ada", age => 36),
Person.new(name => "Bo", age => 19),
Person.new(name => "Cy", age => 28);
my @oldest = @people.sort(*.age).reverse;
say "{.name} is {.age}" for @oldest;Raku's sort accepts either a one-argument key function or a two-argument comparator; here *.age is a Whatever-star closure that extracts the key, and bare .sort uses each element's own value. sort returns a new sorted sequence and never mutates the original, so descending is just .reverse chained on, mirroring Guji. Inside the string, {.age} interpolates a method call on the loop topic $_.
use std::cmp::Reverse;
struct Person {
name: &'static str,
age: u32,
}
fn main() {
let mut nums = [3, 1, 4, 1, 5, 9, 2, 6];
nums.sort();
println!("ascending: {nums:?}");
let mut people = vec![
Person { name: "Ada", age: 36 },
Person { name: "Bo", age: 19 },
Person { name: "Cy", age: 28 },
];
people.sort_by_key(|p| Reverse(p.age));
for p in &people {
println!("{} is {}", p.name, p.age);
}
}Rust sorts slices in place and requires a mut binding to do so. sort() uses the type's natural Ord; sort_by_key takes a key-extracting closure, and wrapping the key in std::cmp::Reverse flips that one field to descending without a separate reverse pass. Both are stable sorts; sort_unstable* variants trade stability for speed, and sort_by takes a full comparator returning an Ordering when a key function is not enough.
from dataclasses import dataclass
@dataclass
class Person:
name: str
age: int
nums = [3, 1, 4, 1, 5, 9, 2, 6]
print("ascending:", sorted(nums))
people = [Person("Ada", 36), Person("Bo", 19), Person("Cy", 28)]
oldest = sorted(people, key=lambda p: p.age, reverse=True)
for p in oldest:
print(f"{p.name} is {p.age}")sorted returns a new list (the in-place list.sort is the mutating sibling). The key= argument is a key function called once per element to extract the value to compare - lambda p: p.age here - and reverse=True flips the result, so descending needs no negation trick. Python dropped the old cmp= comparator in version 3; the key-function style is now the one idiomatic way, with functools.cmp_to_key as the escape hatch.