Types

Every type in Oneway is built by composing two operators — | for "or", & for "and" — over a small core of primitives.

Naming

  • Types and traits: PascalCase
  • Methods: camelCase

The case difference distinguishes a method from a trait implementation declared on the same type:

Type.print  // method
Type.Print  // implementation of the `Print` trait

Unions (|)

A union expresses "this or that":

Bit  = Off | On
Bool = False | True
Ord  = Equal | Greater | Less

Variants must be listed in alphabetical order. There is no separate enum keyword.

Products (&)

A product expresses "this and that". A value of the resulting type has all of its components:

User = Birthday & Username

Components must be in alphabetical order. There is no separate struct keyword.

Field Access

A product's components are addressed by their type name:

user.Birthday
user.Username

For repeated components or anonymous sequences, use 1-based positional indices:

Byte = Bit[8]

byte.1   // first Bit
byte.2   // second Bit

Newtypes

Aliasing a type creates a distinct new type that wraps the original:

Birthday = String
Username = String

Birthday and Username cannot be used interchangeably. They share storage, but they are different types — which is exactly the point. See Philosophy on why types are the documentation.

Fixed and Unbounded Repetition

For a fixed count of the same type, use Type[N]:

Byte = Bit[8]

For unbounded sequences, use ...Type:

Bytes = ...Byte

Higher-level types like Int, Float, and String are defined from Byte / Bytes.

Generics

Type parameters use angle brackets:

List<T>
Option<T>
Result<T, E>
Map<String, Int>

Constraints on type parameters use :, naming a trait the parameter must implement:

List.print = <T: Print>() -> Noop {
    ...
}

Singleton Types

A type with no underlying composition has exactly one value, referenced by writing the type name itself:

main = () -> Noop {
    Noop
}

Noop in return position is the type; Noop in expression position is its sole value. No constructor call is needed (and would not work — there is no data to pass).

Recursive Types

Recursive type definitions are allowed and boxed automatically:

Branch = Left & Right & Value
Left   = Tree
Right  = Tree
Tree   = Branch | Leaf
Value  = Int

There is no user-visible Box<T>. The transpiler chooses an indirection scheme; it is never spelled out in source.

Type Inference

There is none. Every type must be explicitly written. If a function declares it returns Result<T, Err> but no Err ever flows through, that is a compile-time error — declared types must match inferred shape exactly.