Types and data
Yona is statically typed with full type inference. You almost never write a type; the compiler reconstructs the most general (Hindley–Milner) type of every expression and rejects ill-typed programs at compile time.
Type inference
Section titled “Type inference”No annotations are required — polymorphism is inferred:
let twice f x = f (f x) in # inferred: (a -> a) -> a -> atwice (\x -> x + 1) 40 # => 42Annotations are optional documentation, written Haskell-style on the line before a definition; the checker verifies the body against them:
scale : Float -> Float -> Floatscale factor x = factor * xType errors are compile-time errors — 1 + "two" never reaches the
runtime. A full account of the checker lives in the
type system guide.
Algebraic data types
Section titled “Algebraic data types”type declares a sum type: a name, optional type parameters, and one or
more constructors separated by |. Constructor fields are types:
type Option a = Some a | Nonetype Result a e = Ok a | Err etype Color = Red | Green | BlueConstruct values by applying the constructor; inspect them with pattern matching:
let found = Some 42 incase found of Some x -> x None -> 0end # => 42Recursive ADTs
Section titled “Recursive ADTs”A constructor field may mention the type being defined:
type List a = Cons a (List a) | Nil
len l = case l of Nil -> 0 Cons _ t -> 1 + len tend
len (Cons 1 (Cons 2 Nil)) # => 2Implementation note. Non-recursive ADTs compile to flat structs
{tag, payload}; recursive ADTs (and ADTs with function-typed fields) are
heap-allocated and reference-counted.
Function-typed fields
Section titled “Function-typed fields”Fields can hold functions, written as an arrow type in parentheses. This is how lazy structures like streams are built — the tail is a thunk:
type Lazy a = Cons a (() -> Lazy a) | Emptytype Reducer a b = MkReducer (a -> b -> a)
ones = Cons 1 (\-> ones)
case ones of Cons x _ -> x # => 1 Empty -> 0endRecords: named fields
Section titled “Records: named fields”A single-constructor ADT can name its fields. Construct with
Name { field = value, … }, read with dot access, and update functionally
— p { age = 31 } returns a copy with one field replaced, leaving p
unchanged:
type Person = Person { name : String, age : Int }
let p = Person { name = "Alice", age = 30 } inlet older = p { age = 31 } in(p.age, older.age, older.name) # => (30, 31, "Alice")Named fields also work in patterns:
case p of Person { name = n } -> n # => "Alice"endConstructors are functions
Section titled “Constructors are functions”Every constructor is a first-class function of its fields. Pass it to higher-order functions or apply it partially like any other function:
type Pair a b = Pair a b
import map from Std\List inmap Some [1, 2, 3] # => [Some 1, Some 2, Some 3]
let point = Pair 1 in # partial application of a 2-field constructorpoint 2 # => Pair 1 2Traits
Section titled “Traits”Traits are Yona’s interfaces (type classes): a set of function signatures a type can implement. This section is an introduction — the full story, including superclass constraints and cross-module export, is in the traits guide.
Declaring a trait
Section titled “Declaring a trait”trait Show a show : a -> StringendA trait may provide default methods — implementations in terms of the other methods, inherited by instances that don’t override them:
trait Eq a eq : a -> a -> Bool neq : a -> a -> Bool neq x y = if eq x y then false else true # defaultendWriting an instance
Section titled “Writing an instance”instance Show Int show x = Std\String::fromInt xend
# Constrained instance: showing an Option a requires Show ainstance Show a => Show (Option a) show opt = case opt of Some x -> "Some(" ++ show x ++ ")" None -> "None" endend
show (Some 42) # => "Some(42)"Static resolution
Section titled “Static resolution”Trait methods are resolved at compile time by monomorphization: each call site compiles the concrete instance directly, so trait dispatch has zero runtime overhead — there are no vtables or dictionaries at runtime.
Auto-derive
Section titled “Auto-derive”The compiler can generate structural instances of Show, Eq, Ord, and
Hash from an ADT’s shape via a deriving clause — postfix or inline:
type Color = Red | Green | Blue deriving Show, Eq, Ord, Hash
type Pair a b = Pair a b deriving (Show, Eq)
show Green # => "Green"show (Pair 1 2) # => "Pair(1, 2)"eq Red Red # => truecompare Red Blue # => -1 (declaration order defines Ord)Semantics of the generated instances:
- Show — nullary constructors print their name; constructors with
fields print
Name(field1, field2, …), fields shown recursively. - Eq — same constructor and all fields equal.
- Ord — constructor declaration order first (first declared is
smallest), then lexicographic left-to-right field comparison; returns
-1,0, or1. - Hash — the constructor tag mixed with field hashes.
Deriving works for polymorphic and recursive ADTs; the generated methods
recurse through fields. Types with function-typed fields can derive Show
(functions print as <function>) but not Eq, Ord, or Hash. Derived
instances are exported across modules like hand-written ones.
Anonymous sum types
Section titled “Anonymous sum types”A value can be typed as one of several alternatives without declaring an
ADT, using | between types; match on the runtime type with typed patterns
(name : Type):
parse : String -> Int | String
case result of (n : Int) -> n (s : String) -> 0endPrelude types
Section titled “Prelude types”These types are available in every program with no import:
type Option a = Some a | None # optional valuetype Result a e = Ok a | Err e # success or errortype Linear a = Linear a # must be consumed exactly oncetype Iterator a = Iterator (() -> Option a) # pull-based streamOptionandResultare the standard ways to express absence and fallibility; see Std\Option and Std\Result.Linearwraps resources (file handles, sockets) that the linearity checker requires you to consume exactly once.Iteratoris the streaming protocol used by file and string iteration — O(1) memory per element.
Full signatures are in the prelude reference.
Where to next
Section titled “Where to next”- Pattern matching — destructuring the data you define.
- Traits guide — superclasses, constrained instances, exports.
- Type system guide — inference internals and status.