sumtype

A sum type for modern D.

This module provides SumType, an alternative to std.variant.Algebraic with improved pattern-matching, full attribute correctness (pure, @safe, @nogc, and nothrow are inferred whenever possible), and no dependency on runtime type information (TypeInfo).

Public Imports

std.variant
public import std.variant : This;

Members

Classes

MatchException
class MatchException

Thrown by tryMatch when an unhandled type is encountered.

Structs

SumType
struct SumType(TypeArgs...)

A tagged union that can hold a single value from any of a specified set of types.

Templates

canMatch
template canMatch(alias handler, T)

Checks whether a handler can match a given type.

match
template match(handlers...)

Calls a type-appropriate function with the value held in a SumType.

tryMatch
template tryMatch(handlers...)

Attempts to call a type-appropriate function with the value held in a SumType, and throws on failure.

Examples

1 import std.math: approxEqual;
2 
3 struct Fahrenheit { double degrees; }
4 struct Celsius { double degrees; }
5 struct Kelvin { double degrees; }
6 
7 alias Temperature = SumType!(Fahrenheit, Celsius, Kelvin);
8 
9 // Construct from any of the member types.
10 Temperature t1 = Fahrenheit(98.6);
11 Temperature t2 = Celsius(100);
12 Temperature t3 = Kelvin(273);
13 
14 // Use pattern matching to access the value.
15 pure @safe @nogc nothrow
16 Fahrenheit toFahrenheit(Temperature t)
17 {
18     return Fahrenheit(
19         t.match!(
20             (Fahrenheit f) => f.degrees,
21             (Celsius c) => c.degrees * 9.0/5 + 32,
22             (Kelvin k) => k.degrees * 9.0/5 - 459.4
23         )
24     );
25 }
26 
27 assert(toFahrenheit(t1).degrees.approxEqual(98.6));
28 assert(toFahrenheit(t2).degrees.approxEqual(212));
29 assert(toFahrenheit(t3).degrees.approxEqual(32));
30 
31 // Use ref to modify the value in place.
32 pure @safe @nogc nothrow
33 void freeze(ref Temperature t)
34 {
35     t.match!(
36         (ref Fahrenheit f) => f.degrees = 32,
37         (ref Celsius c) => c.degrees = 0,
38         (ref Kelvin k) => k.degrees = 273
39     );
40 }
41 
42 freeze(t1);
43 assert(toFahrenheit(t1).degrees.approxEqual(32));
44 
45 // Use a catch-all handler to give a default result.
46 pure @safe @nogc nothrow
47 bool isFahrenheit(Temperature t)
48 {
49     return t.match!(
50         (Fahrenheit f) => true,
51         _ => false
52     );
53 }
54 
55 assert(isFahrenheit(t1));
56 assert(!isFahrenheit(t2));
57 assert(!isFahrenheit(t3));

Structural matching

In the length and horiz functions below, the handlers for match do not specify the types of their arguments. Instead, matching is done based on how the argument is used in the body of the handler: any type with x and y properties will be matched by the rect handlers, and any type with r and theta properties will be matched by the polar handlers.

1 import std.math: approxEqual, cos, PI, sqrt;
2 
3 struct Rectangular { double x, y; }
4 struct Polar { double r, theta; }
5 alias Vector = SumType!(Rectangular, Polar);
6 
7 pure @safe @nogc nothrow
8 double length(Vector v)
9 {
10     return v.match!(
11         rect => sqrt(rect.x^^2 + rect.y^^2),
12         polar => polar.r
13     );
14 }
15 
16 pure @safe @nogc nothrow
17 double horiz(Vector v)
18 {
19     return v.match!(
20         rect => rect.x,
21         polar => polar.r * cos(polar.theta)
22     );
23 }
24 
25 Vector u = Rectangular(1, 1);
26 Vector v = Polar(1, PI/4);
27 
28 assert(length(u).approxEqual(sqrt(2.0)));
29 assert(length(v).approxEqual(1));
30 assert(horiz(u).approxEqual(1));
31 assert(horiz(v).approxEqual(sqrt(0.5)));

Arithmetic expression evaluator

This example makes use of the special placeholder type This to define a recursive data type: an abstract syntax tree for representing simple arithmetic expressions.

1 import std.functional: partial;
2 import std.traits: EnumMembers;
3 import std.typecons: Tuple;
4 
5 enum Op : string
6 {
7     Plus  = "+",
8     Minus = "-",
9     Times = "*",
10     Div   = "/"
11 }
12 
13 // An expression is either
14 //  - a number,
15 //  - a variable, or
16 //  - a binary operation combining two sub-expressions.
17 alias Expr = SumType!(
18     double,
19     string,
20     Tuple!(Op, "op", This*, "lhs", This*, "rhs")
21 );
22 
23 // Shorthand for the Tuple type above
24 alias BinOp = Expr.Types[2];
25 
26 // Factory function for number expressions
27 pure @safe
28 Expr* num(double value)
29 {
30     return new Expr(value);
31 }
32 
33 // Factory function for variable expressions
34 pure @safe
35 Expr* var(string name)
36 {
37     return new Expr(name);
38 }
39 
40 // Factory function for binary operation expressions
41 pure @safe
42 Expr* binOp(Op op, Expr* lhs, Expr* rhs)
43 {
44     return new Expr(BinOp(op, lhs, rhs));
45 }
46 
47 // Convenience wrappers for creating BinOp expressions
48 alias sum  = partial!(binOp, Op.Plus);
49 alias diff = partial!(binOp, Op.Minus);
50 alias prod = partial!(binOp, Op.Times);
51 alias quot = partial!(binOp, Op.Div);
52 
53 // Evaluate expr, looking up variables in env
54 pure @safe nothrow
55 double eval(Expr expr, double[string] env)
56 {
57     return expr.match!(
58         (double num) => num,
59         (string var) => env[var],
60         (BinOp bop) {
61             double lhs = eval(*bop.lhs, env);
62             double rhs = eval(*bop.rhs, env);
63             final switch(bop.op) {
64                 static foreach(op; EnumMembers!Op) {
65                     case op:
66                         return mixin("lhs" ~ op ~ "rhs");
67                 }
68             }
69         }
70     );
71 }
72 
73 // Return a "pretty-printed" representation of expr
74 @safe
75 string pprint(Expr expr)
76 {
77     import std.format;
78 
79     return expr.match!(
80         (double num) => "%g".format(num),
81         (string var) => var,
82         (BinOp bop) => "(%s %s %s)".format(
83             pprint(*bop.lhs),
84             bop.op,
85             pprint(*bop.rhs)
86         )
87     );
88 }
89 
90 Expr* myExpr = sum(var("a"), prod(num(2), var("b")));
91 double[string] myEnv = ["a":3, "b":4, "c":7];
92 
93 assert(eval(*myExpr, myEnv) == 11);
94 assert(pprint(*myExpr) == "(a + (2 * b))");

Meta

Authors

Paul Backus, Atila Neves