Module std.sumtype
[SumType] is a generic discriminated union implementation that uses design-by-introspection to generate safe and efficient code. Its features
include
* [Pattern matching.][match]
* Support for self-referential types.
* Full attribute correctness (pure
, @safe
, @nogc
, and nothrow
are
inferred whenever possible).
* A type-safe and memory-safe API compatible with DIP 1000 (scope
).
* No dependency on runtime type information (TypeInfo
).
* Compatibility with BetterC.
List of examples
* [Basic usage](#basic-usage)
* [Matching with an overload set](#matching-with-an-overload-set)
* [Recursive SumTypes](#recursive-sumtypes)
* [Memory corruption](#memory-corruption) (why assignment can be @system
)
* [Avoiding unintentional matches](#avoiding-unintentional-matches)
* [Multiple dispatch](#multiple-dispatch)
Example
Basic usage
import std .math .operations : isClose;
struct Fahrenheit { double degrees; }
struct Celsius { double degrees; }
struct Kelvin { double degrees; }
alias Temperature = SumType!(Fahrenheit, Celsius, Kelvin);
// Construct from any of the member types.
Temperature t1 = Fahrenheit(98.6);
Temperature t2 = Celsius(100);
Temperature t3 = Kelvin(273);
// Use pattern matching to access the value.
Fahrenheit toFahrenheit(Temperature t)
{
return Fahrenheit(
t .match!(
(Fahrenheit f) => f .degrees,
(Celsius c) => c .degrees * 9.0/5 + 32,
(Kelvin k) => k .degrees * 9.0/5 - 459.4
)
);
}
assert(toFahrenheit(t1) .degrees .isClose(98.6));
assert(toFahrenheit(t2) .degrees .isClose(212));
assert(toFahrenheit(t3) .degrees .isClose(32));
// Use ref to modify the value in place.
void freeze(ref Temperature t)
{
t .match!(
(ref Fahrenheit f) => f .degrees = 32,
(ref Celsius c) => c .degrees = 0,
(ref Kelvin k) => k .degrees = 273
);
}
freeze(t1);
assert(toFahrenheit(t1) .degrees .isClose(32));
// Use a catch-all handler to give a default result.
bool isFahrenheit(Temperature t)
{
return t .match!(
(Fahrenheit f) => true,
_ => false
);
}
assert(isFahrenheit(t1));
assert(!isFahrenheit(t2));
assert(!isFahrenheit(t3));
}
/** <div id="matching-with-an-overload-set"><h3>Matching with an overload set</h3></div>
*
* Instead of writing `match` handlers inline as lambdas, you can write them as
* overloads of a function. An `alias` can be used to create an additional
* overload for the `SumType` itself.
*
* For example, with this overload set:
*
* ---
* string handle(int n) { return "got an int"; }
* string handle(string s) { return "got a string"; }
* string handle(double d) { return "got a double"; }
* alias handle = match!handle;
* ---
*
* Usage would look like this:
*/
version (D_BetterC) {
Example
Matching with an overload set
Instead of writing match
handlers inline as lambdas, you can write them as
overloads of a function. An alias
can be used to create an additional
overload for the SumType
itself.
For example, with this overload set:
string handle(int n) { return "got an int"; }
string handle(string s) { return "got a string"; }
string handle(double d) { return "got a double"; }
alias handle = match!handle;
Usage would look like this:
alias ExampleSumType = SumType!(int, string, double);
ExampleSumType a = 123;
ExampleSumType b = "hello";
ExampleSumType c = 3.14;
writeln(a .handle); // "got an int"
writeln(b .handle); // "got a string"
writeln(c .handle); // "got a double"
}
/** <div id="recursive-sumtypes"><h3>Recursive SumTypes</h3></div>
*
* This example makes use of the special placeholder type `This` to define a
* [recursive data type](https://en.wikipedia.org/wiki/Recursive_data_type): an
* [abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) for
* representing simple arithmetic expressions.
*/
version (D_BetterC) {
Example
Recursive SumTypes
This example makes use of the special placeholder type This
to define a
[recursive data type](https://en.wikipedia.org/wiki/Recursive_data_type): an
[abstract syntax tree](https://en.wikipedia.org/wiki/Abstract_syntax_tree) for
representing simple arithmetic expressions.
import std .functional : partial;
import std .traits : EnumMembers;
import std .typecons : Tuple;
enum Op : string
{
Plus = "+",
Minus = "-",
Times = "*",
Div = "/"
}
// An expression is either
// - a number,
// - a variable, or
// - a binary operation combining two sub-expressions.
alias Expr = SumType!(
double,
string,
Tuple!(Op, "op", This*, "lhs", This*, "rhs")
);
// Shorthand for Tuple!(Op, "op", Expr*, "lhs", Expr*, "rhs"),
// the Tuple type above with Expr substituted for This.
alias BinOp = Expr .Types[2];
// Factory function for number expressions
Expr* num(double value)
{
return new Expr(value);
}
// Factory function for variable expressions
Expr* var(string name)
{
return new Expr(name);
}
// Factory function for binary operation expressions
Expr* binOp(Op op, Expr* lhs, Expr* rhs)
{
return new Expr(BinOp(op, lhs, rhs));
}
// Convenience wrappers for creating BinOp expressions
alias sum = partial!(binOp, Op .Plus);
alias diff = partial!(binOp, Op .Minus);
alias prod = partial!(binOp, Op .Times);
alias quot = partial!(binOp, Op .Div);
// Evaluate expr, looking up variables in env
double eval(Expr expr, double[string] env)
{
return expr .match!(
(double num) => num,
(string var) => env[var],
(BinOp bop)
{
double lhs = eval(*bop .lhs, env);
double rhs = eval(*bop .rhs, env);
final switch (bop .op)
{
static foreach (op; EnumMembers!Op)
{
case op:
return mixin("lhs" ~ op ~ "rhs");
}
}
}
);
}
// Return a "pretty-printed" representation of expr
string pprint(Expr expr)
{
import std .format : format;
return expr .match!(
(double num) => "%g" .format(num),
(string var) => var,
(BinOp bop) => "(%s %s %s)" .format(
pprint(*bop .lhs),
cast(string) bop .op,
pprint(*bop .rhs)
)
);
}
Expr* myExpr = sum(var("a"), prod(num(2), var("b")));
double[string] myEnv = ["a":3, "b":4, "c":7];
writeln(eval(*myExpr, myEnv)); // 11
writeln(pprint(*myExpr)); // "(a + (2 * b))"
Classes
Name | Description |
---|---|
MatchException
|
Thrown by [tryMatch] when an unhandled type is encountered. |
Structs
Name | Description |
---|---|
SumType
|
A [tagged union](https://en.wikipedia.org/wiki/Tagged_union) that can hold a single value from any of a specified set of types. |
This
|
Placeholder used to refer to the enclosing [SumType]. |
Templates
Name | Description |
---|---|
match
|
Calls a type-appropriate function with the value held in a [SumType]. |
tryMatch
|
Attempts to call a type-appropriate function with the value held in a [SumType], and throws on failure. |
Manifest constants
Name | Type | Description |
---|---|---|
canMatch
|
True if handler is a potential match for Ts , otherwise false.
|
|
isSumType
|
True if T is a [SumType] or implicitly converts to one, otherwise false.
|
Authors
Paul Backus
License
Boost License 1.0