<!-- Sema documentation — Traits, Enums & Generics
     Sema is a new AI-native, neurosymbolic programming language most models
     have not been trained on. Full corpus: https://sema.49.12.246.95.sslip.io/llms-full.txt
     Install language support: https://sema.49.12.246.95.sslip.io/install-skill/ -->

# Traits, Enums & Generics

> Sema's answer to classes — traits with default methods, algebraic enums with payloads, bounded generics, and trait objects. No inheritance.

Sema has **no classes and no inheritance, by design.** Everything class hierarchies
are used for — shared behavior, polymorphism, heterogeneous collections — is done
with **traits** (interfaces with default methods and laws) and **algebraic data
types** (`struct` products, `enum` sums), Rust-style. This page is the core of what
makes Sema's object model different, and every snippet is drawn from the runnable
[`polymorphism` example](/reference/examples-api/polymorphism/).

## Why no classes?

Class inheritance conflates several distinct jobs (interface, code reuse, subtype
polymorphism, open extension) into one mechanism, and the dynamic parts
(metaclasses, monkey-patching, MRO surprises) break static compilation. Sema
splits the jobs apart into orthogonal, checkable constructs:

| Job | Sema mechanism |
|---|---|
| Declare required behavior | `trait` with bodyless `def`s |
| Reuse implementation | `trait` **default methods** |
| Enforce algebraic laws | `trait` `law` clauses (verified) |
| Open-world polymorphism | **trait objects** (`list[Renderer]`) |
| Closed-world exhaustiveness | `enum` + `match` |
| Parametric reuse | **generics** `[T]` with bounds |

## Methods on structs and enums

`struct` and `enum` bodies admit `def` — methods are ordinary functions with an
implicit typed `self`. `mut def` marks methods that mutate `self` and is legal
only through `mut` bindings. Associated constants are `def`-less bindings in the
type body.

```sema
struct Money (Mergeable, Semantic):
    currency: Currency
    minor_units: i64
    invariant minor_units >= 0
    def combine(self, other: Money) -> Money !{}:
        require self.currency == other.currency
        return Money(currency=self.currency, minor_units=self.minor_units + other.minor_units)
```

## Traits: required signatures, default methods, laws

A **trait** declares *required signatures* (bodyless `def`s), optional *default
methods* (`def`s with a body), and *laws as contracts* (making trait obligations
first-class verification targets, not documentation):

```sema
trait Mergeable:
    sem "Types with an associative combine, safe for unordered parallel reduction"
    def combine(self, other: Self) -> Self !{}                     # required
    law associative: combine(combine(a, b), c) == combine(a, combine(b, c))
```

The `law associative` clause feeds the property engine — an `unordered` parallel
reduce demands `Mergeable` with a *killed-mutant record* for `associative`, which
is what "proved associative" concretely means. See
[/neurosymbolic/verification/](/neurosymbolic/verification/).

### Default (provided) methods — reuse without inheritance

A trait method that carries a body is a **default**: written once, grafted onto
every conforming type that does not override it (the type's own definition always
wins). This is Sema's answer to implementation reuse — the legitimate core of what
inheritance is used for — without a class hierarchy. A single required method can
seed an entire interface:

```sema
pub trait Eq:
    """Equality by value."""
    def eq(self, other: Self) -> bool

pub trait Ord (Eq):                              # Eq is a *supertrait* of Ord
    """A total order. Supply `compare`; the rest is provided for free."""
    def compare(self, other: Self) -> int        # the one required method

    # --- default methods: written once, reused by every conformer ---
    def less(self, other: Self) -> bool:
        return self.compare(other) < 0
    def eq(self, other: Self) -> bool:           # satisfies the Eq obligation
        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)
```

### Supertraits

`trait Ord (Eq):` declares that every `Ord` type is also an `Eq` type. The
supertrait's obligations flow down (a conformer must satisfy `Eq`'s required
methods too, unless a default in the chain supplies them) and its defaults are
available to `Ord`'s defaults. Supertrait sets meet transitively; cycles are
rejected.

## Conforming to a trait — two forms

A type conforms in its header list, or out of line with `impl Trait for Type:`.
The out-of-line form retrofits conformance onto existing (even prelude or FFI)
types:

```sema
# 1. Header-list conformance — supply `compare`, inherit `less`/`eq`/`max2`/`clamp`.
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)

# 2. Out-of-line conformance — `Money` is declared plainly, then retrofitted.
struct Money:
    minor_units: int

impl Ord for Money:
    def compare(self, other: Money) -> int:
        return self.minor_units - other.minor_units
```

**Conformance is checked.** A type that declares a user-defined trait but leaves a
required method unimplemented — its own or a supertrait's, and not covered by a
default — is a hard `sema check` error naming the missing methods.

:::note[Coherence]
At most one `impl` per (trait, type) pair, with the orphan rule as in Rust.
Blanket implementations and specialization are excluded from v0.1.
:::

## Enums: sum types with payloads

An `enum` is a sum type — a value is exactly one of its variants, and variants may
carry payloads:

```sema
enum Escalation:
    none
    notify(channel: str)
    page(oncall: str, deadline: Duration)

match esc:
    case Escalation.page(oncall, deadline): dispatch(oncall, deadline)
    case Escalation.notify(channel):        post(channel)
    case Escalation.none:                   pass
```

Variant payloads destructure positionally or by name in `case` patterns, and enum
`match` is **exhaustiveness-checked** (see [Pattern
Matching](/language/pattern-matching/)). Enums can conform to traits and carry
methods just like structs — the very same `Ord` trait grafts its defaults onto an
enum:

```sema
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()
```

The inline form (`sentiment: enum Sentiment: pos | neg | neutral`) is sugar for a
standalone payload-free declaration.

## Generics and bounds

Type parameters are written `[T, U]` on `def`/`struct`/`enum`/`impl`. A parameter
may name the traits it must satisfy — a **bound** — and multiple bounds join with
`+` (`[T: Eq + Ord]`):

```sema
struct Box[T]:
    value: T

# Bounded generic: works for any T that is Ord, using only the trait's surface.
pub def maximum[T: Ord](xs: list[T]) -> T !{}:
    mut best = xs[0]
    for x in xs:
        best = best.max2(x)     # `max2` is available because T is bounded by Ord
    return best
```

One `maximum` works uniformly across a user struct, a retrofitted struct, and an
enum:

```sema
latest  = maximum([Version(major=1, minor=4), Version(major=2, minor=0)])
dearest = maximum([Money(minor_units=1299), Money(minor_units=999)])
top     = maximum([Severity.low, Severity.high, Severity.medium])
```

**Generics are erased at runtime** — the interpreter is uniformly typed, so
generics add expressiveness and documentation without a second type-checking
regime. The bound is the declared obligation, surfaced to `sema check`,
reflection, and docs. It is **enforced at call sites** where the argument's
concrete type is evident: passing a `Blob` that does not conform to `Ord` to
`maximum` is a `sema check` error. Where the checker cannot resolve the type, the
bound is left to runtime dispatch.

## Trait objects — open-world polymorphism

A trait name used in *type position* — `x: Shape`, `list[Shape]`, `-> Shape` — is
a **trait object type**: any value conforming to the trait, dispatched by its
runtime type. This is how you get a heterogeneous collection behind one interface
— the thing class hierarchies use inheritance for — and third parties can add new
conformers without touching a central `enum`:

```sema
pub trait Renderer:
    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

# One list, three concrete types, dispatched dynamically at each call site.
pub def render_all(items: list[Renderer]) -> str !{}:
    return "|".join([it.render() for it in items])
```

Method calls on a trait-object value resolve against the concrete runtime type —
the same dispatch as `x.method()` everywhere. A call to a method the concrete type
does not provide is a typed error at the call site. **Trait-object slots are
conformance-checked**: a value whose concrete type is evident and does not conform
(a `Blob` in a `list[Shape]` argument, a `Shape`-typed binding, or a `-> Shape`
return) is a `sema check` error before the program runs.

### The `is` test — narrowing for open-world code

`value is Type` and `value is not Type` return `bool`: true iff the value's
runtime type *is* that concrete type, or *conforms to* that trait (transitively
through supertraits). Because value semantics make identity comparison meaningless,
`is` is repurposed as the type/conformance test — the narrowing escape hatch when
open-world code needs to recover a concrete type:

```sema
pub def count_rules(items: list[Renderer]) -> int !{}:
    mut n = 0
    for it in items:
        n = n + (1 if it is Rule else 0)     # `is` narrows to a concrete type
    return n

if x is int: ...      # works on built-in types too
```

## Open-world vs closed-world: which to use

| You want… | Use |
|---|---|
| A fixed set of cases, exhaustively handled | `enum` + `match` |
| An open set — third parties add types later | trait objects (`list[Trait]`) |

They are duals. Enums close the set and give you exhaustiveness; trait objects
keep it open and give you extensibility.

## Core prelude traits

`Semantic` (embedding + [canonical
flattening](/neurosymbolic/semantic-values/)), `Iterable`/`Iterator`, `Hashable`,
`Eq`/`Ord`, and `Mergeable` are the traits the language and stdlib build on. The
`Iterable` trait is the single iteration protocol — `for x in xs:`,
comprehensions, `parallel`, and stream consumption all take `Iterable` operands.

## Failure modes

- **Missing required method** (own or supertrait, no default) → `sema check`
  error naming the methods.
- **Non-conforming value in a trait-object slot** (evident type) → `sema check`
  error.
- **Calling an unbounded generic's method the bound doesn't provide** → the body
  cannot use it (bound is the obligation).
- **Two `impl`s for the same (trait, type)** → coherence error.
- **Supertrait cycle** → rejected.

## See also

- [Pattern Matching](/language/pattern-matching/) — how to destructure enums and structs.
- [Types](/language/types/) — the scalar and collection types traits build on.
- [`polymorphism` example](/reference/examples-api/polymorphism/) — the full runnable source.
- [Semantic values](/neurosymbolic/semantic-values/) — how the `Semantic` trait works.
