# Chapter 9: Interfaces and Generics

**What you'll learn:** how to declare an interface and implement it, give an interface default methods, use the standard interfaces that power `==`, `<`, `for` and `with`, overload operators, and write generic functions and classes — including how to constrain a type parameter with `where`.

Two features let one piece of code work with many types. An **interface** names a set of methods; any class that implements them can be used where the interface is expected. **Generics** let a function or class take a type as a parameter, so `Stack[int]` and `Stack[string]` share one implementation and are both fully type-checked.

## Part 1: Interfaces

### Declaring and implementing an interface

```loup
@mode(host.scripted)

interface Shape {
    func area(): float;
    func name(): string;

    func describe(): string {
        return i"{$ this.name() $} with area {$ this.area() $}";
    }
}

class Circle implements Shape {
    pub let radius: float;

    new(radius: float) {
        this.radius = radius;
    }

    pub func area(): float {
        return 3.14159 * this.radius * this.radius;
    }

    pub func name(): string {
        return "circle";
    }
}

class Rectangle implements Shape {
    pub let width: float;
    pub let height: float;

    new(width: float, height: float) {
        this.width = width;
        this.height = height;
    }

    pub func area(): float {
        return this.width * this.height;
    }

    pub func name(): string {
        return "rectangle";
    }
}

func totalArea(shapes: list[Shape]): float {
    var total = 0.0;
    for (shape in shapes) {
        total = total + shape.area();
    }
    return total;
}

let shapes: list[Shape] = [Circle(1.0), Rectangle(3.0, 4.5)];
for (shape in shapes) {
    println(shape.describe());
}
println(i"total: {$ totalArea(shapes) $}");
```

*Listing 9-1: An interface with two required methods and one default method*

```text
circle with area 3.14159
rectangle with area 13.5
total: 16.64159
```

- `interface Shape { … }` lists method signatures. A signature ending in `;` is **required**; a method with a body is a **default** that implementers get for free (§1-11.1).
- `class Circle implements Shape` promises to provide every required method. Interface methods are public, so the implementations are `pub`.
- `list[Shape]` holds any mix of implementers, and `totalArea` works for shapes that do not exist yet. Nothing about `Circle` and `Rectangle` is related except the interface.
- Inside a default method, `this` is the implementing object; call the other methods as `this.area()`.

A class can extend one class and implement several interfaces: `class Thumbnail : Image implements Shape, Closeable { … }`. Writing an interface where the superclass goes, `class Circle : Shape`, means the same as `class Circle implements Shape` (§1-8.3).

### Mistake: leaving out a required method

```loup,does_not_compile
@mode(host.scripted)

interface Shape {
    func area(): float;
    func name(): string;
}

class Square implements Shape {
    pub let side: float;

    new(side: float) {
        this.side = side;
    }

    pub func area(): float {
        return this.side * this.side;
    }
}
```

```text
main.lpscr:8:1: error[E0001]: class 'Square' does not implement all methods of interface 'Shape'; missing: name(): string
```

The message lists every missing method with its signature. (The Language Reference calls this error `E_INTERFACE_NOT_IMPLEMENTED`; Loup 1.25 reports it as `E0001`.) Add `pub func name(): string { return "square"; }`.

### Mistake: a signature that does not match

An implementation must use the interface's parameter and return types exactly:

```loup,does_not_compile
@mode(host.scripted)

interface Shape {
    func area(): float;
}

class Square implements Shape {
    pub let side: int;

    new(side: int) {
        this.side = side;
    }

    pub func area(): int {
        return this.side * this.side;
    }
}
```

```text
main.lpscr:7:1: error[E0001]: method 'area' in class 'Square' has return type 'int', incompatible with interface 'Shape' return type 'float'
```

Return `float`, converting the integer: `return (this.side * this.side) as float;`.

### Overriding a default method

An implementing class may replace a default method by declaring its own:

```loup
#~ @mode(host.scripted)
interface Greeter {
    func name(): string;

    func greet(): string {
        return i"Hello, {$ this.name() $}!";
    }
}

class Friendly implements Greeter {
    pub func name(): string {
        return "Ada";
    }
}

class Formal implements Greeter {
    pub func name(): string {
        return "Dr. Lovelace";
    }

    pub func greet(): string {
        return i"Good afternoon, {$ this.name() $}.";
    }
}

let greeters: list[Greeter] = [Friendly(), Formal()];
for (g in greeters) {
    println(g.greet());
}
```

```text
Hello, Ada!
Good afternoon, Dr. Lovelace.
```

> The Language Reference requires `override` on a method that replaces an interface default (§1-8.3.1). The 1.25 compiler rejects it there instead — `pub override func greet()` in `Formal` reports `E_OVERRIDE_NO_TARGET` because "class `Formal` has no supertype" — so leave `override` off when replacing an interface default. (Replacing a method inherited from a *class* does need `override`; see Chapter 8.)

### Interfaces that extend interfaces

An interface can build on others with `:`. An implementer must provide the methods of the whole chain:

```loup
#~ @mode(host.scripted)
interface Named {
    func name(): string;
}

interface Priced : Named {
    func cents(): int;

    func label(): string {
        return i"{$ this.name() $}: ${$ this.cents() // 100 $}.{$ this.cents() % 100 $}";
    }
}

class Item implements Priced {
    pub let title: string;
    pub let price: int;

    pub func name(): string {
        return this.title;
    }

    pub func cents(): int {
        return this.price;
    }
}

let widget = Item(title = "Widget", price = 1250);
println(widget.label());

let asNamed: Named = widget;
println(asNamed.name());
println(widget is Named);
```

```text
Widget: $12.50
Widget
true
```

`x is I` tests whether a value implements an interface at run time, and `x as I` converts it once you know.

## Standard Interfaces

The language and standard library define interfaces that hook into syntax (§1-11.2):

| Interface | Methods you write | What it enables |
|---|---|---|
| `Equatable[T]` | `operator func ==(other: T): bool` | `==` and `!=` (Chapter 8) |
| `Comparable[T]` | `compareTo(other: T): int` | `<`, `>`, `<=`, `>=` |
| `Hashable` | `hashCode(): int` | use as a `dict` key or `set` element (see the gap below) |
| `Iterable[T]`, `Iterator[T]` | `iter(): Iterator[T]`; `next(): Option[T]` | `for (x in value)` |
| `Closeable` | `close(): void` | `with (x = value) { … }` (Chapter 8) |
| `Finalizable` | `finalize(): void` | cleanup on `del` (Chapter 8) |
| `Dynamic` | provided by `dynamic class` | `obj!{field}` (Chapter 8) |
| `Add`, `Sub`, `Mul`, `Div`, … | `operator func +(…)` and friends | operator overloading (below) |
| `From[E]` | `static func from(err: E): Self` | error conversion with `?` (Chapter 10) |

### `Comparable`: ordering

`compareTo` returns a negative number, zero or a positive number for "less than", "equal" and "greater than":

```loup
#~ @mode(host.scripted)
class Version implements Comparable[Version] {
    pub let major: int;
    pub let minor: int;
    pub let patch: int;

    pub func compareTo(other: Version): int {
        if (this.major != other.major) {
            return this.major - other.major;
        }
        if (this.minor != other.minor) {
            return this.minor - other.minor;
        }
        return this.patch - other.patch;
    }

    pub operator func ==(other: Version): bool {
        return this.compareTo(other) == 0;
    }

    pub func text(): string {
        return i"{$ this.major $}.{$ this.minor $}.{$ this.patch $}";
    }
}

let installed = Version(major = 1, minor = 4, patch = 2);
let required = Version(major = 1, minor = 10, patch = 0);
println(installed < required);
println(installed == Version(major = 1, minor = 4, patch = 2));

var releases = [required, installed, Version(major = 0, minor = 9, patch = 9)];
releases.sort(func(a: Version, b: Version) => a.compareTo(b));
for (v in releases) {
    println(v.text());
}
```

*Listing 9-2: A comparable version number*

```text
true
true
0.9.9
1.4.2
1.10.0
```

Notice that `1.4.2 < 1.10.0`, which a string comparison would get wrong. `list.sort` always takes a comparator function; passing `a.compareTo(b)` sorts in the type's natural order.

Why does `Version` also define `==`? The Language Reference says `Comparable[T]` extends `Equatable[T]` and supplies `==` from `compareTo` (§1-11.2). Loup 1.25 does not do that yet:

```loup,not_desired_behavior
#~ @mode(host.scripted)
class Version implements Comparable[Version] {
    pub let major: int;
    pub let minor: int;

    pub func compareTo(other: Version): int {
        if (this.major != other.major) {
            return this.major - other.major;
        }
        return this.minor - other.minor;
    }
}

let installed = Version(major = 1, minor = 4);
let required = Version(major = 1, minor = 4);
println(installed >= required);
println(installed == required);
```

```text
true
false
```

`>=` uses `compareTo`, but `==` still compares identity. Until the default arrives, give every `Comparable` class an explicit `operator func ==`, as Listing 9-2 does.

### `Iterable` and `Iterator`: making a type work with `for`

A type works with `for` when it implements `Iterable[T]`, whose `iter()` returns a fresh `Iterator[T]`. The iterator's `next()` returns `Some(value)` until it returns `None`:

```loup
#~ @mode(host.scripted)
class CountdownIterator implements Iterator[int] {
    var current: int;

    new(start: int) {
        this.current = start;
    }

    pub modifier func next(): Option[int] {
        if (this.current <= 0) {
            return None;
        }
        let value = this.current;
        this.current = this.current - 1;
        return Some(value);
    }
}

class Countdown implements Iterable[int] {
    pub let start: int;

    new(start: int) {
        this.start = start;
    }

    pub func iter(): Iterator[int] {
        return CountdownIterator(this.start);
    }
}

for (n in Countdown(3)) {
    println(n);
}
println("liftoff");
```

*Listing 9-3: A custom iterable*

```text
3
2
1
liftoff
```

`next()` changes the iterator's `current` field, so it is a `modifier` (Chapter 8); implementing a non-`modifier` interface method with a `modifier` one is allowed. Because `iter()` creates a new iterator each time, a `Countdown` can be looped over more than once. For most cases a generator — `gen func` (Chapter 3) — is shorter.

### `Hashable`

`Hashable` declares `hashCode(): int`. Two values that are `==` must have the same hash code.

> **Known gap.** In Loup 1.25, `dict` keys and `set` elements that are class instances are compared by identity, even when the class implements `Equatable` and `Hashable`: two `Tag("a")` objects are two different keys. Use strings, numbers, enums or structs as keys until this is implemented.

> ⚠ **Planned.** `Renderable` (`func render(ctx: RenderContext): string`) is specified to control how a value appears in interpolated strings and template fences (§1-11.2, Chapter 12). In 1.25 the type `RenderContext` is not defined and interpolation does not call `render`; an object interpolates as `ClassName(field: value, …)`.

## Operator Overloading

Each operator belongs to an interface: `+` to `Add`, `-` to `Sub`, `*` to `Mul`, `/` to `Div`, `%` to `Mod`, `==` to `Equatable`, `a[k]` to `Index`, and so on (§1-5.1). To overload an operator, implement the interface and write `operator func`:

```loup
#~ @mode(host.scripted)
class Vector2 implements Add[Vector2, Vector2], Sub[Vector2, Vector2], Mul[float, Vector2] {
    pub let x: float;
    pub let y: float;

    new(x: float, y: float) {
        this.x = x;
        this.y = y;
    }

    pub operator func +(other: Vector2): Vector2 {
        return Vector2(this.x + other.x, this.y + other.y);
    }

    pub operator func -(other: Vector2): Vector2 {
        return Vector2(this.x - other.x, this.y - other.y);
    }

    pub operator func *(factor: float): Vector2 {
        return Vector2(this.x * factor, this.y * factor);
    }
}

let a = Vector2(1.5, 2.0);
let b = Vector2(3.0, 4.5);
println(a + b);
println(b - a);
println(a * 2.0);

var position = Vector2(0.0, 0.0);
position += a;
position += a;
println(position);
```

*Listing 9-4: Vector arithmetic with operator interfaces*

```text
Vector2(x: 4.5, y: 6.5)
Vector2(x: 1.5, y: 2.5)
Vector2(x: 3.0, y: 4.0)
Vector2(x: 3.0, y: 4.0)
```

The two type arguments are the right-hand operand's type and the result type: `Mul[float, Vector2]` means "`Vector2 * float` gives a `Vector2`". `position += a` is rewritten to `position = position + a`, so it works as soon as `+` does.

### Mistake: forgetting the interface

```loup,does_not_compile
@mode(host.scripted)

class Vector2 {
    pub let x: float;
    pub let y: float;

    new(x: float, y: float) {
        this.x = x;
        this.y = y;
    }

    pub operator func +(other: Vector2): Vector2 {
        return Vector2(this.x + other.x, this.y + other.y);
    }
}

let sum = Vector2(1.0, 2.0) + Vector2(3.0, 4.0);
```

```text
main.lpscr:12:5: error[E_OPERATOR_NO_INTERFACE]: E_OPERATOR_NO_INTERFACE: operator func '+' on 'Vector2' requires `implements Add` on the enclosing type
main.lpscr:17:11: error[E_OPERATOR_MISSING]: type 'Vector2' does not implement 'Add' required for operator '+' (E_OPERATOR_MISSING)
```

The first error is at the declaration, the second at the use. Add `implements Add[Vector2, Vector2]` to the class header.

### Mistake: one type argument

The Language Reference declares `interface Add[T, R = Self]`, so `implements Add[Vector2]` should mean `Add[Vector2, Vector2]`. Loup 1.25 does not support the default yet:

```loup,does_not_compile
@mode(host.scripted)

class Meters implements Add[Meters] {
    pub let value: float;

    pub operator func +(other: Meters): Meters {
        return Meters(value = this.value + other.value);
    }
}
```

```text
main.lpscr:3:25: error[E0001]: type 'Add' expects 2 type argument(s), got 1
main.lpscr:6:5: error[E_OPERATOR_NO_INTERFACE]: E_OPERATOR_NO_INTERFACE: operator func '+' on 'Meters' requires `implements Add` on the enclosing type
```

Write both arguments: `implements Add[Meters, Meters]`.

Some operators cannot be overloaded at all: `and`, `or`, `not`, `??`, `?`, `=`, `.`, `?.`, `is`, `as`, `as?`, `typeof` and `instanceof`.

> ⚠ **Planned.** Unary minus (`interface Neg { operator func -(): Self; }`) is specified but not implemented in 1.25: an `operator func -()` with no parameter is rejected, and a class cannot declare both a unary and a binary `-`.

## Interfaces or Inheritance?

| Use a base class when… | Use an interface when… |
|---|---|
| the types really are kinds of one thing (a `Cat` is an `Animal`) | unrelated types share a capability (a file and a socket can both be `Closeable`) |
| the subclasses share fields and implementation | a type has several independent capabilities |
| the hierarchy is small and stable | other people should be able to plug in their own implementations |

A class has at most one superclass but any number of interfaces, and in Loup 1.25 interfaces are also the more complete feature: the compiler checks that every required method is present, which it does not yet do for abstract classes (Chapter 8).

## Part 2: Generics

### Generic functions

A type parameter goes in square brackets after the name. The compiler infers it from the arguments:

```loup
#~ @mode(host.scripted)
func firstOr[T](items: list[T], fallback: T): T {
    if (items.length() == 0) {
        return fallback;
    }
    return items[0];
}

func swapEnds[T](items: list[T]): list[T] {
    if (items.length() < 2) {
        return items;
    }
    var result = items.clone();
    let first = result[0];
    result[0] = result[-1];
    result[-1] = first;
    return result;
}

println(firstOr([3, 4], 0) + 1);

let noNames: list[string] = [];
println(firstOr(noNames, "nobody"));

println(swapEnds([1, 2, 3, 4]));
println(swapEnds(["a", "b", "c"]));
println(firstOr[int]([7], 0));
```

*Listing 9-5: Generic functions with inferred type arguments*

```text
4
nobody
[4, 2, 3, 1]
["c", "b", "a"]
7
```

`firstOr([3, 4], 0) + 1` type-checks because `T` is `int` at that call, so the result is an `int`. `firstOr[int](…)` gives the type argument explicitly. Note the typed `noNames`: an empty literal `[]` has no element type of its own, so `firstOr([], "nobody")` fails to infer `T`; give the empty list a type first.

### Generic classes

```loup
#~ @mode(host.scripted)
class Stack[T] {
    var items: list[T] = [];

    pub modifier func push(item: T): void {
        this.items.push(item);
    }

    pub modifier func pop(): Option[T] {
        if (this.items.length() == 0) {
            return None;
        }
        return Some(this.items.pop());
    }

    pub func peek(): Option[T] {
        if (this.items.length() == 0) {
            return None;
        }
        return Some(this.items[-1]);
    }

    pub func size(): int {
        return this.items.length();
    }
}

let undo: Stack[string] = Stack();
safe {
    undo.push("type 'hello'");
    undo.push("delete word");
}

var last: Option[string] = None;
safe {
    last = undo.pop();
}
println(last.unwrap_or("nothing to undo"));
println(undo.peek().unwrap_or("empty"));
println(undo.size());
```

*Listing 9-6: A generic stack*

```text
delete word
type 'hello'
1
```

`T` stands for whatever the stack holds. A `Stack[string]` only accepts strings: `undo.push(42)` is a compile error.

When the constructor takes a `T`, the type argument is inferred from the arguments, as for functions: with `class Box[T] { pub let value: T; new(value: T) { this.value = value; } }`, `Box(41)` is a `Box[int]`.

### Mistake: `Stack[int]()` without a type annotation

`Stack` has no constructor parameter to infer `T` from, so you might pass the type argument in the call. In Loup 1.25 that is not enough:

```loup,does_not_compile
@mode(host.scripted)

class Stack[T] {
    var items: list[T] = [];

    pub modifier func push(item: T): void {
        this.items.push(item);
    }
}

let numbers = Stack[int]();
safe {
    numbers.push(1);
}
```

```text
main.lpscr:13:18: error[E0001]: argument 1 of 'push': expected 'T0', found 'int'
```

`T0` is the compiler's name for the unresolved type parameter: the explicit `[int]` on the constructor call is not applied to the new object. Annotate the binding instead, as Listing 9-6 does: `let numbers: Stack[int] = Stack();`.

### Generic structs

Structs and enums take type parameters the same way (Chapters 6 and 7):

```loup
#~ @mode(host.scripted)
struct Pair[A, B] {
    first: A;
    second: B;
}

func swap[A, B](pair: Pair[A, B]): Pair[B, A] {
    return Pair(first = pair.second, second = pair.first);
}

let setting = Pair(first = "port", second = 8080);
let flipped = swap(setting);
println(flipped.first + 1);
println(flipped);
```

```text
8081
Pair(first: 8080, second: "port")
```

### Constraining a type parameter with `where`

Inside a generic function, `T` could be any type, so you can only do with a `T` what every type supports. Comparing two `T`s is not one of those things:

```loup,does_not_compile
@mode(host.scripted)

func largest[T](items: list[T]): T {
    var best = items[0];
    for (item in items) {
        if (item > best) {
            best = item;
        }
    }
    return best;
}
```

```text
main.lpscr:6:13: error[E0001]: type 'T0' has no method 'compareTo'
```

`>` is defined through `Comparable.compareTo`, and nothing says `T` has it. Add a **constraint** with a `where` clause after the return type (§1-11.5):

```loup
#~ @mode(host.scripted)
func largest[T](items: list[T]): T where T: Comparable[T] {
    var best = items[0];
    for (item in items) {
        if (item > best) {
            best = item;
        }
    }
    return best;
}

class Version implements Comparable[Version] {
    pub let major: int;
    pub let minor: int;

    pub func compareTo(other: Version): int {
        if (this.major != other.major) {
            return this.major - other.major;
        }
        return this.minor - other.minor;
    }
}

println(largest([3, 9, 4]));
println(largest(["pear", "apple", "plum"]));

let newest = largest([Version(major = 1, minor = 2), Version(major = 1, minor = 10)]);
println(i"{$ newest.major $}.{$ newest.minor $}");
```

*Listing 9-7: A constrained generic function*

```text
9
plum
1.10
```

Built-in types implement the standard interfaces: `int`, `float` and `string` are all `Comparable`. Several constraints are separated by commas: `where T: Comparable[T], T: Hashable`.

> **Known gaps in generic constraints (Loup 1.25).**
> - The Language Reference also allows the constraint inside the brackets, `func largest[T: Comparable[T]](…)`, and joining bounds with `+` (`T: Comparable[T] + Hashable`, §1-12). The bracket form is accepted but not used — the body still reports `type 'T0' has no method 'compareTo'` — and `+` in a `where` clause does not parse. Use `where` with commas. The same applies to class type parameters: `class SortedBag[T: Comparable[T]]` cannot compare its elements yet, and a `where` clause on a method does not help either.
> - A constraint is not checked where the function is called. The Language Reference makes a call with a type that does not satisfy it a compile error (`E_WHERE_BOUND_NOT_MET`); in 1.25 the call compiles and fails when the missing method is reached, as shown below.
> - A value of a type parameter cannot be passed where `Any` is expected: `str(value)`, `println(value)` or `i"{$ value $}"` with `value: T` report `expected 'Any', found 'T0'`. Return the value to code that knows its type, or format it there.

What an unmet constraint does today:

```loup,throws
#~ @mode(host.scripted)
func largest[T](items: list[T]): T where T: Comparable[T] {
    var best = items[0];
    for (item in items) {
        if (item > best) {
            best = item;
        }
    }
    return best;
}

class Crate {
    pub let kilos: int;
}

let heaviest = largest([Crate(kilos = 10), Crate(kilos = 30)]);
println(heaviest.kilos);
```

```text
Uncaught RuntimeError: internal interpreter limitation: unsupported instruction: CallVirtual compareTo on user class Crate — method not found
```

`Crate` does not implement `Comparable`, so there is no `compareTo` to call. Implement `Comparable[Crate]` (comparing `kilos`) and the call works.

## A Complete Example: A Small Data Pipeline

Interfaces and generics together give you code that you extend by adding classes, not by editing it:

```loup
#~ @mode(host.scripted)
interface Source[T] {
    func name(): string;
    func rows(): list[T];
}

interface Step[T] {
    func apply(rows: list[T]): list[T];
}

interface Sink[T] {
    func write(rows: list[T]): int;
}

func runPipeline[T](source: Source[T], step: Step[T], sink: Sink[T]): int {
    let rows = source.rows();
    let kept = step.apply(rows);
    return sink.write(kept);
}

class LinesSource implements Source[string] {
    pub let text: string;

    pub func name(): string {
        return "lines";
    }

    pub func rows(): list[string] {
        return this.text.split("\n");
    }
}

class DropBlank implements Step[string] {
    pub func apply(rows: list[string]): list[string] {
        return rows.filter(func(row: string) => row.trim().length() > 0);
    }
}

class PrintSink implements Sink[string] {
    pub let prefix: string;

    pub func write(rows: list[string]): int {
        for (row in rows) {
            println(this.prefix + row);
        }
        return rows.length();
    }
}

let written = runPipeline(
    LinesSource(text = "alpha\n\nbeta\n   \ngamma"),
    DropBlank(),
    PrintSink(prefix = "> "),
);
println(i"{$ written $} rows written");
```

*Listing 9-8: A pipeline of pluggable stages*

```text
> alpha
> beta
> gamma
3 rows written
```

`runPipeline` knows nothing about lines, blank rows or printing. A CSV source, a deduplicating step or a file sink are new classes that implement `Source[T]`, `Step[T]` or `Sink[T]`; `runPipeline` does not change.

## Summary

- An interface lists required methods (ending in `;`) and default methods (with a body); a class `implements` it and must provide every required method with the exact signature.
- In 1.25, replace an interface default without `override`; replace a superclass method with `override`.
- Interfaces can extend interfaces; `x is I` and `x as I` test and convert at run time.
- `Comparable` gives `<`, `>`, `<=`, `>=`; define `==` explicitly as well. `Iterable`/`Iterator` make a type work with `for`; `Closeable` works with `with`.
- Operators are overloaded by implementing their interface with both type arguments (`Add[Other, Result]`) and writing `operator func`.
- Generic functions and classes take type parameters in square brackets; arguments are inferred where possible. Annotate the binding when a constructor has nothing to infer from.
- Constrain type parameters with `where T: Interface[T]`; in 1.25 the bracket form is not used and unmet constraints fail at run time.
- Class-instance keys in `dict` and `set`, `Renderable` and unary `-` overloading are not implemented yet.

## Try It Yourself

1. Add a `Triangle` to Listing 9-1 (Heron's formula needs `sqrt` from `core.math`) and check that `totalArea` needs no change.
2. Implement `Comparable[Version]` on a version with a pre-release tag (`1.4.0-beta` sorts before `1.4.0`), sort a list, and print it.
3. Write `class Queue[T]` with `modifier` methods `enqueue` and `dequeue(): Option[T]`, and use it with strings and with `Version` objects.
4. Write `func countAbove[T](items: list[T], limit: T): int where T: Comparable[T]` and call it with numbers and with strings.
5. Add a `Step[string]` that upper-cases every row (`toUpperCase()`) and chain two steps in Listing 9-8 by writing a `Chain[T]` step that holds two other steps.
