Values & Types
Primitive types
| Type | Examples |
|---|---|
int | 0, 42, -7 |
float | 3.14, -0.5 |
bool | true, false |
str | "hello", "world" |
() | () (unit) |
never | uninhabited; the type of expressions that don't return |
Tuples
(1, "hello", true)Tuples are positional products. Their elements can be destructured with patterns, e.g. my (a, b, c) = triple.
Lists
[1, 2, 3]A list of 'a values has type list 'a. Index with xs[i] (the Index role on list 'a).
Records
{ x: 3, y: 4 }
{ ..base, x: 3 }
{ x: 3, ..rest }Anonymous products with named fields. Field access uses .field.
Record patterns can make fields optional with defaults:
my width_or_default { width = 1 } = width
my keep_rest { ..rest, width = 1 } = restThe type printer marks optional record fields with ?, for example {width?: α} -> α | int. A spread pattern that keeps the rest may render an intersection such as α & {width?: ⊤}.
Optional
just 42
nilopt 'a is the standard library type enum opt 'a = nil | just 'a. The prelude reexports both the type and its variants, so ordinary code writes opt, just, and nil without std::opt:: or opt:: qualification.
Result
ok 1
err "bad"result 'ok 'err is the standard library type for fallible computations. The prelude reexports result, ok, and err; qualify them only when a local name would be ambiguous.
Ranges
0..<10 // 0 to 9 (exclusive)
0..10 // 0 to 10 (inclusive)
0.. // 0 upward (unbounded).., ..<, <.., <..< are range operators in std::range. Ranges implement Fold, so they work with for x in r: and the nondeterminism each collector.
Type variables
Type variables use the 'a prefix. They appear directly inside type annotations; there is no separate type-parameter binder on ordinary function bindings.
my id(x: 'a): 'a = xType inference fills in type variables automatically in most cases — annotations are only needed for clarity or for resolving ambiguity.
Inline ascription with as
Binding ascription (my x: T = e) and argument ascription (my f(x: T) = ...) both use :. To ascribe a type to a sub-expression, use as:
([] as list int)
(nil as opt str)
my n = (read_input() as int) + 1Inline : is reserved for colon application (f: x), so an expression-level (e : T) would clash with that form. as is the form to reach for when you want to fix a polymorphic value's type without introducing a my binding.
Function and computation types
int -> int
() -> [console] str
[console] strA -> B is a function type. If the result may perform effects, the result side can carry an effect row:
() -> [console] str[console] str by itself is an effectful computation value: it may perform the console effect and returns a str. This form is useful for handlers and control abstractions:
our run_console(action: [console] 'a): 'a = catch action:
console::read(), k -> run_console(k "42")The type printer may also show an effect row on a function argument, e.g. α [io; β] -> [β] α. That means the argument is an effectful computation. Source annotations usually write that argument type with a concrete value type or a type variable, such as [io; e] 'a.
_ can appear in annotations as a placeholder to ask inference to fill a type hole. It is not a type constructor and should not be read as part of the underlying type syntax.
Effect rows
Effect rows appear in type signatures with [...]:
[console; e] str
() -> [console; e] strA row lists the named effects, optionally followed by a row variable such as ; e to indicate "any other effects." A wildcard row such as [_] is an annotation placeholder for inference; it is not the canonical form of an effect row type.
Role constraints
Constraints are introduced with where:
my twice(x: 'a) =
where 'a: Add
x.add xThe same form works inside a role/impl body and inside a binding body. where clauses constrain a type variable to implement one or more roles.
Inferred unions and intersections
Yulang's inferred types may contain unions and intersections even though there is not yet a stable source annotation syntax for writing them directly.
α | int
α & {width?: ⊤}You will usually see these in the Types pane when a branch, default value, or pattern spread leaves more than one possible shape. Adding an annotation can often collapse the displayed type to the intended public shape.
See also
- Effects — declaring, calling, and handling effects.
- Type Inference Theory — subtyping, effect rows, and handler hygiene.