View source code
Display the source code in std/sumtype.d from which thispage was generated on github.
Report a bug
If you spot a problem with this page, click here to create aBugzilla 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 usinglocal 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.

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

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