Nanyx

Traits and typeclasses

Structural record types for is-a abstractions, contexts for typeclass-style instances, and instance synthesis.

module traitsAndTypeclasses

---
Some abstractions are implemented directly by a type. This is an "is a"
relationship, and in Nanyx it is a structural record type, possibly
intersected with others.
---

type Iterator(a) = (moveNext: () -> #some(a) | #done)
type Seq(a) = (getIterator: () -> Iterator(a))
type Collection(a) = Seq(a) & (size: () -> int)
type Set(a) = Collection(a) & (contains: a -> bool)
type SortedSet(a) = Set(a) & (min: a, max: a)

---
Others are implemented by a separate instance: a "there exists a ... for ..."
relationship. In Nanyx that is a context. Crucially, there can be several
instances for the same type: int is a monoid under addition, under
multiplication, under bitwise and, and so on.
---

context Monoid(a) = (
  empty: a
  combine: (a, a) -> a
)

def additiveMonoid: Monoid(int) = (empty = 0, combine = { x, y -> x + y })
def multiplicativeMonoid: Monoid(int) = (empty = 1, combine = { x, y -> x * y })
def bitwiseAndMonoid: Monoid(int) = (empty = -1, combine = { x, y -> Int.bitAnd(x, y) })
def bitwiseOrMonoid: Monoid(int) = (empty = 0, combine = { x, y -> Int.bitOr(x, y) })

def reduce: [Monoid(a)] list(a) -> a = { xs ->
  xs \fold(empty) { acc, x -> combine(acc, x) }
}

def product: list(int) -> int = { xs -> use multiplicativeMonoid in reduce(xs) }
def total: list(int) -> int = { xs -> use additiveMonoid in reduce(xs) }

---
When a type has attached definitions that match a context's members, the
compiler synthesizes the instance, so no declaration is needed.
---

type Point = (x: int, y: int)

def Point.`0` = Point(x = 0, y = 0)
def Point.`+` = { a, b -> Point(x = a.x + b.x, y = a.y + b.y) }

context Sum(a) = (
  `0`: a
  `+`: (a, a) -> a
)

def sum: [Sum(a)] list(a) -> a = { items -> items \fold(`0`) { + } }

def points = [Point(x = 1, y = 2), Point(x = 3, y = 4), Point(x = 5, y = 6)]

-- Sum(Point) is synthesized from Point.`0` and Point.`+`
def pointTotal = sum(points)   -- Point(x = 9, y = 12)

-- An explicitly loaded instance takes priority over synthesis
def reverseTotal = {
  use Sum(Point)(
    `0` = Point(x = 0, y = 0)
    `+` = { a, b -> Point(x = a.x - b.x, y = a.y - b.y) }
  ) in sum(points)
}

@entry
def main = {
  println("{product([1, 2, 3, 4])}")   -- 24
  println("{total([1, 2, 3, 4])}")     -- 10
  println("{pointTotal}")
  println("{reverseTotal}")
}

This program lives in the repository as examples/traitsAndTypeclasses.nyx.