Algebra
Algebraic structures: ordering, and combining values associatively.
Kex traits do not inherit from one another, so concrete types explicitly implement every structure whose laws they satisfy.
Two things live here that you will meet in everyday code. Ordering is what a comparison answers, and it composes — which is how a multi-key sort is written without nested +if+s:
a.age.compare(b.age).thenBy { a.score.compare(b.score) }
Monoid is "these two values combine, and there is a neutral one". Numbers, strings and lists all satisfy it, which is what lets repeat be written once:
"ab".repeat(3) # => "ababab"
[1].repeat(2) # => [1, 1]
5.repeat(3) # => 15
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
LessEqualGreater
Implements Monoid.
Methods
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 — 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
ntimes.
trait Comparable
Types that have a total order.
Implemented by Number, which covers both Integer and Float.
Implemented by Number.
Required methods
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 }
extends Number
More methods of 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
trait Monoid
Types whose values combine associatively and have a neutral element.
Implemented by Integer (addition), String and List (concatenation), Map and both Set flavours (union), and Ordering ("first decision wins").
Implemented by Integer, String, [A], Ordering, Map<K, V>, Queue<A>, Set<A>, UnorderedSet<A>, Stack<A>.
Required methods
An implementor writes these.
identity
identity : This
The neutral element: combining it with any value gives that value back.
combine must be associative and identity neutral on both sides.
Returns the identity
Examples
Integer.identity # => 0
String.identity # => ""
combine
combine(other: This) -> This
Combines this value with other.
Must be associative: a.combine(b).combine(c) and a.combine(b.combine(c)) have to agree.
Parameters
other- the value to combine with
Returns the combined value
Examples
5.combine(3) # => 8
"ab".combine("cd") # => "abcd"
[1].combine([2]) # => [1, 2]
Folding a list of values into one
parts.reduce(String.identity) { |acc, s| acc.combine(s) }
Provided methods
Every implementor gets these, written in terms of the required methods.
repeat
repeat(n: Integer) -> This
Combines this value with itself n times.
Repeating zero times gives the identity — "" for a string, 0 for an integer, [] for a list. A negative count is invalid and ends the program.
Parameters
n- how many copies to combine; must not be negative
Returns the repeated value
Examples
"ab".repeat(3) # => "ababab"
[1].repeat(2) # => [1, 1]
5.repeat(3) # => 15
"x".repeat(0) # => ""
Drawing a separator line
IO.printLine("-".repeat(40))
trait Group
A Monoid in which every value has an inverse that combines with it to give the identity.
Implemented by Integer, where the inverse is negation.
Implemented by Integer.
Required methods
An implementor writes these.
extends Integer
More methods of Integer, added by this module.
identity Monoid Group
0 — the neutral element for addition.
Returns Integer — zero
Examples
Integer.identity # => 0
extends String
More methods of String, added by this module.
Implements Monoid.
extends [A]
More methods of List, added by this module.
Implements Monoid.
identity Monoid
[] — the neutral element for concatenation.
Returns [A] — the empty list
Examples
[1].combine(List.identity) # => [1]
combine Monoid
combine(other: This) -> This
Concatenates other onto this list. Concatenation is the monoid operation for List.
Parameters
other- the list to append
Returns the concatenation
Examples
[1].combine([2, 3]) # => [1, 2, 3]
Flattening a list of lists
groups.reduce([]) { |acc, g| acc.combine(g) }