Skip to content

Sum types, 2025 variant #559

@Araq

Description

@Araq

Sum types, 2025 variant

We tweak case inside objects to offer:

  • Type safety: field accesses are checked entirely at compile time.
  • Conciseness: The duplication of enum field declarations and their usage in case objects is removed.
  • Conciseness: Constructing an instance of a sum type is shorter: BinaryOpr(a: a, b: b) instead of Node(kind: BinaryOpr, a: a, b: b).
  • Pattern matching.

This year's proposal accomplishes all of the above with a simple syntactic tweak of the existing case objects.
The "discriminator" can now be left out. If it is left out, the entire object is a so called "new style case object".

type
  Node = ref object
    case
    of AddOpr, SubOpr, MulOpr, DivOpr:
      a, b: Node
    of UnaryOpr:
      a: Node
    of Variable:
      name: string
    of Value:
      val: int
    # "shared" fields are directly supported:
    info: LineInfo

The "branch names" (e.g. AddOpr, UnaryOpr, etc.) are used
for construction and pattern matching.

Construction

Construction is done with the syntax Branch(fieldNameA: valueA, fieldNameB: valueB, ...). (A shortcut
for this should be added later, in a separate proposal that affects all object types!)

To access the attached values, pattern matching must be used. This enforces correct access at compile-time.

Pattern matching

The syntax of BranchName(fields...) is used for pattern matching:

proc traverse(n: Node) =
  case n
  of {AddOpr, SubOpr, MulOpr, DivOpr}(a, b):
    traverse a
    traverse b
  of UnaryOpr(a):
    traverse a

  of Variable(name):
    echo name
  of Value(x):
    counter += x

Whether the local variables (a, b, name, x in the above example) are mutable or not depends on the
mutability of expression that is being matched:

  var x = Some(90)
  case x
  of Some(y):
    y = 180 # writes to `x`
  of None:
    echo "no value"

Serialization

As before, traverse the type's AST in a macro to generate the store and load procedures. No special
additions are included in this proposal.

Future directions

  • Allow object construction to leave out the field names: MyObject(1, 2) instead of MyObject(a: 1, b: 2).
  • Allow for a case type to be written outside of an object and allow field names to be left out:
type
  Option[T] =
    case
    of Some: T
    of None: discard
  Either[A, B] =
    case
    of Left: A
    of Right: B

Would be short for:

type
  Option[T] = object
    case
    of Some:
      _: T
    of None:
      _: discard
  Either[A, B] = object
    case
    of Left:
      _: A
    of Right:
      _: B

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions