polymorphism
Traits, enums, and generics — Sema polymorphism without classes or inheritance.
Run it from sema/:
sema check examples/polymorphismSEMA_STRICT=1 sema run examples/polymorphismsema assure examples/polymorphism --grade silverSource
Section titled “Source”src/main.sema
Section titled “src/main.sema”"""Polymorphism worked example (§3.9).
Demonstrates the whole trait trio and a functional pipeline in one runnableprogram on the deterministic mock engine:
- a **struct conforming via the header list** (`Version`),- a **struct conforming out of line** with `impl Ord for Money` (retrofit),- an **enum conforming to the same trait** (`Severity`), defaults grafted too,- a **bounded generic** `maximum[T: Ord]` reused across all three,- **default methods** (`less`/`max2`/`clamp`) derived from one `compare`,- a **supertrait** obligation (`Ord` requires `Eq`),- **trait objects**: a heterogeneous `list[Renderer]` dispatched dynamically, with `is` narrowing (open-world polymorphism — see `plugins.sema`),- **functional style**: immutable bindings, a comprehension, conditional expressions."""
assure gold
from polymorphism.order import Ord, maximumfrom polymorphism.plugins import Renderer, Text, Bullet, Rule, render_all, count_rules
struct Version (Ord): """Ordered by major, then minor.""" major: int minor: int def compare(self, other: Version) -> int: return (self.major - other.major) if self.major != other.major else (self.minor - other.minor)
# Out-of-line conformance: `Money` is declared plainly, then retrofitted to# `Ord` — the same defaults graft on as if listed in the header.struct Money: minor_units: int
impl Ord for Money: def compare(self, other: Money) -> int: return self.minor_units - other.minor_units
# An enum conforms to the very same trait; `less`/`max2`/`clamp` graft onto it.enum Severity (Ord): low medium high def rank(self) -> int: return 0 if self == Severity.low else (1 if self == Severity.medium else 2) def compare(self, other: Severity) -> int !{}: return self.rank() - other.rank()
def latest_version(): ensure result.major == 2 ensure result.minor == 0 return maximum([Version(major=1, minor=4), Version(major=2, minor=0), Version(major=1, minor=9)])
def maximum_money(prices: list[Money]): require len(prices) > 0 return maximum(prices)
def highest_severity(): ensure result == Severity.high return maximum([Severity.low, Severity.high, Severity.medium])
def clamped_version(value: Version, lower: Version, upper: Version) !{}: return value.clamp(lower, upper)
def rendered_report(): ensure result == "Report|----|- alpha|- beta" return render_all([Text(body="Report"), Rule(width=4), Bullet(item="alpha"), Bullet(item="beta")])
def rendered_rule_count(items: list[Renderer]): return count_rules(items)
def default_methods_hold() !{}: ensure result == true lower = Version(major=1, minor=9) upper = Version(major=2, minor=0) return lower.less(upper) and upper.eq(Version(major=2, minor=0))
test "bounded generic selects latest Version 2.0": latest = latest_version() ensure latest.major == 2 ensure latest.minor == 0
test "out-of-line Money impl selects maximum 1799": prices = [Money(minor_units=1299), Money(minor_units=999), Money(minor_units=1799)] ensure maximum_money(prices).minor_units == 1799
test "bounded generic preserves a maximum in the first slot": prices = [Money(minor_units=1799), Money(minor_units=1299), Money(minor_units=999)] ensure maximum(prices).minor_units == 1799
test "enum Ord dispatch selects Severity.high": ensure highest_severity() == Severity.high
test "enum rank distinguishes every variant": ensure Severity.low.rank() == 0 ensure Severity.medium.rank() == 1 ensure Severity.high.rank() == 2
test "default clamp returns Version 2.0": clamped = clamped_version(Version(major=5, minor=0), Version(major=1, minor=0), Version(major=2, minor=0)) ensure clamped.major == 2 ensure clamped.minor == 0
test "trait-object dispatch renders Report": ensure rendered_report() == "Report|----|- alpha|- beta"
test "is narrowing counts one Rule": items = [Text(body="Report"), Rule(width=4), Bullet(item="alpha"), Bullet(item="beta")] ensure rendered_rule_count(items) == 1
test "Ord defaults provide less and eq": ensure default_methods_hold() == true
def main() !{}: ensure result == "latest=2.0 dearest=1799 cheaper=[1299, 999] top=high clamped=2.0 rendered=Report|----|- alpha|- beta rules=1" # Bounded generic over a user struct. latest = latest_version()
# Retrofitted struct + a functional comprehension using a default method. prices = [Money(minor_units=1299), Money(minor_units=999), Money(minor_units=1799)] dearest = maximum_money(prices) cheaper = [p.minor_units for p in prices if p.less(dearest)]
# Enum ordering through the grafted defaults. top = highest_severity()
# `clamp` is a default method that calls other defaults. clamped = clamped_version(latest, latest, latest)
# Trait objects: heterogeneous lists dispatch dynamically inside the wrappers. rendered = rendered_report() rules = rendered_rule_count([Rule(width=len(rendered))])
summary = f"latest={latest.major}.{latest.minor} dearest={dearest.minor_units} cheaper={cheaper} top={top.name} clamped={clamped.major}.{clamped.minor} rendered={rendered} rules={rules}" print(summary) return summarysrc/algebraic.sema
Section titled “src/algebraic.sema”"""Algebraic conformances (§3.9): `std.algebra` traits implemented for real types.
Three conforming types, so the trait laws in `std.algebra` are exercised ratherthan decorative:
- `IntSum` — the integer-addition monoid,- `Text` — the string-concatenation monoid,- `IntBox` — a one-slot container: the identity functor, and a monad.
`sema assure` instantiates every law of each trait — and of its supertraits —against each of these types and property-tests it. A passing law means **nocounterexample was found in the generated cases**; it is not a proof.
## The failure mode, made visible
Add the type below and `sema assure` turns red. This is the output it actuallyproduces, copied from a run, not a paraphrase:
struct BadSum (Monoid): value: int def combine(self, other: BadSum) -> BadSum !{}: return BadSum(value=self.value + other.value + 1) def empty(self) -> BadSum !{}: return BadSum(value=0)
properties: 2/4 held ✗ law Monoid.left_identity for BadSum: falsified — property is false at a = BadSum(value=0) ✗ law Monoid.right_identity for BadSum: falsified — property is false at a = BadSum(value=0) ✓ law Monoid.empty_is_canonical for BadSum: 64 cases ✓ law Semigroup.associativity for BadSum: 64 cases [sema assure] FAIL (bronze)
Note what survives: `x + y + 1` *is* associative and `empty` *is* canonical, sothose two laws still hold. Only the two identity laws are refuted, which isexactly where the defect is. `test "the broken monoid really is broken"` belowpins the same arithmetic, so this comment cannot rot into a lie without a testgoing red."""
from std.algebra import Semigroup, Monoid, Functor, Monad, combine_all, identity
struct IntSum (Monoid): """Integers under addition, identity 0.""" value: int
def combine(self, other: IntSum) -> IntSum: return IntSum(value=self.value + other.value)
def empty(self) -> IntSum: return IntSum(value=0)
struct Text (Monoid): """Strings under concatenation, identity "".""" body: str
def combine(self, other: Text) -> Text: return Text(body=self.body + other.body)
def empty(self) -> Text: return Text(body="")
struct IntBox (Monad): """One integer in a box: the identity functor, and a monad over it.
Endomorphic (`IntBox -> IntBox`) because Sema's erased generics have no higher-kinded types — see the `std.algebra` module docstring.""" item: int
def map(self, f) -> IntBox !{}: return IntBox(item=f(self.item))
def pure(self, value) -> IntBox: return IntBox(item=value)
def bind(self, f) -> IntBox !{}: return f(self.item)
# The Functor and Monad laws that quantify over arbitrary *functions* cannot be# written as `law` clauses — a law quantifies its free identifiers over `Self`,# and assurance generates no function values. They are witnessed here instead:# concrete instances, honestly weaker than the universally quantified law.
test "functor composition holds for two concrete functions": double = (n => n * 2) succ = (n => n + 1) box = IntBox(item=7) ensure box.map(double).map(succ) == box.map((n => succ(double(n))))
test "monad left identity holds for a concrete function and value": to_box = (n => IntBox(item=n * 3)) witness = IntBox(item=0) ensure witness.pure(5).bind(to_box) == to_box(5)
test "monad associativity holds for two concrete functions": f = (n => IntBox(item=n + 1)) g = (n => IntBox(item=n * 10)) box = IntBox(item=4) ensure box.bind(f).bind(g) == box.bind((n => f(n).bind(g)))
test "identity is the functor's fixed point": ensure IntBox(item=9).map(identity) == IntBox(item=9)
test "combine_all folds a semigroup": ensure combine_all([IntSum(value=1), IntSum(value=2), IntSum(value=4)]) == IntSum(value=7) ensure combine_all([Text(body="a"), Text(body="b")]) == Text(body="ab")
test "the broken monoid really is broken": # `combine(x, y) = x + y + 1` is associative, but 0 is not its identity — # exactly what the commented-out `BadSum` above would report. combine = ((x, y) => x + y + 1) ensure combine(combine(1, 2), 3) == combine(1, combine(2, 3)) ensure combine(0, 5) != 5src/order.sema
Section titled “src/order.sema”"""Reusable ordering vocabulary (§3.9).
`Ord` is built on the supertrait `Eq`. A conforming type supplies a singlerequired method — `compare` — and inherits every other operation as a *defaultmethod*: `less`, `eq`, `max2`, and `clamp` are written once here and graftedonto every type that conforms, with the type's own definition winning if itprovides one. `maximum` is a *bounded generic*: it works for any `T` that is`Ord`, using only the trait's surface.
Both traits carry `law` clauses. A law is a universally quantified property overthe implementing type: every free identifier that is not a trait method and doesnot resolve in this module (`a`, `b`, `c` below) ranges over `Self`, and a traitmethod is callable receiver-first, so `compare(a, b)` means `a.compare(b)`.`sema assure` generates values and looks for a counterexample — a law thatpasses means none was found in the generated cases, not that the law is proved."""
pub trait Eq: """Equality by value.""" def eq(self, other: Self) -> bool !{}
law reflexive: eq(a, a) law symmetric: eq(a, b) == eq(b, a)
pub trait Ord (Eq): """A total order. Supply `compare`; the rest is provided for free.""" def compare(self, other: Self) -> int !{}
# An `Ord` impl is held to `Eq`'s laws too, because `Eq` is a supertrait. law compare_reflexive: compare(a, a) == 0 law antisymmetric: compare(a, b) == -compare(b, a) law consistent_with_eq: eq(a, b) == (compare(a, b) == 0) law transitive: (less(a, b) and less(b, c)) ⇒ less(a, c)
# --- default (provided) methods: written once, reused by every conformer --- def less(self, other: Self) -> bool !{}: return self.compare(other) < 0
def eq(self, other: Self) -> bool !{}: return self.compare(other) == 0
def max2(self, other: Self) -> Self !{}: return other if self.less(other) else self
def clamp(self, lo: Self, hi: Self) -> Self !{}: return lo if self.less(lo) else (hi if hi.less(self) else self)
# Bounded generic (§3.9): `[T: Ord]` is erased at runtime, but the bound is the# declared obligation that the element type provides `Ord`, so the body may use# `max2`. Works uniformly for structs and enums that conform.pub def maximum[T: Ord](xs: list[T]) -> T !{}: require len(xs) > 0 mut best = xs[0] for x in xs[1:]: best = best.max2(x) return bestsrc/plugins.sema
Section titled “src/plugins.sema”"""Trait objects (§3.9) — open-world polymorphism.
A `Renderer` trait with several unrelated concrete implementations, heldtogether in one `list[Renderer]` and dispatched dynamically. This is the patternclasses use *inheritance* for (a heterogeneous collection behind an interface),done with traits + dynamic dispatch instead — third parties can add new`Renderer`s without touching a central `enum`. `is` recovers the concrete typewhen open-world code needs it."""
pub trait Renderer: """Anything that can render itself to a line of text.""" def render(self) -> str
pub struct Text (Renderer): body: str def render(self) -> str !{}: return self.body
pub struct Bullet (Renderer): item: str def render(self) -> str !{}: return f"- {self.item}"
pub struct Rule (Renderer): width: int def render(self) -> str !{}: return "-" * self.width
# A heterogeneous collection behind the trait, dispatched dynamically: each# element is a different concrete type, resolved at the call site by its runtime# type. No `enum`, no shared base class.pub def render_all(items: list[Renderer]) -> str !{}: lines = [it.render() for it in items] return "|".join(lines)
# `is` narrowing: open-world code can still ask a value's concrete type or test# trait conformance.pub def count_rules(items: list[Renderer]) -> int !{}: mut n = 0 for it in items: n = n + (1 if it is Rule else 0) return nReflected API
Section titled “Reflected API”algebraic
Section titled “algebraic”Algebraic conformances (§3.9): std.algebra traits implemented for real types.
Three conforming types, so the trait laws in std.algebra are exercised rather
than decorative:
IntSum— the integer-addition monoid,Text— the string-concatenation monoid,IntBox— a one-slot container: the identity functor, and a monad.
sema assure instantiates every law of each trait — and of its supertraits —
against each of these types and property-tests it. A passing law means no
counterexample was found in the generated cases; it is not a proof.
The failure mode, made visible
Section titled “The failure mode, made visible”Add the type below and sema assure turns red. This is the output it actually
produces, copied from a run, not a paraphrase:
struct BadSum (Monoid): value: int def combine(self, other: BadSum) -> BadSum !{}: return BadSum(value=self.value + other.value + 1) def empty(self) -> BadSum !{}: return BadSum(value=0)
properties: 2/4 held ✗ law Monoid.left_identity for BadSum: falsified — property is false at a = BadSum(value=0) ✗ law Monoid.right_identity for BadSum: falsified — property is false at a = BadSum(value=0) ✓ law Monoid.empty_is_canonical for BadSum: 64 cases ✓ law Semigroup.associativity for BadSum: 64 cases[sema assure] FAIL (bronze)Note what survives: x + y + 1 is associative and empty is canonical, so
those two laws still hold. Only the two identity laws are refuted, which is
exactly where the defect is. test "the broken monoid really is broken" below
pins the same arithmetic, so this comment cannot rot into a lie without a test
going red.
struct IntSum
Section titled “struct IntSum”Integers under addition, identity 0.
Fields
| field | type | descriptor |
|---|---|---|
value |
int |
struct Text
Section titled “struct Text”Strings under concatenation, identity “”.
Fields
| field | type | descriptor |
|---|---|---|
body |
str |
struct IntBox
Section titled “struct IntBox”One integer in a box: the identity functor, and a monad over it.
Endomorphic (IntBox -> IntBox) because Sema’s erased generics have no
higher-kinded types — see the std.algebra module docstring.
Fields
| field | type | descriptor |
|---|---|---|
item |
int |
Polymorphism worked example (§3.9).
Demonstrates the whole trait trio and a functional pipeline in one runnable program on the deterministic mock engine:
- a struct conforming via the header list (
Version), - a struct conforming out of line with
impl Ord for Money(retrofit), - an enum conforming to the same trait (
Severity), defaults grafted too, - a bounded generic
maximum[T: Ord]reused across all three, - default methods (
less/max2/clamp) derived from onecompare, - a supertrait obligation (
OrdrequiresEq), - trait objects: a heterogeneous
list[Renderer]dispatched dynamically, withisnarrowing (open-world polymorphism — seeplugins.sema), - functional style: immutable bindings, a comprehension, conditional expressions.
struct Version
Section titled “struct Version”Ordered by major, then minor.
Fields
| field | type | descriptor |
|---|---|---|
major |
int |
|
minor |
int |
struct Money
Section titled “struct Money”Fields
| field | type | descriptor |
|---|---|---|
minor_units |
int |
enum Severity
Section titled “enum Severity”Variants
lowmediumhigh
def latest_version
Section titled “def latest_version”def latest_version()def maximum_money
Section titled “def maximum_money”def maximum_money(prices: list[Money])Parameters
| name | type |
|---|---|
prices |
list[Money] |
def highest_severity
Section titled “def highest_severity”def highest_severity()def clamped_version
Section titled “def clamped_version”def clamped_version(value: Version, lower: Version, upper: Version) !{}Parameters
| name | type |
|---|---|
value |
Version |
lower |
Version |
upper |
Version |
Effects !{}
def rendered_report
Section titled “def rendered_report”def rendered_report()def rendered_rule_count
Section titled “def rendered_rule_count”def rendered_rule_count(items: list[Renderer])Parameters
| name | type |
|---|---|
items |
list[Renderer] |
def default_methods_hold
Section titled “def default_methods_hold”def default_methods_hold() !{}Effects !{}
def main
Section titled “def main”def main() !{}Effects !{}
Reusable ordering vocabulary (§3.9).
Ord is built on the supertrait Eq. A conforming type supplies a single
required method — compare — and inherits every other operation as a default
method: less, eq, max2, and clamp are written once here and grafted
onto every type that conforms, with the type’s own definition winning if it
provides one. maximum is a bounded generic: it works for any T that is
Ord, using only the trait’s surface.
Both traits carry law clauses. A law is a universally quantified property over
the implementing type: every free identifier that is not a trait method and does
not resolve in this module (a, b, c below) ranges over Self, and a trait
method is callable receiver-first, so compare(a, b) means a.compare(b).
sema assure generates values and looks for a counterexample — a law that
passes means none was found in the generated cases, not that the law is proved.
trait Eq
Section titled “trait Eq”Equality by value.
trait Ord
Section titled “trait Ord”A total order. Supply compare; the rest is provided for free.
def maximum
Section titled “def maximum”def maximum[T: Ord](xs: list[T]) -> T !{}Parameters
| name | type |
|---|---|
xs |
list[T] |
Returns T
Effects !{}
plugins
Section titled “plugins”Trait objects (§3.9) — open-world polymorphism.
A Renderer trait with several unrelated concrete implementations, held
together in one list[Renderer] and dispatched dynamically. This is the pattern
classes use inheritance for (a heterogeneous collection behind an interface),
done with traits + dynamic dispatch instead — third parties can add new
Renderers without touching a central enum. is recovers the concrete type
when open-world code needs it.
trait Renderer
Section titled “trait Renderer”Anything that can render itself to a line of text.
struct Text
Section titled “struct Text”Fields
| field | type | descriptor |
|---|---|---|
body |
str |
struct Bullet
Section titled “struct Bullet”Fields
| field | type | descriptor |
|---|---|---|
item |
str |
struct Rule
Section titled “struct Rule”Fields
| field | type | descriptor |
|---|---|---|
width |
int |
def render_all
Section titled “def render_all”def render_all(items: list[Renderer]) -> str !{}Parameters
| name | type |
|---|---|
items |
list[Renderer] |
Returns str
Effects !{}
def count_rules
Section titled “def count_rules”def count_rules(items: list[Renderer]) -> int !{}Parameters
| name | type |
|---|---|
items |
list[Renderer] |
Returns int
Effects !{}