英語版
このページの英語版を見る

std.sumtype

SumTypeは汎用の差別化共用体の実装である。 デザイン・バイ・イントロスペクションを用いて安全で効率的なコードを生成する。その特徴 を含む:
  • パターン・マッチ。
  • 自己参照型のサポート。
  • 完全な属性の正しさ(pure,@safe,@nogc,nothrow は可能な限り推論される。 は可能な限り推論される)。
  • DIP 1000と互換性のある型安全かつメモリ安全なAPI (scope)。
  • 実行時の型情報に依存しない(TypeInfo)。
  • BetterCとの互換性。
License:
Boost License 1.0
Authors:
Paul Backus

ソース std/sumtype.d

Examples:

基本的な使い方

import std.math.operations : isClose;

struct Fahrenheit { double degrees; }
struct Celsius { double degrees; }
struct Kelvin { double degrees; }

alias Temperature = SumType!(Fahrenheit, Celsius, Kelvin);

// いずれかのメンバー型から構築する。
Temperature t1 = Fahrenheit(98.6);
Temperature t2 = Celsius(100);
Temperature t3 = Kelvin(273);

// パターンマッチングを使用して値にアクセスする。
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));

// refを使用して、その場で値を変更する。
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));

// デフォルトの結果を返すには、catch-allハンドラを使用する。
bool isFahrenheit(Temperature t)
{
    return t.match!(
        (Fahrenheit f) => true,
        _ => false
    );
}

assert(isFahrenheit(t1));
assert(!isFahrenheit(t2));
assert(!isFahrenheit(t3));
Examples:

イントロスペクションに基づくマッチング

以下のlengthhoriz 関数の中で、match のハンドラは引数の型を指定していない。 は引数の型を指定しない。その代わり、マッチングは とプロパティを持つ型はすべて、x ハンドラによってマッチングされる。y プロパティを持つ型は、rect ハンドラーによってマッチングされ、rtheta プロパティを持つ型はpolar ハンドラーによってマッチングされる。
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)));
Examples:

算術式評価器

この例では、特別なプレースホルダ型This を使って、再帰データ型である 定義している。 抽象構文木 を定義している。
import std.functional : partial;
import std.traits : EnumMembers;
import std.typecons : Tuple;

enum Op : string
{
    Plus  = "+",
    Minus = "-",
    Times = "*",
    Div   = "/"
}

// 式は以下のいずれかである
//  - 数値、
//  - 変数、または
//  - 2つの部分式を組み合わせた2項演算子。
alias Expr = SumType!(
    double,
    string,
    Tuple!(Op, "op", This*, "lhs", This*, "rhs")
);

// Tuple!(Op, "op", Expr*, "lhs", Expr*, "rhs")の短縮形であり、
// これは、上のTuple型でExprがThisに置き換えられたものである。
alias BinOp = Expr.Types[2];

// 数値式用のファクトリー関数
Expr* num(double value)
{
    return new Expr(value);
}

// 変数式のファクトリー関数
Expr* var(string name)
{
    return new Expr(name);
}

// 二項演算式のファクトリー関数
Expr* binOp(Op op, Expr* lhs, Expr* rhs)
{
    return new Expr(BinOp(op, lhs, rhs));
}

// BinOp式を作成するための簡易ラッパー
alias sum  = partial!(binOp, Op.Plus);
alias diff = partial!(binOp, Op.Minus);
alias prod = partial!(binOp, Op.Times);
alias quot = partial!(binOp, Op.Div);

// exprを評価し、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");
                }
            }
        }
    );
}

// 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))"
struct This;
SumType を囲むプレースホルダ。
struct SumType(Types...) if (is(NoDuplicates!Types == Types) && (Types.length > 0));
タグ付き共用体。 タグ付き結合で、指定された型のいずれかの値を保持することができる。
の値は、 パターンマッチングを使用して操作できる。 SumTypeの値は、パターンマッチを使って操作できる。
曖昧さを避けるため、型の重複は許されない。 を参照のこと)。
特殊型This は、 と同様に、自己参照型を作成するためのプレースホルダとして使用できる。 Algebraic と同様に、自己参照型を作成するためのプレースホルダとして使用できる。以下を参照のこと。 "算術式評価子"の例を参照のこと。 を参照のこと。
A SumTypeはデフォルトで初期化され、その最初のメンバ型の".init "値を保持する。 Aは、通常の共用体のように、その最初のメンバ型の値を保持するように初期化される。バージョン識別子 SumTypeNoDefaultCtor を使うと、この動作を無効にすることができる。
alias Types = AliasSeq!(ReplaceTypeUnless!(isSumTypeInstance, This, typeof(this), TemplateArgsOf!SumType));
SumType が保持できる型。
this(T value);

const this(const(T) value);

immutable this(immutable(T) value);

inout this(Value)(Value value)
if (is(Value == DeducedParameterType!(inout(T))));
特定の値を保持するSumType を構築する。
inout this(ref inout(SumType) other);
別のSumType のコピーであるSumType を構築する。
ref SumType opAssign(T rhs);
SumType に値を代入する。
値をSumType に代入する。 代入されるメンバ以外のメンバにポインタや参照が含まれていると、代入によってメモリ破壊が発生する可能性がある。 がメモリ破壊を引き起こす可能性がある。 以下の"メモリ破壊"の例を参照のこと)。 を参照のこと)。したがって、このような代入は @system.
個々の代入は、呼び出し元が以下のことを保証できる場合、@trusted 。 呼び出し元が、ポインタや参照を含むメンバへの未解決の参照がないことを保証できる場合、 個別の代入は。SumType 個々の代入は、呼び出し元が、代入が発生した時点でポインタや参照を含むメンバへの未解決の参照がないことを保証できる場合、。 個々の代入は、呼び出し元が、代入が発生した時点で、ポインタや参照を含むメンバへの未処理の参照がないことを保証できる場合、。
Examples:

メモリ破損

この例は、SumType への代入がどのように使われるかを示している。 @system@safes = 123
SumType!(int*, int) s = new int;
s.tryMatch!(
    (ref int* p) {
        s = 123; // `p`を上書きする
        return *p; // 未定義の動作
    }
);
ref SumType opAssign(ref SumType rhs);
別のSumType の値をこの値にコピーする。
@safetyの詳細については、value-assignmentオーバーロードを参照のこと。
コピー代入は、Types のいずれかがコピー不可の場合、@disabled となる。
ref SumType opAssign(SumType rhs);
別のSumType の値をこの値に移動する。
@safetyの詳細についてはvalue-assignmentオーバーロードを参照のこと。
bool opEquals(this This, Rhs)(auto ref Rhs rhs)
if (!is(CommonType!(This, Rhs) == void));
2つのSumTypesが等しいかどうかを比較する。
2つのSumTypeが等しいのは、それらが同じ種類のSumType で、同じ型の値を含み、それらの値が等しい場合である。 が同じ型の値を含み、それらの値が等しい場合に等しい。
string toString(this This)();
SumType の現在値の文字列表現を返す。
-betterC と一緒にコンパイルされた場合は利用できない。
void toString(this This, Sink, Char)(ref Sink sink, const ref FormatSpec!Char fmt);
SumType の現在値の書式付き書き込みを処理する。
-betterC と一緒にコンパイルした場合は使用できない。
Parameters:
Sink sink 書き込む出力範囲。
FormatSpec!Char fmt 使用するフォーマット指定子。
const size_t toHash();
SumType の現在値のハッシュを返す。
-betterC と一緒にコンパイルされた場合は利用できない。
enum bool isSumType(T);
TSumTypeであるか、暗黙のうちにSumTypeに変換される場合は真、そうでない場合は偽を返す。
Examples:
static struct ConvertsToSumType
{
    SumType!int payload;
    alias payload this;
}

static struct ContainsSumType
{
    SumType!int payload;
}

assert(isSumType!(SumType!int));
assert(isSumType!ConvertsToSumType);
assert(!isSumType!ContainsSumType);
template match(handlers...)
SumTypeに保持された値で、型に応じた関数を呼び出す。
SumTypeが持ちうるそれぞれの型について、指定されたハンドラがその型の引数を1つ受け入れるかどうかが順番にチェックされる。 は、その型の単一の引数を受け付けるかどうかを順番にチェックする。 その型にマッチする最初のものが選ばれる。(注釈:) 最初にマッチしたものが、必ずしも完全にマッチするとは限らないことに注意。 よくある落とし穴については、"意図しないマッチを避ける"を参照のこと)。 を参照のこと)。
すべての型はマッチするハンドラーを持たなければならず、すべてのハンドラーは少なくとも一つの型にマッチしなければならない。 これはコンパイル時に強制される。これはコンパイル時に強制される。
ハンドラーは、関数、デリゲート、またはopCall オーバーロードを持つオブジェクトである。もし 複数のオーバーロードを持つ "関数"がハンドラーとして与えられた場合、すべてのオーバーロードがマッチする可能性があるとみなされる。 がマッチする可能性がある。
テンプレート化されたハンドラも受け付ける。 マッチする。以下を参照のこと。 を参照のこと。 テンプレート化されたハンドラの使用例 "を参照のこと。
マッチングのために複数のSumTypesが渡された場合、それらの値は別々の引数としてハンドラに渡される。 マッチングに複数のSumTypesが渡された場合、それらの値は別々の引数としてハンドラに渡され、マッチングは可能な値型の組み合わせごとに行われる。 マッチングが行われる。例については"複数のディスパッチ"を参照のこと。 例 "を参照のこと。
Returns:
現在保持されている型にマッチするハンドラーから返される値。
Examples:

不用意なマッチングを避ける

時には、暗黙の変換によって、ハンドラが意図した以上の型にマッチしてしまうことがある。 にマッチしてしまうことがある。以下の例は、この問題に対する2つの解決策を示している。
alias Number = SumType!(double, int);

Number x;

// 問題: intは暗黙的にdoubleに変換されるため、
// 両方の型に対してdoubleハンドラが使用され、intハンドラは決して一致しない。
assert(!__traits(compiles,
    x.match!(
        (double d) => "got double",
        (int n) => "got int"
    )
));

// 解決策1: "より専門的"な型(この場合はint型)のハンドラーを、
// 変換先の型のハンドラの前に置く。
assert(__traits(compiles,
    x.match!(
        (int n) => "got int",
        (double d) => "got double"
    )
));

// 解決策2: 暗黙的に変換されるあらゆる型ではなく、
// 一致するはずの正確な型のみを受け入れるテンプレートを使用する。
alias exactly(T, alias fun) = function (arg)
{
    static assert(is(typeof(arg) == T));
    return fun(arg);
};

// ここで、たとえdoubleハンドラを最初に配置したとしても、
// それはdoubleに対してのみ使用され、intには使用されない。
assert(__traits(compiles,
    x.match!(
        exactly!(double, d => "got double"),
        exactly!(int, n => "got int")
    )
));
Examples:

複数の派遣

パターン・マッチは、複数の引数を持つハンドラを渡すことで、一度に複数のSumType。 ハンドラに複数の引数を 渡すことで、一度に複数のハンドラに対してパターンマッチングを行うことができる。これは通常 をネストして使うよりも、より簡潔なコードになる。 matchをネストして呼び出すよりも簡潔なコードになる。
struct Point2D { double x, y; }
struct Point3D { double x, y, z; }

alias Point = SumType!(Point2D, Point3D);

version (none)
{
    // この関数は機能するが、コードは醜く、繰り返しが多い。
    // マッチするために3つの別々の呼び出しを使っている!
    @safe pure nothrow @nogc
    bool sameDimensions(Point p1, Point p2)
    {
        return p1.match!(
            (Point2D _) => p2.match!(
                (Point2D _) => true,
                _ => false
            ),
            (Point3D _) => p2.match!(
                (Point3D _) => true,
                _ => false
            )
        );
    }
}

// このバージョンはずっといい。
@safe pure nothrow @nogc
bool sameDimensions(Point p1, Point p2)
{
    alias doMatch = match!(
        (Point2D _1, Point2D _2) => true,
        (Point3D _1, Point3D _2) => true,
        (_1, _2) => false
    );

    return doMatch(p1, p2);
}

Point a = Point2D(1, 2);
Point b = Point2D(3, 4);
Point c = Point3D(5, 6, 7);
Point d = Point3D(8, 9, 0);

assert( sameDimensions(a, b));
assert( sameDimensions(c, d));
assert(!sameDimensions(a, c));
assert(!sameDimensions(d, b));
ref auto match(SumTypes...)(auto ref SumTypes args)
if (allSatisfy!(isSumType, SumTypes) && (args.length > 0));
実際の match関数」である。
Parameters:
SumTypes args 1つ以上のSumTypeオブジェクト。
template tryMatch(handlers...)
に保持された値で、型に応じた関数を呼び出そうとする。 保持された値で型に応じた関数を呼び出そうとし、失敗するとスローする。
マッチはmatchと同じルールで選択されるが、網羅的である必要はない。 言い換えると、型(または型の組み合わせ)は、マッチするハンドラを持たないことが許される。 がマッチするハンドラを持たないことも許される。実行時にハンドラのない型に遭遇した場合、 Matchが実行される。 実行時にハンドラのない型に遭遇した場合、MatchExceptionがスローされる。
-betterC でコンパイルされた場合は利用できない。
Returns:
現在保持されている型にマッチするハンドラーから返される値、 その型に対してハンドラーが与えられていれば、そのハンドラーから返される値。
ref auto tryMatch(SumTypes...)(auto ref SumTypes args)
if (allSatisfy!(isSumType, SumTypes) && (args.length > 0));
実際の tryMatch関数である。
Parameters:
SumTypes args 1つ以上のSumTypeオブジェクト。
class MatchException: object.Exception;
未処理の型に遭遇したときにtryMatchによってスローされる。
-betterC と共にコンパイルされた場合は利用できない。
pure nothrow @nogc @safe this(string msg, string file = __FILE__, size_t line = __LINE__);
template canMatch(alias handler, Ts...) if (Ts.length > 0)
handlerTs にマッチする可能性があれば真、そうでなければ偽。
マッチがどのように選択されるかについての完全な説明は、matchのドキュメントを参照のこと。 のドキュメントを参照のこと。
Examples:
alias handleInt = (int i) => "got an int";

assert( canMatch!(handleInt, int));
assert(!canMatch!(handleInt, string));