August 29, 2021

On Sunday, 29 August 2021 at 19:19:54 UTC, SealabJaster wrote:

>

On Sunday, 29 August 2021 at 15:05:57 UTC, Paul Backus wrote:

>

...

It is also of course totally possible I'm not using sumtypes right, but I'm frequently in situations such as this:

private string expectSingleIdentifier(Token tok, Expression[] exp)
{
    string ret;
    exp[0].match!(
        (IdentifierExpression exp) { ret = exp.ident; },
        (_) { throw new Exception(...); }
    );
    return ret;
}

For cases like this you can use tryMatch, which does not require exhaustiveness and throws an exception at runtime if it encounters a type with no handler:

return exp[0].tryMatch!(
    (IdentifierExpression exp) => exp.ident
);
>

While in this case I still have to go through match in order to access the value, sometimes I simply want to do a type check, and going through match seems a bit overkill.

I guess it's just a niche case (wanting to 'abort' on a bad value rather than keep going) I keep running into, likely a sign I need to change my mindset rather than anything else.

Or I'm just using it as a wrong abstraction >;3

If "abort unless this SumType contains a particular type" is a common pattern in your code I would suggest extracting it into a helper function:

void require(T, S)(S s)
    if (isSumType!S)
{
    s.match!(
        (T t) {}
        (_) { assert(0); }
    );
}

But in general, I think this is the wrong approach. What I would do instead is create a helper function like the following:

Optional!T maybeGet(T, S)(S s)
    if (isSumType!S)
{
    return s.match!(
        (T t) => some(t),
        _ => no!T;
    );
}

This narrows the set of cases you have to deal with from "anything that could be in the SumType" to "either T or not T", and from there you can handle the "not T" case however you want--abort, throw an exception, etc.

The general principle here is "Parse, don't validate."

>

In this specific case as well I don't believe I can use return match!(...) because I can't return anything in the "bad" case, since I'd then get an unreachable code error. Very minor yet annoying thing, but that's more a language issue rather than something inherent to SumType.

There is actually a workaround for this:

return exp[0].match!(
    (IdentifierExpression exp) => exp.ident,
    function string (_) { throw new Exception("..."); }
);

Note the explicit return type of string on the second lambda. The compiler will allow this, even though the lambda never returns a string, because it never returns normally at all.

August 29, 2021

On Sunday, 29 August 2021 at 19:29:29 UTC, SealabJaster wrote:

>

On Sunday, 29 August 2021 at 19:19:54 UTC, SealabJaster wrote:

>

...

While I'm on a tiny rant, I'd like to just mention that compiler errors with SumType can be a bit obtuse.

It's often annoying when you're using a generic case but due to a compiler error that the SumType eats up, all you get is "handler[x] is never matched" or "type X is unhandled", etc.

I agree, this is annoying. My current best solution to this is:

  1. Recompile with -verrors=spec and redirect the compiler's output to a file.
  2. grep the file for error messages whose location is in one of my source files.
  3. Search the results of that grep for the actual error that caused things to fail.

By the way, this process works for anything that fails because of errors in a __traits(compiles) or typeof(...) check, not just match.

August 29, 2021

On Sunday, 29 August 2021 at 19:51:30 UTC, Paul Backus wrote:

>
return exp[0].tryMatch!(
    (IdentifierExpression exp) => exp.ident
);

That's pretty much perfect, thanks!

>

But in general, I think this is the wrong approach. What I would do instead is create a helper function like the following:

Optional!T maybeGet(T, S)(S s)
    if (isSumType!S)
{
    return s.match!(
        (T t) => some(t),
        _ => no!T;
    );
}

This narrows the set of cases you have to deal with from "anything that could be in the SumType" to "either T or not T", and from there you can handle the "not T" case however you want--abort, throw an exception, etc.

The general principle here is ["Parse, don't validate."][1]

An interesting way to think about things, which I assume is from your apparent experience with functional languages? There's definitely a lot to learn from them, even with my limited experience with things like F#.

>
return exp[0].match!(
    (IdentifierExpression exp) => exp.ident,
    function string (_) { throw new Exception("..."); }
);

I didn't even realise that syntax existed, TIL.

>

I agree, this is annoying. My current best solution to this is:

Glad it's not just me, another reason we need this: https://forum.dlang.org/post/rj5hok$c6q$1@digitalmars.com

Thanks for your thorough answers.

August 29, 2021

On Sunday, 29 August 2021 at 19:51:30 UTC, Paul Backus wrote:

>
return exp[0].match!(
    (IdentifierExpression exp) => exp.ident,
    function string (_) { throw new Exception("..."); }
);

Note the explicit return type of string on the second lambda. The compiler will allow this, even though the lambda never returns a string, because it never returns normally at all.

Can't wait until DIP1034 is fully implemented so one needn't do this.

August 29, 2021

On Sunday, 29 August 2021 at 19:51:30 UTC, Paul Backus wrote:

>

On Sunday, 29 August 2021 at 19:19:54 UTC, SealabJaster wrote:

>

[...]

For cases like this you can use tryMatch, which does not require exhaustiveness and throws an exception at runtime if it encounters a type with no handler:

return exp[0].tryMatch!(
    (IdentifierExpression exp) => exp.ident
);
>

[...]

If "abort unless this SumType contains a particular type" is a common pattern in your code I would suggest extracting it into a helper function:

void require(T, S)(S s)
    if (isSumType!S)
{
    s.match!(
        (T t) {}
        (_) { assert(0); }
    );
}

But in general, I think this is the wrong approach. What I would do instead is create a helper function like the following:

Optional!T maybeGet(T, S)(S s)
    if (isSumType!S)
{
    return s.match!(
        (T t) => some(t),
        _ => no!T;
    );
}

This narrows the set of cases you have to deal with from "anything that could be in the SumType" to "either T or not T", and from there you can handle the "not T" case however you want--abort, throw an exception, etc.

The general principle here is "Parse, don't validate."

>

[...]

There is actually a workaround for this:

return exp[0].match!(
    (IdentifierExpression exp) => exp.ident,
    function string (_) { throw new Exception("..."); }
);

Note the explicit return type of string on the second lambda. The compiler will allow this, even though the lambda never returns a string, because it never returns normally at all.

I have used something like the following (although I am finding I use this type of thing less the longer I use sumtype):

import std;

struct A {}
struct B {}

alias Sum = SumType!(A,B);

T asserter(T)(string errMsg,string file =__FILE__,ulong line=__LINE__)
{
    assert(0,format("%s(%s): %s",file,line,errMsg));
}

T as(T,S)(S s, string file=__FILE__,ulong line=__LINE__) if(isSumType!S)
{
    return s.match!((T t)=>t,_=>asserter!T("Sum contains unexpected type",file,line));
}

void main()
{
    Sum s = Sum(A.init);
    auto a = s.as!A;
    writeln(a);
    auto b = s.as!B;
}
August 30, 2021

On Sunday, 29 August 2021 at 17:49:05 UTC, Paul Backus wrote:

>

staticIndexOf returns -1 when the type is not found, and 255 is what you get when you cast -1 to ubyte.

Yes, I understand, but it wasn't problem.

>

Note that this version has [the problem you complained about before][1] where it does not distinguish between the int[] and the const(int[]) members of a const(SumType!(int[], const(int[]))). This is unavoidable--match itself is not capable of distinguishing between those members, so anything that uses match will also fail to do so.

If this is still a problem for you, you can strip off the qualifier from the SumType before calling typeIndex:

        return cast(TaggedTagType!(T))((cast(Unqual!T) val).typeIndex);

No problem with typeIndex, but std.sumtype have no typeIndex.

If I understand correctly problem must resolve by removing const before match.

Is this code will be correct?

auto typeIndex(T)(in T val) if (is(T == SumType!Args, Args...))
{
  return match!(v => staticIndexOf!(typeof(v), T.Types))(*(cast(T*)&val));
}

In what cases I get different result between your version with CopyTypeQualifiers and my?

August 30, 2021
30.08.2021 13:20, Oleg B пишет:
> 
> ```d
> auto typeIndex(T)(in T val) if (is(T == SumType!Args, Args...))
> {
>    return match!(v => staticIndexOf!(typeof(v), T.Types))(*(cast(T*)&val));
> }
> ```

A little tip: you can use just `cast()` to remove qualifiers:
```D
return match!(v => staticIndexOf!(typeof(v), T.Types))(cast()val));
```
August 30, 2021

On Monday, 30 August 2021 at 10:50:40 UTC, drug wrote:

>

A little tip: you can use just cast() to remove qualifiers:

return match!(v => staticIndexOf!(typeof(v), T.Types))(cast()val));

I'm not sure what with simple cast() not opCast nor ctors are called, it depends on code of SumType, if it changed they can be called (not sure). Cast through pointer avoid it. Or I mistake?

August 30, 2021

On Monday, 30 August 2021 at 11:19:46 UTC, Oleg B wrote:

>

On Monday, 30 August 2021 at 10:50:40 UTC, drug wrote:

>

A little tip: you can use just cast() to remove qualifiers:

return match!(v => staticIndexOf!(typeof(v), T.Types))(cast()val));

I'm not sure what with simple cast() not opCast nor ctors are called, it depends on code of SumType, if it changed they can be called (not sure). Cast through pointer avoid it. Or I mistake?

SumType does not define any opCast overloads, so you don't have to worry about that.

Re: ctors/copying, the language spec for casts says:

>
  1. Casting to a CastQual replaces the qualifiers to the type of the UnaryExpression.

  2. Casting with no Type or CastQual removes any top level const, immutable, shared or inout type modifiers from the type of the UnaryExpression.

In other words, casting to a different qualifier does not create a new value; it just causes the compiler to treat the existing value as though it had a different type.

August 30, 2021

On Monday, 30 August 2021 at 12:13:56 UTC, Paul Backus wrote:

>

On Monday, 30 August 2021 at 11:19:46 UTC, Oleg B wrote:

>

On Monday, 30 August 2021 at 10:50:40 UTC, drug wrote:

>

A little tip: you can use just cast() to remove qualifiers:

return match!(v => staticIndexOf!(typeof(v), T.Types))(cast()val));

I'm not sure what with simple cast() not opCast nor ctors are called, it depends on code of SumType, if it changed they can be called (not sure). Cast through pointer avoid it. Or I mistake?

SumType does not define any opCast overloads, so you don't have to worry about that.

Re: ctors/copying, the language spec for casts says:

>
  1. Casting to a CastQual replaces the qualifiers to the type of the UnaryExpression.

  2. Casting with no Type or CastQual removes any top level const, immutable, shared or inout type modifiers from the type of the UnaryExpression.

In other words, casting to a different qualifier does not create a new value; it just causes the compiler to treat the existing value as though it had a different type.

Thanks!

1 2 3 4 5 6 7 8
Next ›   Last »