One noteworthy thing is that in the `case` assuming each branch label emits a distinct type(like my package fungus does ) you can dispatch on those types type Shape = case of Circle: r: int of Triangle, Square: w, h: int proc area(circ: Circle): int = circ.r * circ.r * 4 # We're engineers proc area(tri: Triangle) int = tri.w * tri.h / 2 proc area(square: Square): int = square.w * square.h proc area(shape: Shape): int = #shape.unpackIt(area(it)) # The aforementioned macro is how one would really do this, but alas case shape of Circle: Circle(shape).area() of Triangle: Triangle(shape).area() of Square: Square(shape).area() var shapes = @[Shape Circle(r: 3), Square(w: 100, h: 20), Triangle(w: 100, h: 20)] for shape in shapes: echo shape.area() Run
Whereas in the the world with default case objects and aggressive checking the same logic would be type ShapeKind = enum Circle, Square, Triangle Shape = object case kind: ShapeKind of Circle: r: int of Square, Triangle: w, h: int proc areaCircl(shape: Shape): int = case shape.kind of Circle: shape.r else: raiseAssert false proc areaSquare(shape: Shape): int = case shape.kind of Square: shape.w * shape.h else: raiseAssert false proc areaTriangle(shape: Shape): int = case shape.kind of Triangle: shape.w * shape.h div 2 else: raiseAssert false proc area(shape: Shape): int = case shape.kind of Circle: areaCircle(shape) of Square: areaSquare(shape) of Triangle: areaTriangle(shape) Run Whilst the former is syntax sugar on the latter one does not have to always have a `case` statement before accessing fields. Practically it removes runtime checks before access. Which I'd argue is just better as it means fields are a type operation away. If you want to have better static typing it seems like a no brainer to embrace a distinct type per branch as it removes ambiguity and means you only need runtime checks on write and conversion, no amount of aliasing through procedure parameters makes `Circle` lose the `Circle` type unless you pass it to a`Shape`.