kex docs Standard Library

Comparable

Ordering and comparison: what a comparison answers, and the types that have a total order.

Kex traits do not inherit from one another, so concrete types explicitly implement every structure whose laws they satisfy.

The two things you meet in everyday code here. Ordering is what a comparison answers, and it composes. This is how a multi-key sort is written without nested +if+s:

a.age.compare(b.age).thenBy { a.score.compare(b.score) }

Ordering is also a Monoid under "first decision wins", which is what makes comparisons chain with combine; that conformance is declared here, beside the type it belongs to, rather than in algebra.kex with the Monoid trait.

type Ordering

The result of a comparison: Less, Equal or Greater.

Declared here rather than only inside the interpreter so that Ordering, Less, Equal and Greater reach the semantic layer the same way every other stdlib type does (through the collected interfaces) instead of existing solely as native environment bindings the type checker and name resolver cannot see.

Variants

  • Less
  • Equal
  • Greater

Implements Monoid.

Functions

Ordering is a Monoid under "first decision wins", with Equal as identity. That is what makes multi-key comparison compose instead of nesting ifs:

a.name.compare(b.name).combine(a.age.compare(b.age))

combine evaluates its argument eagerly, so the later comparison runs even when the earlier one already decided. Use thenBy when that matters.

identity Monoid

Equal is the neutral element, since an undecided comparison lets the next one decide.

Returns Ordering — Equal

combine Monoid

combine(other: Ordering, other: Ordering) -> Ordering

Returns the first decisive ordering: this one if it is not Equal, otherwise other.

This is what makes multi-key comparison compose. Note that other is evaluated eagerly, so the later comparison runs even when the earlier one has already decided: use thenBy when that matters.

Parameters
other
the tie-breaking ordering

Returns the first decisive ordering

Examples
Equal.combine(Less)     # => Less
Less.combine(Greater)   # => Less

Sorting by surname, then by first name

a.last.compare(b.last).combine(a.first.compare(b.first))

reverse

reverse : Ordering

Returns the opposite ordering: Less becomes Greater, Greater becomes Less, and Equal stays Equal.

The one-word way to turn an ascending comparison into a descending one.

Returns the reversed ordering

Examples
Less.reverse      # => Greater
Greater.reverse   # => Less
Equal.reverse     # => Equal

Sorting newest first

a.created.compare(b.created).reverse

thenBy

thenBy(tieBreaker: Block<Ordering>) -> Ordering

Returns this ordering if it is decisive, otherwise the result of calling tieBreaker.

The short-circuiting form of combine: the block runs only when this comparison is Equal, so a tie-breaker costs nothing once the order is already decided. Prefer it whenever the tie-breaker is more than a field read.

Parameters
tieBreaker
evaluated only on a tie

Returns the first decisive ordering

Examples
Equal.thenBy { 2.compare(1) }   # => Greater
Less.thenBy { 2.compare(1) }    # => Less

Sorting by age, then by an expensive score

a.age.compare(b.age).thenBy { score(a).compare(score(b)) }

From Monoid

repeat
Combines this value with itself n times.

trait Comparable

Types that have a total order.

Implemented by Number, which covers both Integer and Float, and by String.

Implemented by Number, String.

Required functions

An implementor writes these.

compare

compare(other: This) -> Ordering

Compares this value with other and answers Less, Equal or Greater.

== stays independent of this: a type may be equatable without being ordered.

A total order: compare answers Less, Equal or Greater. == stays independent: a type may be Equatable without being ordered.

Parameters
other
the value to compare against

Returns how this value orders against other

Examples
1.compare(2)     # => Less
2.compare(2)     # => Equal
3.compare(2)     # => Greater

Sorting with an explicit comparison

people.sort { |a, b| a.age.compare(b.age) == Less }

Provided functions

Every implementor gets these, written in terms of the required functions.

max

max(other: This) -> This

What every ordered type gets from compare alone. Returns the larger of this value and other; this value when they are equal.

UFCS lets you put the first argument before the dot: max(3, 7) is 3.max(7).

Parameters
other
the value to compare against

Returns the larger of the two

Examples
max(3, 7)             # => 7
1.5.max(2.5)          # => 2.5
"pear".max("apple")   # => "pear"

min

min(other: This) -> This

Returns the smaller of this value and other; this value when they are equal.

Parameters
other
the value to compare against

Returns the smaller of the two

Examples
min(3, 7)             # => 3
"pear".min("apple")   # => "apple"

clamp

clamp(low: This, high: This) -> This

Returns this value limited to the range low to high: low when it is below, high when it is above, and itself otherwise.

Parameters
low
the smallest value allowed
high
the largest value allowed

Returns the value, clamped into low to high

Examples
15.clamp(0, 10)      # => 10
(-3).clamp(0, 10)    # => 0
0.5.clamp(0.0, 1.0)  # => 0.5

<

<(other: This) -> Bool

The ordering operators, from compare alone: whatever is Comparable can be written with < and >, a record as much as a number. The compiler knows them from here, not from a list of its own; numbers and strings still compare natively.

Parameters
other
the value to compare against

Returns whether this value orders before other

Examples
Version { major: 1 } < Version { major: 2 }   # => true

>

>(other: This) -> Bool
Parameters
other
the value to compare against

Returns whether this value orders after other

<=

<=(other: This) -> Bool
Parameters
other
the value to compare against

Returns whether this value orders before other or with it

>=

>=(other: This) -> Bool
Parameters
other
the value to compare against

Returns whether this value orders after other or with it

extends Number

More functions for Number, added by this module.

Implements Comparable.

Number carries the implementation, so Integer and Float both inherit it rather than repeating the same three comparisons. Mixed receivers work because < and > promote across the two (1.compare(1.0) is Equal).

compare Comparable

compare(other: Number) -> Ordering

Compares two numbers, across the Integer/Float boundary.

Mixed receivers work because < and > promote across the two, so 1.compare(1.0) is Equal.

Parameters
other
the number to compare against

Returns how this number orders against other

Examples
1.compare(2)      # => Less
1.compare(1.0)    # => Equal
2.5.compare(2)    # => Greater

extends String

More functions for String, added by this module.

Implements Comparable.

Strings order lexicographically, character by character, by code point — the same order < and > already give them.

compare Comparable

compare(other: String) -> Ordering

Compares this string with other lexicographically, the same order < and > give strings.

Parameters
other
the string to compare against

Returns how this string orders against other

Examples
"apple".compare("banana")   # => Less
"kex".compare("kex")        # => Equal
"cherry".compare("apple")   # => Greater