Giter Site home page Giter Site logo

sumtype's Introduction

sumtype

A sum type for modern D.

Note: Since DMD v2.097.0 (released 2021-06-03), sumtype is part of the standard library, and can be accessed by importing std.sumtype.

Due to differing release schedules, features or bug fixes present in the latest release of sumtype may not be present in std.sumtype. Please refer to the official std.sumtype documentation on dlang.org for information about the standard-library version, and report any issues encountered while using std.sumtype on the official D issue tracker.

Features

  • Pattern matching, including support for introspection-based matching.
  • Self-referential types, using This.
  • Works with pure, @safe, @nogc, nothrow, and immutable.
  • Compatible with -betterC and -dip1000.
  • Zero runtime overhead compared to hand-written C.
    • No heap allocation.
    • Does not rely on runtime type information (TypeInfo).

Documentation

View online on Github Pages.

sumtype uses adrdox to generate its documentation. To build your own copy, run the following command from the root of the sumtype repository:

path/to/adrdox/doc2 --genSearchIndex --genSource -o generated-docs src

Example

import std.math: 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.
pure @safe @nogc nothrow
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.
pure @safe @nogc nothrow
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.
pure @safe @nogc nothrow
bool isFahrenheit(Temperature t)
{
    return t.match!(
        (Fahrenheit f) => true,
        _ => false
    );
}

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

Open on run.dlang.io

Installation

If you're using dub, add the sumtype package to your project as a dependency.

Alternatively, since it's a single, self-contained module, you can simply copy sumtype.d to your source directory and compile as usual.

sumtype's People

Contributors

atilaneves avatar feepingcreature avatar geezee avatar geod24 avatar pbackus avatar wilzbach avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar

sumtype's Issues

Error message on no match type

I was getting this static assert:

Error: static assert:  "handler #1 of type Optional!(Exception) function(return FailureContainer container) pure nothrow @nogc @safe never matches"

And for a while I was trying to analyze the first handler, until I switched them around and saw that handler #0 was the problem, and then I realized it was zero based :)

So, suggestion to use "first, second, third, etc" in that error message.

Granted that would involve a lot more processing from sumtype. So I understand if you just close this as wontfix or something.

`match` cannot infer `@nogc` with non-template handlers

Reviving #24.

void main() @safe @nogc {
    import sumtype;

    int acc = 0;
    // `!(string, int)` is OK...
    SumType!(string, int)(1).match!(
        (string _) => 0,
        (int x) => acc += x,
    );
    // But `!(int, string)` is somehow not!
    SumType!(int, string)(1).match!(
        (string _) => 0,
        (int x) => acc += x,
    );
    // src/main.d(1,6): Error: function D main is @nogc yet allocates closures with the GC
    // src/main.d(13,9):        main.main.__lambda4 closes over variable acc at src/main.d(4,9)
}

Turns out this issue was fixed only for the last SumType’s template parameter.

SumType!(int, bool).match will not compile, depending on argument order

alias Boxed = SumType!(int, bool);
Boxed boxed;
boxed.match!(
    (int n) => "int",
    (bool b) => "bool",
    (string s) => s,
);

Fails to compile with the message:

Error: static assert:  "handler `__lambda3` of type `void delegate(bool b) @safe` never matches"

I believe this because of how the matching algorithm is implementation in matchImpl:

                static foreach (tid, T; Types) {
                    static foreach (hid, handler; handlers) {
                        static if (canMatch!(handler, typeof(self.get!T()))) {
                            if (matches[tid] == noMatch) {
                                matches[tid] = hid;
                            }
                        }
                    }
                }

And canMatch:

enum bool canMatch(alias handler, T) = is(typeof((T arg) => handler(arg)));

My assumption is that canMatch!(handler, typeof(self.get!T())) matches the handler for int to both bool and int due to the implicit conversion of the former to the latter, and thus incorrectly determines that the actual bool handler has no match.

This seems true for any types that can implicitly convert to each-other. Example:

    struct S1
    {
    }

    struct S2
    {
        S1 s1;
        alias s1 this;
    }

    alias S = SumType!(S1, S2);
    S().match!(             //Error
        (S1 s1) => "S1",
        (S2 s2) => "S2",
    );

In both cases, switching the order of the handlers passed to match such that the "subtype" comes first (e.g., bool must come before int and S2 must come before S1) allows the code to compile.

Nested sumtypes and `alias this`

Found a bug while trying to represent an AST with SumTypes. Here’s a reduced example:

alias A = SumType!int;

struct B {
    A a;
    int y;

    alias a this;
}

alias C = SumType!B;

pragma(msg, C.Types); // (SumType!int), i.e. (A), though expected (B).

// This test fails:
@safe unittest {
    auto c = C(B(A(1), 123));
    c.match!((B b) {
        b.a.match!((int x) => assert(x == 1));
        assert(b.y == 123);
    });
    static assert(!__traits(compiles, C(A(1))));

    // This one is questionable.
    // I guess it might introduce some gotchas, so I'd forbid it as well.
    static assert(!__traits(compiles, c.match!(
        (A a) => a.match!((int x) => assert(x == 1)),
    )));
}

Match does not play nice with assert(0)

import sumtype;

void main()
{
    SumType!(int, string) s = 1;
    s.match!(
        (int _) => 3,
        (string _) { assert(0); return 1; },
    );
}

The above won't compile due to compiler error:

onlineapp.d(12,33): Warning: statement is not reachable
dmd failed with exit code 1.

And if you remove the return value it won't compile because of an error in SumType:

.dub/packages/sumtype-0.9.4/sumtype/src/sumtype.d(1341,7): Error: mismatched function return type inference of `void` and `int`
.dub/packages/sumtype-0.9.4/sumtype/src/sumtype.d(1165,14): Error: template instance `sumtype.matchImpl!(cast(Flag)true, function (int _) pure nothrow @nogc @safe => 3, function (string _) pure nothrow @nogc @safe
{
assert(0);
}
).matchImpl!(SumType!(int, string))` error instantiating
onlineapp.d(10,6):        instantiated from here: `match!(SumType!(int, string))`
dmd failed with exit code 1

ReplaceTypeUnless decays enums into basetype

Note: This bug has also been posted on the dlang issue tracker under 20410. I am posting it here for completeness' sake.

ReplaceTypeUnless (and probably ReplaceType) decay enums into their base types. This causes issues with e.g. the Sumtype library, which expects its template argument list to contain no duplicates. (internally sumtype uses ReplaceTypeUnless as well, thereby invalidating its own invariant.)

import std.meta;
import std.typecons;
import std.traits;

enum Enum : string { foo = "Bar" }

alias seq = AliasSeq!(string, Nullable!string, Enum, Nullable!Enum);
alias replaced = ReplaceTypeUnless!(isBoolean, int, uint, seq);

pragma(msg, seq);      // prints: (string, Nullable!string, Enum, Nullable!(Enum)
pragma(msg, replaced); // prints: (string, Nullable!string, string, Nullable!string)

void main() {
}

Does not compile with nested self-referential sumtypes

Using v0.8.6 I get a compilation error when I use nested self-referential SumType types. The smallest program I managed to find that showcases the bug is

struct Node(T) { T left; T right; }
alias Tree = SumType!(int, Node!(This*));
alias TrivialTree = SumType!(Tree); // does not compile
alias Forest = SumType!(Tree, Tree[]); // also does not compile

The problem happens during the replacement of types. At compile time
alias TrivialTree = SumType!(Tree) is treated as alias TrivialTree = SumType!(SumType!(int, Node!(This*))), and This is treated as the outer SumType (not the inner one as is expected).

I managed to implement a fix, albeit dirty, that I will improve on in the upcoming days. Here's the gist of it: The SumType template will take an extra template arg and that will be the type to replace. So the previous script becomes

struct Node(T) { T left; T right; }
struct TreeRef {}
alias Tree = SumType!(TreeRef, int, Node!(TreeRef*));
alias TrivialTree = SumType!(This, Tree); // will compile since Tree does not use This but rather TreeRef
alias Forest = SumType!(This, Tree, Tree[]); // will also work for the same reason

alias TrivialForest = SumType!(This, Forest); // will not compile
alias AnotherTrivialTree = SumType!(TreeRef, Tree); // will not compile

Here's the steps I have in mind for a "better" fix:

  1. rename SumType struct to SelfRefSumType
  2. SelfRefSumType's signature becomesstruct SelfRefSumType(ThisRef, TypeArgs...) (notice the extra ThisRef template argument)
  3. alias SumType(TypeArgs...) = SelfRefSumType!(This, TypeArgs)
  4. unittest

I'm not a huge fan of this fix, I think a better one would have the struct create a fresh dummy var for every sumtype and use that. However I'm not entirely sure how to go about doing that.

Attached is a patch that can be applied on top of commit b6a24cb that fixes the bug (but breaks all previous unittests, hence they're removed). Again, I will submit a better PR once I find some time.

sumtype-0.8.6-patch.txt

SumType doesn't hold a struct that defines a copy-constructor

I get this error:

C:\tools\ldc2-1.27.0-beta2-windows-x64\bin\..\import\std\sumtype.d-mixin-399(399,41): Error: none of the overloads of `__ctor` are callable using a `const` object, candidates are:
****:        `MyStruct.this(Data* data)`
****:        `MyStruct.this(return ref scope Data otherData)`
C:\tools\ldc2-1.27.0-beta2-windows-x64\bin\..\import\std\sumtype.d(395,17): Error: constructor `std.sumtype.SumType!(Data, string).SumType.this` label `__returnLabel` is undefined
C:\tools\ldc2-1.27.0-beta2-windows-x64\bin\..\import\std\sumtype.d-mixin-417(417,45): Error: none of the overloads of `__ctor` are callable using a `immutable` object, candidates are:
****:        `MyStruct.this(Data* data)`
****:        `MyStruct.this(return ref scope Data otherData)`
C:\tools\ldc2-1.27.0-beta2-windows-x64\bin\..\import\std\sumtype.d(413,17): Error: constructor `std.sumtype.SumType!(Data, string).SumType.this` label `__returnLabel` is undefined
****: Error: template instance `std.sumtype.SumType!(Data, string)` error instantiating

The struct defines this copy constructor:

struct MyStruct {
  Data* data
  this(Data* data) @nogc nothrow
  {
	this.data = data;
  }

  this(ref return scope Data otherData) @nogc nothrow
  {
	// ....
  }
  @disable this(this);
}

I am trying to use

SumType!(MyStruct, string)

trying to isolate a DMD bug when sumtype is involved

First of all, this is pretty clearly a DMD issue so feel free to close this without mercy. I'm merely asking here because I am a metaprogramming novice and don't know enough to properly reduce sumtype with dustmite, without losing the subtlety of the bug - ie, it guts the whole file to the point of triggering the error every time, whereas the essence of the bug is that seemingly irrelevant changes, like the location of a class declaration, affect whether SumType is copyable or not.

example: https://gist.github.com/dewf/a27a45b8632866c2cf10d557a18abf4b

I have played a bit with protecting sections from dustmite removal (via DustMiteNoRemoveStart/Stop), but to no avail. For example I got it to the point where commenting a module-level "import std" made the difference between the error or not - but I have no idea if that's normal for D metaprogramming. (Seems odd that a 'static if' would just silently evaluate to false if a symbol were missing, though...)

Anyhow, just wondering if you have any insight on reducing / isolating the part of sumtype involved, without losing the essence of the DMD bug. No expectations - I presume you're a busy man with limited time to look into these sorts of things.

cheers and thanks for writing sumtype in the first place!

Bad error message when having a single handler that's supposed to match everything

Example:

	int someFun(T)(T v)
	{
		static assert(!is(T == int));
		return 3;
	}

	SumType!(int, string, This[]) v;
	v.match!(all => someFun(all));

use-case is that I have a generic (de)serialization function that's supposed to work on every type of my sumtype, but it doesn't for one. The current error message says something like:

/usr/include/dlang/dmd/std/sumtype.d(1998,13): Error: static assert:  "`handlers[0]` of type `template` never matches"
/usr/include/dlang/dmd/std/sumtype.d(1590,52):        instantiated from here: `matchImpl!(SumType!(typeof(null), string, bool, long, double, This[], This[string]))`
source/workspaced/backend.d(132,15):        instantiated from here: `match!(SumType!(typeof(null), string, bool, long, double, This[], This[string]))`
source/workspaced/backend.d(149,22):        instantiated from here: `deepCopy!(SumType!(typeof(null), string, bool, long, double, This[], This[string]))`
source/workspaced/backend.d(136,40):        instantiated from here: `deepCopy!(SumType!(typeof(null), string, bool, long, double, This[], This[string])[string])`
source/workspaced/backend.d(113,30):        instantiated from here: `deepCopy!(Section)`

which is hard to debug.

I propose to add a special case for single-pattern match invocations, where it will just shove it into my custom handler without testing if it's plausible and just let compiler errors break it.

Wrong error message when a handler never matches

In v0.6.4,

import sumtype;

alias Number = SumType!(Int, Double);
struct Int { int i; }
struct Double { double d; }

string myfunc(Number number) {
    return number.match!(
        myfunc,
    );
}

string myfunc(Int i) {
    return "int";
}

string myfunc(Double d) {
    return "double";
}

This fails to compile with error message:

/home/atila/coding/d/sumtype/src/sumtype.d(686,19): Error: none of the overloads of myfunc are callable using argument types (), candidates are:
source/app.d(16,8):        app.myfunc(SumType!(Int, Double) number)
source/app.d(22,8):        app.myfunc(Int i)
source/app.d(26,8):        app.myfunc(Double d)

That's because it's using handler.stringof instead __traits(identifier, handler).

Add a serialization/deserialization example to the documentation

From the discussion on #57:

Can you please show example how to serialize sumtype object and deserialize it to original state (for example to and from abstract text representation)? By now I don't see easy (short) way, it requires many non trivial or not universal code (synchronize methods of getting tag, tag type, tag limits, type by tag in serialization and deserialization parts, when it simply hidden by private).

Any plans on making get public?

Although SumType is meant to be a @safe algebraic type, sometimes we might want to extract the value from it directly. If the user knows what value is contained within SumType, then always resorting to match, its heavy usage might become a little tedious to write. I propose making get, or other function, public so that this can be avoided. Such function needs to be @system as it can lead to undefined behavior.

Pattern match by reference?

Hey, I'm trying to alter a value inside a sumtype, but it doesn't seem to support pattern matching by reference:

alias Value = SumType!(long, double);
auto value = Value(3.14);
value.match!(
    (long) {},
    (ref double d) { d *= 2; } // compile-time error right here
);

The error says, Error: static assert: "No matching handler for type double". Is there a workaround (I couldn't find a way to extract the value inside, even though I know what the type is)?

Regression: Compilation fails with compile-time init of Nullable!T

This fails right now:

unittest {
	import std.typecons;
	static struct A {
		SumType!(Nullable!int) a = Nullable!int.init;
	}
	A a;
}

It was working in 0.8.1. Git bisect revealed the commit that added allowing non-copyable objects (51b3dd8). And the reason it happens is because the constructors call move. I'm not sure what the correct solution is though.

Instead of using forward in the constructors, this would fix the compile error:

static if (isMutable!T && !isCopyable!T) {
	storage = Storage(move(val));
} else {
	storage = Storage(val);
}

But I'm not sure if that's the semantics you want.

[REG 1.0.1] match no longer returns an lvalue

@safe unittest {
	SumType!int x = 123;

	x.match!(ref (ref int n) => n) = 456;
	// Error: match(x) is not an lvalue and cannot be modified

	assert(x.match!((int n) => n == 456));
}

This regression was introduced by PR #50, which inadvertently removed the ref attribute from matchImpl.

Member type with disabled postblit constructor causes compilation to fail

The following code fails to compile:

struct NoCopy
{
    @disable this(this);
}

SumType!NoCopy x;

There are several errors:

  • In Storage's constructor, assigning a NoCopy to values[i] attempts to make a copy, and fails.
  • In SumType's constructor and opAssign, passing a NoCopy by value to Storage's constructor attempts to make a copy, and fails.
  • In SumType's postblit, attempting to call the __xpostblit member of a NoCopy fails compilation, resulting in a "no matching handler" error.

The last error is a consequence of the fact that hasElaborateCopyConstructor evaluates to true for types with disabled postblit constructors. This is a known issue (dlang issue 18628).

SumType fails to replace This[] inside Tuple!()

Using sumtype 1.1.1:

import std.typecons : Tuple;
import sumtype;

alias A = SumType!(Tuple!(This[]));

void main() {}

fails with:

.dub/packages/sumtype-1.1.1/sumtype/src/sumtype.d(281,16): Error: template instance `AliasSeq!(ReplaceTypeUnless!(isSumTypeInstance, This, SumType!(Tuple!(This[])), Tuple!(This[])))` recursive template expansion
/dlang/dmd/linux/bin64/../../src/phobos/std/typecons.d(9037,39): Error: template instance `std.typecons.U!(SumType!(Tuple!(This[]))[])` error instantiating
.dub/packages/sumtype-1.1.1/sumtype/src/sumtype.d(282,3):        instantiated from here: `ReplaceTypeUnless!(isSumTypeInstance, This, SumType!(Tuple!(This[])), Tuple!(This[]))`
onlineapp.d(7,11):        instantiated from here: `SumType!(Tuple!(This[]))`
dmd failed with exit code 1.

Link to run.dlang.io: https://run.dlang.io/is/Vm8AAK

Same code but with This[] outside of Tuple!() works fine

canMatch returns false negative for inout(T)

The following example fails to compile:

struct S {}
alias f = (inout(S) s) => s;
static assert(canMatch!(f, inout(S)));

The cause is this line in canMatchImpl, which attempts to declare a variable of type inout(S):

static if (is(typeof({ T dummy = T.init; realHandler(dummy); }))) {

Change type unification rules for `match` regarding `void`?

I stumble upon the following pattern from time to time:

void processA(A a) { ... }

bool processB(B b, bool someFlag) { ... }

... {
    ...
    x.match!(
        a => processA(a),
        b => processB(b, false), // Error: cannot return non-void from void function.
    );
    ...
}

The first handler returns void, but the second one returns bool, so I have to rewrite it using the longer lambda syntax: (b) { processB(b, false); }. And if the first handler returned non-void, the error message would be, “mismatched function return type inference of void and bool”—which is more specific, but again, it’s an error.

What do you think about such behaviour as, “if at least one of the handlers returns void, then match itself returns void”? It’s not a breaking change. And if a user did want match to return something, but made a mistake and did not return anything from their handler, instead of one of error messages mentioned above they would be told, “expression match(x) is void and has no value”.

Provide convenient access to the type index

This feature was requested on the D forums [1].

It is possible to compute the type index in user code, but the implementation is a bit verbose, and easy to get wrong:

size_t typeIndex(Val)(Val val)
    if (isSumType!Val)
{
    alias QualifiedTypes = CopyTypeQualifiers!(Val, Val.Types);
    return val.match!(v => staticIndexOf!(typeof(v), QualifiedTypes));
}

For both convenience and correctness, it would be better if this functionality were included in the sumtype module.

[1] https://forum.dlang.org/post/[email protected]

Invariants are not checked

struct Nat {
    int value;

    invariant {
        assert(value >= 0);
    }
}

SumType!Nat s;
s.match!((ref n) => n.value = -1);
assert(&s); // Passes; does not run `Nat.invariant`.

I haven’t found a way to determine if a type has a non-trivial invariant, so probably the only solution is to always declare an invariant for SumType. Anyway, it’s not a performance issue since invariants are dropped in -release builds.

Cannot infer `@nogc` when passing a fully typed delegate to `match`

void main() nothrow pure @safe @nogc {
    import sumtype;

    auto s = SumType!int(1);
    int acc = 0; // The variable that is closed over.

    // OK.
    s.match!(x => acc += x);

    // Error: function `test.main` is `@nogc` yet allocates closures with the GC
    s.match!((int x) => acc += x);
}

std.algorithm.iteration.each doesn’t suffer from this problem, so, apparently, it’s a bug in the library and not in the compiler.

I’m currently using the following workaround:

template case_(T, alias f) {
    static if (__traits(compiles, f()))
        auto case_()(ref const T _) { return f(); } // With no argument.
    else static if (__traits(compiles, f(T.init)))
        auto case_()(ref const T x) { return f(x); } // By value.
    else
        auto case_()(ref T x) { return f(x); } // By ref.
}

// A shortcut for a no-op handler.
template case_(T) {
    void case_()(ref const T _) { }
}
// OK.
s.match!(case_!(int, x => acc += x));

Confusing error message when matching immutable or const SumType

immutable SumType!int st;
st.match!(
	(ref int _) => true
);
Error: static assert:  "`handlers[0]` of type `bool function(ref int _) pure nothrow @nogc @safe` never matches"

instantiated from here: `matchImpl!(immutable(SumType!int))`

Trying to match a ref of an immutable or const SumType outputs this error, which makes total sense, however, it took me some time trying to find the issue. I don't know if it's easy to change the error message based on the implementation, but I think it could help others when facing the same problem.

Something like cannot match 'ref' of immutable SumType, perhaps you mean 'const ref'? would be a better straightforward message.

Comparison fails for list of types

Seen in 0.6.3 with dmd 2.083.0. The assertion below fails:

import sumtype;

alias Node = SumType!Struct;

struct Struct {
    Field[] fields;
}

struct Field {
}

void main() {
    assert(Node(Struct([Field()])) == Node(Struct([Field()])));
}

Can't construct inout SumType

inout(SumType!(int[])) example(inout int[] arr)
{
    return inout(SumType!(int[]))(arr);
}

Fails with:

Error: none of the overloads of `__ctor` are callable using a `inout` object
     Candidates are: `sumtype.SumType!(int[]).SumType.this(int[] value)`
                     `sumtype.SumType!(int[]).SumType.this(const(int[]) value)`
                     `sumtype.SumType!(int[]).SumType.this(immutable(int[]) value)`

Can't store scope pointers in a SumType

As of sumtype 1.1.2, the following program fails to compile with -preview=dip1000:

void main() @safe
{
    import std.sumtype;
    int n = 123;
    SumType!(int*) s = &n;
}

The error message is:

Error: reference to local variable n assigned to non-scope parameter value calling std.sumtype.SumType!(int*).SumType.this

DIP 1000 issues when creating a `SumType` from a function argument

nothrow pure @safe @nogc:

alias A = SumType!string;

A createA(string arg) {
    return A(arg);
}

void test() {
    A a = createA("");
}

The code above can be successfully compiled (with -dip1000) on DMD up to and including 2.085. Starting from 2.086, it produces the following error:

returning `SumType(cast(ubyte)0u, Storage(null)).this(arg)` escapes a reference to parameter `arg`, perhaps annotate with `return`

But annotating the argument with return doesn’t help; neither return scope does.

opEquals fails to compile with separate compilation

As of master on commit 7c8ad0c, this fails to compile with dub build --build-mode=singleFile:

// app.d
void main() {
    import node: Node;
    auto n0 = Node();
    auto n1 = Node();
    pragma(msg, typeof(n0 == n1));
}
// node.d
import sumtype;

alias Node = SumType!(Struct, String);

struct Struct {
    string spelling;
    Node[] nodes;
}


struct String {
    string value;
}

`match` does not accept structs that alias themselves to a `SumType`

alias this is a means of implicit conversion in D. Aliasing a type A to its member of type B implies that we want to be able to:

  1. access B’s fields via an A object;
  2. pass an A object to functions that expect a B object.

However, since dfcaa29, match doesn’t follow this convention and requires its argument to be a “real” SumType.


My use case:

struct A { }
struct B { }

alias C = SumType!(A, D*);

// alias D = SumType!(B, C); // Does not compile, we have to declare a struct instead:
struct D {
    SumType!(B, C) value;

    alias value this;
}

Confusing error message for type mismatch in `opEquals`

SumType!int x;
x == 123;

The above code produces the following error message:

src/sumtype.d(587,11): Error: void has no value
src/sumtype.d(587,39): Error: void has no value

This is confusing, because the actual error is that it is not valid to compare a SumType!int with an int.

Construction and assignment from lvalue require exact type match

From https://forum.dlang.org/thread/[email protected]#post-pnufczmiecexaicklnrw:40forum.dlang.org:

Older version of sumtype accept this code:

void main(){
    import sumtype;
    alias Val = SumType!(bool);

    const bool b = true;
    Val val = b;  //fail in newest version
    val = b;      //fail in newest version
}

but new version need exact type:

void main(){
    import sumtype;
    alias Val = SumType!(bool);

    bool b = true;   //cannot be const
    Val val = b;
    val = b;
}

More problems with opEquals

This code fails with an assertion failure:

import sumtype;

void main() {
    assert(
        Node(
            Struct(
                "Outer",
                [
                    Node(Struct("Inner", [ Node(Field(Type(Int()), "x")) ])),
                    Node(Field(Type(UserDefinedType("Inner")), "inner")),
                ],
            )
        )

     ==

        Node(
            Struct(
                "Outer",
                [
                    Node(Struct("Inner", [ Node(Field(Type(Int()), "x")) ])),
                    Node(Field(Type(UserDefinedType("Inner")), "inner")),
                ],
            )
        )
    );
}


alias Node = SumType!(
    Struct,
    Field,
);


struct Struct {
    string spelling;
    Node[] nodes;
}


struct Field {
    Type type;
    string spelling;
}


alias Type = SumType!(
    Int,
    UserDefinedType,
);


struct Int {}

struct UserDefinedType {
    string spelling;
}

Add examples of pattern matching to README

This will REALLY get those functional folks going. As it is, though the README example only binds the whole value!

I would have submitted a PR instead, but...I'm not sure what the syntax would be. :)

SumType.init matches on first type

It's usually the case that one would want sum types so that invalid states are unrepresentable. However:

import sumtype;
import std.stdio;
alias TheSum = SumType!(string, int);
auto s = TheSum();
s.match!((string s) => writeln("string"), (int i) => writeln("int"))

This will print "string". As I see it the options are:

  • Throw an exception if not initialised
  • assert(false) if not initialised
  • Decide between the current behaviour and throwing via a policy template parameter
  • Add a @disable this(); to SumType

What do you think?

std.variant.Algebraic throws a VariantException for the code above.

Member type with copy-constructor fails to compile

Instantiating a SumType with a type that has a defined copy-constructor causes compilation to fail.

This code:

import sumtype;

struct A
{
	int a;

	this (ref typeof(this) source)
	{}
}

alias X = SumType!(A);

results in the following error with sumtype v0.9.4:

.\sumtype.d(1322): Error: static assert:  "handler #0 of type template never matches"
.\sumtype.d(1165):        instantiated from here: matchImpl!(const(SumType!(A)))
.\sumtype.d(371):        instantiated from here: match!(const(SumType!(A)))
.\test.d(11):        instantiated from here: SumType!(A)

Adding another type to the instantiation results in a different error:

struct B
{
	int b;
}

alias Y = SumType!(A, B);
.\sumtype.d(1344): Error: static assert:  "No matching handler for type B"
.\sumtype.d(1165):        instantiated from here: matchImpl!(SumType!(A, B))
.\sumtype.d(358):        instantiated from here: match!(SumType!(A, B))
.\test.d(16):        instantiated from here: SumType!(A, B)

Unit test fails with -dip1000

Compiling with -dip1000 succeeds, but the following unit test fails:

sumtype/src/sumtype.d

Lines 1306 to 1311 in b6a24cb

assert(!__traits(compiles, () @safe {
x.match!(
(ref int n) => &n,
_ => null,
);
}));

Using DMD v2.085.1:

dmd -debug -main -unittest -dip1000 sumtype.d

When compiling without -dip1000, the test passes.

Remove check for handlers that never match?

Hey, I was just thinking of being able to do this:

template match(handlers...) {
    auto match(T)(auto ref T value) {
        import sumtype: SumType, match;
        return SumType!T(value).match!handlers;
    }
}

So that match can be used as a static type switch statement: e.g.

auto r0 = a.match!(
    (string a) => false,
    (int a) => true,
    (float a) => false
);

assert(r0);

What do you think?

No longer works with recursive types with separate compilation

The code below fails to compile:

// app.d
import type;
// type.d
import sumtype;

alias Type = SumType!(Bool, Pointer);

struct Bool {}

struct Pointer {
    Type* pointeeType;
}
% dmd -c -I~/.dub/packages/sumtype-0.6.4/sumtype/src app.d
/usr/include/dlang/dmd/std/traits.d(2678): Error: unable to determine fields of Pointer because of forward references
/usr/include/dlang/dmd/std/traits.d(3760): Error: template instance `std.traits.FieldTypeTuple!(Pointer)` error instantiating
/usr/include/dlang/dmd/std/meta.d(887):        instantiated from here: F!(Pointer)
/usr/include/dlang/dmd/std/meta.d(893):        instantiated from here: anySatisfy!(hasElaborateDestructor, Pointer)
/home/atila/.dub/packages/sumtype-0.6.4/sumtype/src/sumtype.d(303):        instantiated from here: anySatisfy!(hasElaborateDestructor, Bool, Pointer)
type.d(4):        instantiated from here: SumType!(Bool, Pointer)

It works fine if the two files are compiled together.

This regression was introduced in this commit:

commit ed05b67ee6f338aaad50175e258c88f51b5cb641
Author: Paul Backus <[email protected]>
Date:   Sun Aug 5 17:35:12 2018 -0400

    Handle types with destructors correctly
    
    std.algorithm.mutation.moveEmplace is used in opAssign as a workaround
    for dlang issue 19150.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.