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.