257 lines
No EOL
5.5 KiB
Markdown
257 lines
No EOL
5.5 KiB
Markdown
Patty - A pattern matching library
|
|
==================================
|
|
|
|
Patty is a library to perform pattern matching in Nim. The patterns have to be [variant objects](http://nim-lang.org/docs/manual.html#types-object-variants), which in Nim are encoded with a field (usually called `kind`) which varies in an enum, and a different object layout based on the value of this tag. An example would be
|
|
|
|
```nim
|
|
type
|
|
ShapeKind = enum
|
|
Circle, Rectangle
|
|
Shape = object
|
|
case kind: ShapeKind
|
|
of Circle:
|
|
r: float
|
|
of Rectangle:
|
|
w, h: float
|
|
```
|
|
|
|
If you have such an algebraic data type, you can do the following with Patty:
|
|
|
|
```nim
|
|
import patty
|
|
|
|
proc makeRect(w, h: float): Shape = Shape(kind: Rectangle, w: w, h: h)
|
|
|
|
match makeRect(3, 4):
|
|
Circle(r: radius):
|
|
echo "it is a circle of radius ", radius
|
|
Rectangle(w: width, h: height):
|
|
echo "it is a rectangle of height ", height
|
|
```
|
|
|
|
This will be translated by the `match` macro into the following form
|
|
|
|
```nim
|
|
let :tmp = makeRect(3, 4)
|
|
case :tmp.kind
|
|
of Circle:
|
|
let radius = :tmp.r
|
|
echo "it is a circle of radius ", radius
|
|
of Rectangle:
|
|
let
|
|
width = :tmp.w
|
|
height = :tmp.h
|
|
echo "it is a rectangle of height ", height
|
|
```
|
|
|
|
One can also use `_` for a variable, in which case it will not be bound. That is, the following
|
|
|
|
```nim
|
|
import patty
|
|
|
|
proc makeRect(w, h: float): Shape = Shape(kind: Rectangle, w: w, h: h)
|
|
|
|
match makeRect(3, 4):
|
|
Circle(r: radius):
|
|
echo "it is a circle of radius ", radius
|
|
Rectangle(w: _, h: height):
|
|
echo "it is a rectangle of height ", height
|
|
```
|
|
|
|
becomes
|
|
|
|
```nim
|
|
let :tmp = makeRect(3, 4)
|
|
case :tmp.kind
|
|
of Circle:
|
|
let radius = :tmp.r
|
|
echo "it is a circle of radius ", radius
|
|
of Rectangle:
|
|
let height = :tmp.h
|
|
echo "it is a rectangle of height ", height
|
|
```
|
|
|
|
Notice that Patty requires the field you dispatch on to be called `kind`. Also, checks are exhaustive: if you miss a case, the compiler will complain.
|
|
|
|
Patty also provides another macro to create algebraic data types. It looks like
|
|
|
|
```nim
|
|
variant Shape:
|
|
Circle(r: float)
|
|
Rectangle(w: float, h: float)
|
|
UnitCircle
|
|
```
|
|
|
|
and expands to
|
|
|
|
```nim
|
|
type
|
|
ShapeE = enum
|
|
CircleE, RectangleE, UnitCircleE
|
|
Shape = object
|
|
case kind: ShapeE
|
|
of CircleE:
|
|
r: float
|
|
of RectangleE:
|
|
w: float
|
|
h: float
|
|
of UnitCircleE:
|
|
nil
|
|
|
|
proc `==`(a: Shape; b: Shape): bool =
|
|
if a.kind == b.kind:
|
|
case a.kind
|
|
of CircleE:
|
|
return a.r == b.r
|
|
of RectangleE:
|
|
return a.w == b.w and a.h == b.h
|
|
of UnitCircleE:
|
|
return true
|
|
else:
|
|
return false
|
|
|
|
proc Circle(r: float; x: float; y: float): Shape =
|
|
Shape(kind: CircleE, r: r)
|
|
|
|
proc Rectangle(w: float; h: float): Shape =
|
|
Shape(kind: RectangleE, w: w, h: h)
|
|
|
|
proc UnitCircle(side: int): Shape =
|
|
Shape(kind: UnitCircleE)
|
|
```
|
|
|
|
Notice that the macro also generates three convenient constructors (`Circle` ,`Rectangle` and `UnitCircle`), and in fact the names in the enum are `CircleE`, `RectangleE` and `UnitCircleE` to avoid a name conflict. Also, a proper definition of equality based on the actual contents of the record is generated.
|
|
|
|
A couple of limitations fo the `variant` macro:
|
|
|
|
* field names must be unique across branches (that is, different variants cannot have two fields with the same name). This is actually a limitation of Nim.
|
|
* the shortcut that groups field names by type does not seem to work, that is, in the above example one could not write `Rectangle(w, h: float)`.
|
|
|
|
In the future, Patty may also add copy constructors. Also, some work needs to be done to make it easier to use the generated contructors with `ref` types, in particular for the important case of recursive algebraic data types.
|
|
|
|
Things that do not work (yet)
|
|
-----------------------------
|
|
|
|
One would expect many forms of pattern matching but, at least for now, the support in Patty is very limited. Things that would be nice to support but do not work yet include:
|
|
|
|
* catch-all patterns
|
|
|
|
```nim
|
|
match c:
|
|
Circle(r: r):
|
|
echo "it is a circle"
|
|
_:
|
|
echo "it is not a circle"
|
|
```
|
|
|
|
* matching a constant
|
|
|
|
```nim
|
|
match c:
|
|
"hello":
|
|
echo "the string was hello"
|
|
```
|
|
|
|
* matching an existing variable
|
|
|
|
```nim
|
|
let x = 5
|
|
match c:
|
|
x:
|
|
echo "c == 5"
|
|
```
|
|
|
|
* irrefutable patterns (no dispatch on `kind`)
|
|
|
|
```nim
|
|
type Person = object
|
|
name: string
|
|
age: int
|
|
let p = Person(name: "John Doe", age: 37)
|
|
match p:
|
|
Person(name: n, age: a):
|
|
echo n, "is ", a, " years old"
|
|
```
|
|
|
|
* nested pattern matching
|
|
|
|
```nim
|
|
match c:
|
|
Circle(Point(x: x, y: y), r: r):
|
|
echo "the abscissa of the center is ", x
|
|
```
|
|
|
|
* matching without binding
|
|
|
|
```nim
|
|
match c:
|
|
Circle:
|
|
echo "it is a circle!"
|
|
```
|
|
|
|
* matching by position
|
|
|
|
```nim
|
|
match c:
|
|
Circle(x, y, r):
|
|
echo "the radius is ", r
|
|
```
|
|
|
|
* binding subpatterns
|
|
|
|
```nim
|
|
match getMeACircle():
|
|
c@Circle(x, y, r):
|
|
echo "there you have ", c
|
|
```
|
|
|
|
* pattern matching as an expression
|
|
|
|
```nim
|
|
let coord = match c:
|
|
Circle(x: x, y: y, r: r):
|
|
x
|
|
Rectangle(w: w, h: h):
|
|
h
|
|
```
|
|
|
|
* unification
|
|
|
|
```nim
|
|
match r:
|
|
Rectangle(w: x, h: x):
|
|
echo "it is a square"
|
|
```
|
|
|
|
* guards
|
|
|
|
```nim
|
|
match c:
|
|
Circle(x: x, y: y, r: r) if r < 0:
|
|
echo "the circle has negative length"
|
|
```
|
|
|
|
* variable-length pattern matching, such as with arrays
|
|
|
|
```nim
|
|
match c:
|
|
[a, b, c]:
|
|
echo "the length is 3 and the first elements is ", a
|
|
```
|
|
|
|
* custom pattern matchers, such as in regexes
|
|
|
|
```nim
|
|
let Email = r"(\w+)@(\w+).(\w+)"
|
|
match c:
|
|
Email(name, domain, tld):
|
|
echo "hello ", name
|
|
```
|
|
|
|
* combining patterns with `or`
|
|
|
|
```nim
|
|
match c:
|
|
Circle or Rectangle:
|
|
echo "it is a shape"
|
|
``` |