Happy New Year! I'm posting this here as it's not refined enough for an RFC, yet. Feedback appreciated.
# Sum types, 2024 variant In this proposal it is a simple extension to the existing `enum` construct: type Option[T] = enum of None: discard of Some: T Either[A, B] = enum of Le: A of Ri: T BinaryNode = object a, b: ref Node UnaryNode = object a: ref Node Node = enum of BinaryOpr: BinaryNode of UnaryOpr: UnaryNode of Variable: string of Value: int Run The new form of an `enum` that uses an `of` syntax is called the "sum enum". Constructing an enum branch uses the branch name plus its payload in parenthesis. However, `BinaryOpr(BinaryNode(a: x, b: y))` can be shortened to `BinaryOpr(a: x, b: y)`, an analogous shortcut exists for tuples. To access the attached values, pattern matching must be used. This enforces correct access at compile-time. ### Simple pattern matching The syntax `of Branch as x` can be used to unpack the sum type to `x`. proc traverse(n: ref Node) = case n[] of BinaryOpr as x: traverse x.a traverse x.b of UnaryOpr as x: traverse x.a of Variable as name: echo name of Value as x: counter += x Run `of Branch as variable` is sugar for `of Branch(let variable)`. `of Branch(var variable)` is also available allowing mutations to `variable` to write through to the underlying enum object. The syntax `of Branch as x` can later be naturally extended to if statements: `if n of BinaryOpr as x` or `if n of Some(var n)`. ### More complex pattern matching Proposed syntax: case n of BinaryOpr(var a, UnaryOpr(let b)) if a == b: a = ... # can write-through Run ### Serialization There are two new macros that can traverse enums: 1. `constructEnum` takes in a type `T` and an expression in order to construct an enum type `T`. 2. `unpackEnum` takes in a value of an enum type and an expression in order to traverse the data structure. For example: type BinaryNode = object a, b: ref Node UnaryNode = object a: ref Node Node = enum of BinaryOpr: BinaryNode of UnaryOpr: UnaryNode of Variable: string of Value: int proc store(f: var File; x: int) = f.write x # atom proc store(f: var File; r: ref Node) = store r[] # deref `ref` proc store[T: object](f: var File; x: T) = # known Nim pattern: for y in fields(x): store y proc store[T: enum](f: var File; x: T) = unpack x, f.store, f.store # `unpack` is expanded into a case statement: # `case x[] # of Val as val: f.store(kind); f.store(val)` # ... proc load[T: enum](f: var File; x: typedesc[T]): T = let tmp = f.load[:IntegralType(T)]() result = constructEnum(T, tmp, f.load) # constructEnum is expanded into a case statement: # `case tmp # of Val: Val(f.load[:BranchType]())` # ... Run