shapes
Sum types and pattern matching: a Shape ADT, guards, pipes and test blocks.
examples/data/shapes.dawn
# Sum types and pattern matching: a Shape ADT, guards, pipes and test blocks.
#
# A `type` with `|` alternatives is a closed set, so `match` over one is
# checked for exhaustiveness: add a fourth shape and every match in this file
# that has not been extended is a compile error rather than a surprise at run
# time. That is the trade an ADT makes -- adding a case is expensive, and
# adding an operation over the cases is free.
#
# Run: dawn run examples/data/shapes.dawn
# Test: dawn test examples/data/shapes.dawn
type Shape =
| Circle(r: Float)
| Rect(w: Float, h: Float)
| Point
fn area(s: Shape) -> Float =
match s {
Circle(r) -> 3.14159 * r * r
Rect(w, h) -> w * h
Point -> 0.0
}
## The first arm that matches wins, so the guarded one has to come first.
fn describe(s: Shape) -> String =
match s {
Circle(r) if r > 100.0 -> "a huge circle"
Circle(r) -> "circle with r = $r"
Rect(w, h) -> "rect $w x $h"
Point -> "a point"
}
pub fn main() -> Unit !io = {
let shapes = [Circle(1.0), Rect(2.0, 3.0), Point, Circle(200.0)]
for s in shapes {
println(describe(s))
}
# arguments may be named, and named ones may come in any order
println(describe(Rect(h: 3.0, w: 2.0)))
# equality is structural by default -- no `impl Eq`, no `derive`
println("equal: ${Circle(1.0) == Circle(1.0)}")
shapes
|> map(area)
|> fold(0.0, (acc, a) => acc + a)
|> total => println("total area: $total")
}
test "area of unit circle" {
assert area(Circle(1.0)) == 3.14159
}
test "point has no area" {
assert area(Point) == 0.0
}
test "a guard picks the arm, not the constructor" {
assert describe(Circle(1.0)) == "circle with r = 1.0"
assert describe(Circle(200.0)) == "a huge circle"
}
test "named arguments name the fields, in any order" {
assert Rect(h: 3.0, w: 2.0) == Rect(2.0, 3.0)
}