View source code
Display the source code in std/sumtype.d from which this page was generated on github.
Report a bug
If you spot a problem with this page, click here to create a Bugzilla issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using local clone.

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.

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="introspection-based-matching"><h3>Introspection-based matching</h3></div>
*
* 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.
*/
version (D_BetterC) {

Example

Introspection-based 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.

import std.math.operations : isClose;
import std.math.trigonometry : cos;
import std.math.constants : PI;
import std.math.algebraic : sqrt;

struct Rectangular { double x, y; }
struct Polar { double r, theta; }
alias Vector = SumType!(Rectangular, Polar);

double length(Vector v)
{
    return v.match!(
        rect => sqrt(rect.x^^2 + rect.y^^2),
        polar => polar.r
    );
}

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).isClose(sqrt(2.0)));
assert(length(v).isClose(1));
assert(horiz(u).isClose(1));
assert(horiz(v).isClose(sqrt(0.5)));
}

/** <div id="arithmetic-expression-evaluator"><h3>Arithmetic expression evaluator</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

Arithmetic expression evaluator

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

NameDescription
MatchException Thrown by [tryMatch] when an unhandled type is encountered.

Structs

NameDescription
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

NameDescription
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

NameTypeDescription
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