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

Reply via email to