Thrown by tryMatch when an unhandled type is encountered.
This placeholder, for use in self-referential types.
A tagged union that can hold a single value from any of a specified set of types.
Checks whether a handler can match a given type.
Calls a type-appropriate function with the value held in a SumType.
Attempts to call a type-appropriate function with the value held in a SumType, and throws on failure.
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));
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.
import std.math: approxEqual, cos, PI, sqrt; struct Rectangular { double x, y; } struct Polar { double r, theta; } alias Vector = SumType!(Rectangular, Polar); pure @safe @nogc nothrow double length(Vector v) { return v.match!( rect => sqrt(rect.x^^2 + rect.y^^2), polar => polar.r ); } pure @safe @nogc nothrow double horiz(Vector v) { return v.match!( rect => rect.x, polar => polar.r * cos(polar.theta) ); } Vector u = Rectangular(1, 1); Vector v = Polar(1, PI/4); assert(length(u).approxEqual(sqrt(2.0))); assert(length(v).approxEqual(1)); assert(horiz(u).approxEqual(1)); assert(horiz(v).approxEqual(sqrt(0.5)));
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 Tuple!(Op, "op", Expr*, "lhs", Expr*, "rhs"), 24 // the Tuple type above with Expr substituted for This. 25 alias BinOp = Expr.Types[2]; 26 27 // Factory function for number expressions 28 pure @safe 29 Expr* num(double value) 30 { 31 return new Expr(value); 32 } 33 34 // Factory function for variable expressions 35 pure @safe 36 Expr* var(string name) 37 { 38 return new Expr(name); 39 } 40 41 // Factory function for binary operation expressions 42 pure @safe 43 Expr* binOp(Op op, Expr* lhs, Expr* rhs) 44 { 45 return new Expr(BinOp(op, lhs, rhs)); 46 } 47 48 // Convenience wrappers for creating BinOp expressions 49 alias sum = partial!(binOp, Op.Plus); 50 alias diff = partial!(binOp, Op.Minus); 51 alias prod = partial!(binOp, Op.Times); 52 alias quot = partial!(binOp, Op.Div); 53 54 // Evaluate expr, looking up variables in env 55 pure @safe nothrow 56 double eval(Expr expr, double[string] env) 57 { 58 return expr.match!( 59 (double num) => num, 60 (string var) => env[var], 61 (BinOp bop) { 62 double lhs = eval(*bop.lhs, env); 63 double rhs = eval(*bop.rhs, env); 64 final switch(bop.op) { 65 static foreach(op; EnumMembers!Op) { 66 case op: 67 return mixin("lhs" ~ op ~ "rhs"); 68 } 69 } 70 } 71 ); 72 } 73 74 // Return a "pretty-printed" representation of expr 75 @safe 76 string pprint(Expr expr) 77 { 78 import std.format; 79 80 return expr.match!( 81 (double num) => "%g".format(num), 82 (string var) => var, 83 (BinOp bop) => "(%s %s %s)".format( 84 pprint(*bop.lhs), 85 bop.op, 86 pprint(*bop.rhs) 87 ) 88 ); 89 } 90 91 Expr* myExpr = sum(var("a"), prod(num(2), var("b"))); 92 double[string] myEnv = ["a":3, "b":4, "c":7]; 93 94 assert(eval(*myExpr, myEnv) == 11); 95 assert(pprint(*myExpr) == "(a + (2 * b))");
MIT
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).