Rust-like tuple enums. Give me a macro and a fulcrum to rest it on and I'll likely make an obnoxious macro.
What does it do?
Does what it says on the tin... generates Rust-like ADT enums, those weird ones with fields. Consider the following idiomatic Nim code:
type ShapeKind = enum NoneKind, CircleKind, RectangleKind, LineKind Shape = object case kind: ShapeKind of LineKind: lx1, ly1, lx2, ly2: int of RectangleKind: rx, ry, rw, rh: int of CircleKind: cRadius, cx, cy: int of NoneKind: discard
This is a bit messy due to rightly not being able to share field names(Nim variants are great for specific things, for other things they are a bit in the way). Other languages get around it doing something similar to what this library does, statically enforcing unpacking each kind. What one writes with fungus instead is:
import fungus adtEnum(Shape): None Circle: tuple[x, y, r: int] Rectangle: tuple[x, y, w, h: int] Line: tuple[x1, y1, x2, y2: int]
This generates a static type per each branch, and also a bunch of accessor procedures. The type definitions look like:
type ShapeKind = enum NoneKind, CircleKind, RectangleKind, LineKind Shape = object case kind: ShapeKind of LineKind: LineData: tuple[x1, y1, x2, y2: int] of RectangleKind: RectangleData: tuple[x, y, w, h: int] of CircleKind: CircleData: tuple[x, y, r: int] of NoneKind: nil None = distinct Shape Circle = distinct Shape Rectangle = distinct Shape Line = distinct Shape
Which means the following is possible:
var a = Shape Circle.init(10, 10, 100) a.to(Circle).r = 300 echo a.to(Circle) a = Shape Line.init(0, 0, 1, 1)
This doesnt solve much altogether, which is what the
from macros are for.
if (var myVar: Line) from a: # notice `var` this is a mutable match inc myVar.x1 echo myVar elif (myOtherVar: Circle) from a: echo myOtherVar
What this macro expands into is
a.kind == CircleKind and (let myOtherVar = Circle(a); true).
This allows you to work similarly to Rust's
if let construct, but this composes better(it should not bug out as easily :P ).
Now to do some primitive pattern matching!
match a: of Circle as mut circ: circ.r = 1000 echo circ of Rectangle as rect: echo rect of Line as (mut x1, _, x2, _): inc x1 echo a.to(Line) else: discard
match macro is very very basic pattern matching.
It is not too disimilar to the
As one can see though it allows a
as mut name to introduce a mutable name to the matching variable.
There is also the
as name which is an immutable variable match.
Finally there is a
tuple match which is
(x, y, z, w), much like the other match
mut can prefix a name to indicate it is a mutable match.
Tuple unpacking works just like normally for this matching, although one can match as few positional fields as they want.
Yes this also supports some subset of generics.
import fungus adtEnum(LinkedList[T]): Nil Node: tuple[n: ref LinkedList[T], val: T] proc newNode[T](val: T): Node[T] = result = Node[T].init((ref LinkedList[T])(), val) result.n = LinkedList[T] Nil[T].init() proc prepend[T](node: LinkedList[T], val: T): LinkedList[T] = result = LinkedList[T] Node[T].init(new LinkedList[T], val) Node[T](result).n = node proc len[T](list: LinkedList[T]): int = var theList = list while (node: Node) from theList: inc result theList = node.n proc `$`[T](list: LinkedList[T]): string = var theList = list result = "[" while (node: Node) from theList: result.add $node.val result.add ", " theList = node.n if result.len > 1: result.setLen(result.len - 2) result.add "]" var myList = LinkedList[int] newNode(10) myList = myList.prepend(11) myList = myList.prepend(12) myList = myList.prepend(13) echo myList echo myList.len var myList2 = LinkedList[string] newNode("world") myList2 = myList2.prepend "cruel" myList2 = myList2.prepend "hello" echo myList2 echo myList2.len
The above just works(thanks to the Rust book for a small example of linked lists using an ADT).
In the present state all generated procedures are private and need to be manually exported.