www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - What's wrong with std.variant.Variant?

reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
I was curious about collecting a list of grievances about Variant. It's 
the oldest piece of generic code in std, and it predates a lot of good 
language additions.

So what's wrong with Variant? One thing I collected from a coworker is 
that it doesn't work with Windows DLLs, because in turn typeof() 
comparison does not work across Windows DLLs.

What are other problems with it?
Jun 13
next sibling parent reply user1234 <user1234 12.de> writes:
On Saturday, 13 June 2020 at 19:10:04 UTC, Andrei Alexandrescu 
wrote:
 I was curious about collecting a list of grievances about 
 Variant. It's the oldest piece of generic code in std, and it 
 predates a lot of good language additions.

 So what's wrong with Variant? One thing I collected from a 
 coworker is that it doesn't work with Windows DLLs, because in 
 turn typeof() comparison does not work across Windows DLLs.

 What are other problems with it?
IIRC another is the use of typeid() instead of an enum to discriminate the store
Jun 13
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/13/20 3:38 PM, user1234 wrote:
 On Saturday, 13 June 2020 at 19:10:04 UTC, Andrei Alexandrescu wrote:
 I was curious about collecting a list of grievances about Variant. 
 It's the oldest piece of generic code in std, and it predates a lot of 
 good language additions.

 So what's wrong with Variant? One thing I collected from a coworker is 
 that it doesn't work with Windows DLLs, because in turn typeof() 
 comparison does not work across Windows DLLs.

 What are other problems with it?
IIRC another is the use of typeid() instead of an enum to discriminate the store
It can't use an enum because it's open-ended (must accommodate any type). An enum would work for Algebraic but to consolidate implementations both use the same mechanism.
Jun 13
prev sibling next sibling parent reply Johannes Pfau <nospam example.com> writes:
Am Sat, 13 Jun 2020 15:10:04 -0400 schrieb Andrei Alexandrescu:


 So what's wrong with Variant? One thing I collected from a coworker is
 that it doesn't work with Windows DLLs, because in turn typeof()
 comparison does not work across Windows DLLs.
I think you mean typeid? This is not Variant's fault though, we really need full DLL support in general...
 What are other problems with it?
I never had any real problems with std.variant, but I have some smaller nits: * Does the Algebraic implementation use typeid/TypeInfo? I think that's not documented and I'd prefer a numeric/enum tag * Memory Management is not documented Algebraic has to compete with https://code.dlang.org/packages/ taggedalgebraic which has some more features. I never really liked the Agelbraic APIs (phobos and TaggedAlgebraic: casting to get values / explicit get calls etc.). I prefer something with a union-like interface like TaggedUnion: union Base { int a; string b; } alias Type = TaggedUnion!Base; Type t = Type(0); t.a = 42; t.b = "foo"; writeln(t.b); if (t.kind == Type.Kind.Foo) if (t.isFoo)... -- Johannes
Jun 13
parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 6/13/20 3:42 PM, Johannes Pfau wrote:
 Am Sat, 13 Jun 2020 15:10:04 -0400 schrieb Andrei Alexandrescu:
 
 
 So what's wrong with Variant? One thing I collected from a coworker is
 that it doesn't work with Windows DLLs, because in turn typeof()
 comparison does not work across Windows DLLs.
I think you mean typeid? This is not Variant's fault though, we really need full DLL support in general...
Yes, typeid, thanks.
 What are other problems with it?
I never had any real problems with std.variant, but I have some smaller nits: * Does the Algebraic implementation use typeid/TypeInfo? I think that's not documented and I'd prefer a numeric/enum tag
I think so. It was just for the sake of reusing the same codebase for Algebraic and Variant. An enum might save space in some instances, but in most cases it simply eats a word anyway. But, space is important so this should go on the list. E.g. there should be no reason Algebraic!(char, byte, ubyte) should eat more than 2 bytes.
 * Memory Management is not documented
Yah, that whole thing with dynamic allocation and eager copying of types greater than a threshold.
 Algebraic has to compete with https://code.dlang.org/packages/
 taggedalgebraic which has some more features. I never really liked the
 Agelbraic APIs (phobos and TaggedAlgebraic: casting to get values /
 explicit get calls etc.). I prefer something with a union-like interface
 like TaggedUnion:
 
 union Base
 {
      int a;
      string b;
 }
 
 alias Type = TaggedUnion!Base;
 
 Type t = Type(0);
 t.a = 42;
 t.b = "foo";
 writeln(t.b);
 if (t.kind == Type.Kind.Foo)
 if (t.isFoo)...
Interesting, so members should receive names much like they do in std.tuple. Cool. I think this could be done. Andrei
Jun 13
parent welkam <wwwelkam gmail.com> writes:
On Saturday, 13 June 2020 at 19:51:51 UTC, Andrei Alexandrescu 
wrote:
 should eat more than 2 bytes.
He he
Jun 13
prev sibling next sibling parent Paul Backus <snarwin gmail.com> writes:
On Saturday, 13 June 2020 at 19:10:04 UTC, Andrei Alexandrescu 
wrote:
 I was curious about collecting a list of grievances about 
 Variant. It's the oldest piece of generic code in std, and it 
 predates a lot of good language additions.

 So what's wrong with Variant? One thing I collected from a 
 coworker is that it doesn't work with Windows DLLs, because in 
 turn typeof() comparison does not work across Windows DLLs.

 What are other problems with it?
Here's what I found searching for "variant" on bugzilla: https://issues.dlang.org/show_bug.cgi?id=10223 https://issues.dlang.org/show_bug.cgi?id=12944 https://issues.dlang.org/show_bug.cgi?id=11864 https://issues.dlang.org/show_bug.cgi?id=12997 https://issues.dlang.org/show_bug.cgi?id=14062 https://issues.dlang.org/show_bug.cgi?id=15615 https://issues.dlang.org/show_bug.cgi?id=18780 https://issues.dlang.org/show_bug.cgi?id=18339 https://issues.dlang.org/show_bug.cgi?id=18183 https://issues.dlang.org/show_bug.cgi?id=20793 https://issues.dlang.org/show_bug.cgi?id=20666 https://issues.dlang.org/show_bug.cgi?id=20360 https://issues.dlang.org/show_bug.cgi?id=20030 Common themes: - issues with type conversions (esp. regarding const) - doesn't work with D's static analysis features ( safe, pure, etc.) - incorrect behavior for large types that require boxing
Jun 13
prev sibling next sibling parent Mathias LANG <geod24 gmail.com> writes:
On Saturday, 13 June 2020 at 19:10:04 UTC, Andrei Alexandrescu 
wrote:
 I was curious about collecting a list of grievances about 
 Variant. It's the oldest piece of generic code in std, and it 
 predates a lot of good language additions.

 So what's wrong with Variant? One thing I collected from a 
 coworker is that it doesn't work with Windows DLLs, because in 
 turn typeof() comparison does not work across Windows DLLs.

 What are other problems with it?
I tried to use it a while ago and figured rolling out my own would be better (https://github.com/Geod24/minivariant). I've seen other (and probably better) implementations out there, but I mostly use this one because I'm familiar with the tradeoff. I think most people want a tagged union over a generic variant type, however things are built the other way around: the tagged union is built on the generic variant type. Building a typed union over a type that relies on type erasure means Algebraic has to reimplement all the conversions rules that would normally be done by the compiler, e.g. assigning `immutable(uint)` to a `ulong` should just work, but it does not.
Jun 13
prev sibling next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Saturday, 13 June 2020 at 19:10:04 UTC, Andrei Alexandrescu 
wrote:
 So what's wrong with Variant? One thing I collected from a 
 coworker is that it doesn't work with Windows DLLs, because in 
 turn typeof() comparison does not work across Windows DLLs.

 What are other problems with it?
I have used both `std.variant` and DUB package Taggedalgebraic in a real project, so I think I can testify. There is nothing absolutely wrong about Phobos variant -it is definitely an improvement over `union`s or `void[someSize]` in the general case. If I wanted to make a variant without pre-specifying the types, I'd still consider it (through I'd most likely check the Tardy library Atila just announced first). But all my use cases have been ones where I can pre-provide a list. The typical thing I want to do is to either return a normal value or an error value. I personally don't like exceptions because I want to give the calling code an option to treat errors as normal low-level results without performance implications. For that use, `std.variant` proved itself serviceable, but not optimal. The first problem is that it always has one separate `null` value. I want to be able to specify an arbitrary number of different null values, or to have none at all. `taggedalgebraic.TaggedUnion` lets me do this, with the `Void` type provided by the package. The second problem is that standard variant will not work with any of the attributes. `TaggedUnion` neither works with `nothrow` or ` nogc`, but at least it has no problems with ` safe` and `pure`. The latter two are more important anyway. And finally, with `TaggedUnion` the union can contain the same type two times, being still separate by it's tag value. If I want a tagged version of this: ``` union PictureSize { int[2] inPixels; float[2] inMeters; float[2] inSourceSizes; } ``` ...I can't do that with the standard variant.
Jun 14
parent reply Paul Backus <snarwin gmail.com> writes:
On Sunday, 14 June 2020 at 17:31:32 UTC, Dukc wrote:
 The second problem is that standard variant will not work with 
 any of the attributes. `TaggedUnion` neither works with 
 `nothrow` or ` nogc`, but at least it has no problems with 
 ` safe` and `pure`. The latter two are more important anyway.
FYI, SumType works with nothrow and nogc, and can solve all of your other problems with std.variant as well. :) Duplicate types are not supported out-of-the-box, but can very easily be accomplished using `std.typecons.Typedef`: alias InPixels = Typedef!(int[2], int[2].init, "InPixels"); alias InMeters = Typedef!(float[2], float[2].init, "InMeters"); alias SourceSizes = Typedef!(float[2], float[2].init, "SourceSizes"); alias PictureSize = SumType!(InPixels, InMeters, SourceSizes);
Jun 14
next sibling parent reply Dukc <ajieskola gmail.com> writes:
On Sunday, 14 June 2020 at 18:04:57 UTC, Paul Backus wrote:
 FYI, SumType works with nothrow and  nogc, and can solve all of 
 your other problems with std.variant as well. :)

 Duplicate types are not supported out-of-the-box, but can very 
 easily be accomplished using `std.typecons.Typedef`:

     alias InPixels = Typedef!(int[2], int[2].init, "InPixels");
     alias InMeters = Typedef!(float[2], float[2].init, 
 "InMeters");
     alias SourceSizes = Typedef!(float[2], float[2].init, 
 "SourceSizes");
     alias PictureSize = SumType!(InPixels, InMeters, 
 SourceSizes);
Supporting all attributes definitely sounds good. OTOH your example isn't as DRY as with Taggedalgebraic: ``` alias PictureSize = TaggedUnion!PictureSize_; union PictureSize_ { int[2] inPixels; float[2] inMeters; float[2] inSourceSizes; } ``` In essence, the problem is that I want an union with a customizable tag, not an afterwards defined "base class" for the types. I'd check Atila's library first for the latter. But I definitely could adapt `SumType` for my use with some extra wrapping.
Jun 14
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 15 June 2020 at 04:08:14 UTC, Dukc wrote:
 Supporting all attributes definitely sounds good. OTOH your 
 example isn't as DRY as with Taggedalgebraic:

 ```
 alias PictureSize = TaggedUnion!PictureSize_;
 union PictureSize_
 {   int[2] inPixels;
     float[2] inMeters;
     float[2] inSourceSizes;
 }
 ```

 In essence, the problem is that I want an union with a 
 customizable tag, not an afterwards defined "base class" for 
 the types. I'd check Atila's library first for the latter. But 
 I definitely could adapt `SumType` for my use with some extra 
 wrapping.
Personally I would say it is more DRY, because there's no need to declare a superfluous `union` type for your tagged version to "inherit" from. But really this is just a matter of preference. I'm not sure what you mean by 'base class.' Are you referring to the lack of built-in support for named members? It would not be terribly difficult to add a syntax for named SumType members similar to std.typecons.Tuple, so that you could write SumType!( int[2], "InPixels", float[2], "InMeters", float[2], "InSourceSizes" ) ...and have SumType take care of all the `Typedef` stuff for you. So far I've held off on it because I figured it was easy enough already to do by hand, but if this is a feature that a lot of people want, I can certainly add it.
Jun 15
parent reply Dukc <ajieskola gmail.com> writes:
On Monday, 15 June 2020 at 12:41:03 UTC, Paul Backus wrote:
 I'm not sure what you mean by 'base class.' Are you referring 
 to the lack of built-in support for named members? It would not 
 be terribly difficult to add a syntax for named SumType members 
 similar to std.typecons.Tuple, so that you could write
I understood correctly, SumType automatically implements the member functions that all the member types do. In that regard it becomes a kind of base class for all the types specified. Thats also what the `TaggedAlgebraic` types of the Taggedalgebraic package (as opposed to `TaggedUnion`s) does IIRC. But as you can imagine, it isn't necessarily a good thing for my `PictureSize` example. Consider `aPictureSize[1] += 10;`. If I did that, it is unlikely that I wanted to add 10 to picture height, regardless of the units. More likely scenario is that I forgot to specify the units I'm assuming `PictureSize` to be in. Thus, I prefer a compiler error when it happens.
 ...and have SumType take care of all the `Typedef` stuff for 
 you. So far I've held off on it because I figured it was easy 
 enough already to do by hand, but if this is a feature that a 
 lot of people want, I can certainly add it.
I think it's just about how you think about the type tag. If you think it just as an internal bookkeeper of what type is in, your current design is good. But if you want to base program logic directly on the tag value, then you get the opinions I have. Check Rust's `enum` types, they are a perfect example of my school of thought. So if you want, you could do like Taggedalgebraic and have two types, one for each school. On the other hand, there is nothing wrong in just picking the audience and let others look for something else.
Jun 15
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 15 June 2020 at 16:06:15 UTC, Dukc wrote:
 On Monday, 15 June 2020 at 12:41:03 UTC, Paul Backus wrote:
 I'm not sure what you mean by 'base class.' Are you referring 
 to the lack of built-in support for named members? It would 
 not be terribly difficult to add a syntax for named SumType 
 members similar to std.typecons.Tuple, so that you could write
I understood correctly, SumType automatically implements the member functions that all the member types do. In that regard it becomes a kind of base class for all the types specified. Thats also what the `TaggedAlgebraic` types of the Taggedalgebraic package (as opposed to `TaggedUnion`s) does IIRC.
SumType does not implement any member functions other than the ones listed in its documentation [1]: opAssign, opEquals, toString, and toHash. I am curious how you arrived at this misconception. Is there a way I can improve SumType's documentation to make its behavior in this regard clearer? [1] https://pbackus.github.io/sumtype/sumtype.SumType.html
 ...and have SumType take care of all the `Typedef` stuff for 
 you. So far I've held off on it because I figured it was easy 
 enough already to do by hand, but if this is a feature that a 
 lot of people want, I can certainly add it.
I think it's just about how you think about the type tag. If you think it just as an internal bookkeeper of what type is in, your current design is good. But if you want to base program logic directly on the tag value, then you get the opinions I have. Check Rust's `enum` types, they are a perfect example of my school of thought.
In both Rust's `enum` types and SumType, you cannot access the tag directly. Instead, you must use pattern-matching.
Jun 15
parent reply Dukc <ajieskola gmail.com> writes:
On Monday, 15 June 2020 at 16:30:17 UTC, Paul Backus wrote:
 SumType does not implement any member functions other than the 
 ones listed in its documentation [1]: opAssign, opEquals, 
 toString, and toHash.

 I am curious how you arrived at this misconception.
By cutting corners when doing research. Sorry.
 Is there a way I can improve SumType's documentation to make 
 its behavior in this regard clearer?
It's not documentations fault. But if you want to supersede Taggedalgebraic really hard, you can try convincing Mike to let you write a project highlight in the D blog, to raise awareness for thickskulls like me :D.
 In Rust's `enum` types you cannot access the tag directly.
Again bad research from my part. But it's something I want to do. Why? `final switch` statements. I may want to `return` or `break` directly from the switch, and in that case I can't use pattern matching without extra thinking. Well, on D anyway. I don't know about Rust.
Jun 15
parent reply Paul Backus <snarwin gmail.com> writes:
On Monday, 15 June 2020 at 21:48:19 UTC, Dukc wrote:
 Is there a way I can improve SumType's documentation to make 
 its behavior in this regard clearer?
It's not documentations fault. But if you want to supersede Taggedalgebraic really hard, you can try convincing Mike to let you write a project highlight in the D blog, to raise awareness for thickskulls like me :D.
That's an interesting idea. I'll look into it after the upcoming 1.0.0 release.
 In Rust's `enum` types you cannot access the tag directly.
Again bad research from my part. But it's something I want to do. Why? `final switch` statements. I may want to `return` or `break` directly from the switch, and in that case I can't use pattern matching without extra thinking. Well, on D anyway. I don't know about Rust.
If it's not too much trouble, can you give an example of the kind of thing you'd like to be able to do? I suspect it's possible with SumType, but it may not be obvious how, which makes it a good candidate for the documentation's "examples" section.
Jun 16
next sibling parent reply Simen =?UTF-8?B?S2rDpnLDpXM=?= <simen.kjaras gmail.com> writes:
On Tuesday, 16 June 2020 at 12:21:56 UTC, Paul Backus wrote:
 On Monday, 15 June 2020 at 21:48:19 UTC, Dukc wrote:
 Is there a way I can improve SumType's documentation to make 
 its behavior in this regard clearer?
It's not documentations fault. But if you want to supersede Taggedalgebraic really hard, you can try convincing Mike to let you write a project highlight in the D blog, to raise awareness for thickskulls like me :D.
That's an interesting idea. I'll look into it after the upcoming 1.0.0 release.
 In Rust's `enum` types you cannot access the tag directly.
Again bad research from my part. But it's something I want to do. Why? `final switch` statements. I may want to `return` or `break` directly from the switch, and in that case I can't use pattern matching without extra thinking. Well, on D anyway. I don't know about Rust.
If it's not too much trouble, can you give an example of the kind of thing you'd like to be able to do? I suspect it's possible with SumType, but it may not be obvious how, which makes it a good candidate for the documentation's "examples" section.
I'm not Dukc, but here's an example I came up with: float myFun() { MyTaggedUnion!(float, int, string, int[]) a = fun(); float value; final switch (a.type) { case a._float: return a.as!float(); case a._int: return a.as!int(); case a._string: value = a.as!string().process(); case a._intArray: value = a.as!(int[]).process(); } // Lots of code that manipulates 'value'. if (a.type == a._string) { // Do something special for only one of the types, // but after doing lots of common things } // More code that manipulates 'value'. return value; } Essentially, there's shortcut returns for a few cases, but the others have some common behavior, even better if this is slightly different in one single place for one single held type, so you can't really factor it out as a separate function. -- Simen
Jun 16
next sibling parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 16 June 2020 at 12:44:54 UTC, Simen Kjærås wrote:
 On Tuesday, 16 June 2020 at 12:21:56 UTC, Paul Backus wrote:
 If it's not too much trouble, can you give an example of the 
 kind of thing you'd like to be able to do? I suspect it's 
 possible with SumType, but it may not be obvious how, which 
 makes it a good candidate for the documentation's "examples" 
 section.
I'm not Dukc, but here's an example I came up with: float myFun() { MyTaggedUnion!(float, int, string, int[]) a = fun(); float value; final switch (a.type) { case a._float: return a.as!float(); case a._int: return a.as!int(); case a._string: value = a.as!string().process(); case a._intArray: value = a.as!(int[]).process(); } // Lots of code that manipulates 'value'. if (a.type == a._string) { // Do something special for only one of the types, // but after doing lots of common things } // More code that manipulates 'value'. return value; } Essentially, there's shortcut returns for a few cases, but the others have some common behavior, even better if this is slightly different in one single place for one single held type, so you can't really factor it out as a separate function.
I wonder how that final switch would look like if the tagged union contains a type such as Some!(Other!(immutable(int)[]), 3). I.e. how would you spell out the case :)
Jun 16
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Tuesday, 16 June 2020 at 12:44:54 UTC, Simen Kjærås wrote:
 I'm not Dukc, but here's an example I came up with:
[...]
 Essentially, there's shortcut returns for a few cases, but the 
 others have some common behavior, even better if this is 
 slightly different in one single place for one single held 
 type, so you can't really factor it out as a separate function.

 --
   Simen
I think this is an instructive example, because it illustrates one of the key differences between functional-style code and imperative-style code: explicit coupling via funcion parameters and return values vs. implicit coupling via shared state. Here's how I would write it: float common1(float value) { // Lots of code that manipulates 'value' } float common2(float value) { // More code that manupulates 'value' } float special(string s, float value) { // Do something special for only one of the types, // but after doing lots of common things } float myFun(SumType!(float, int, string, int[]) a) { import std.functional: pipe; return a.match!( number => cast(float) number, (string s) => s.process .common1 .pipe!(value => special(s, value)) .common2 (int[] ia) => ia.process .common1 .common2 ); } You'll notice that I've taken the liberty of extracting each arbitrary "chunk" of code indicated by a comment into its own function. In doing so, I've had to make the inputs and outputs of those chunks explicit (and make some assumptions about what they are). While this is a bit of extra work up front, I think it makes the end result easier to understand--not to mention easier to unit test. Exercise for the reader: how can this code be further refactored to eliminate the duplication between the string and int[] cases? (Solution: https://gist.github.com/pbackus/deda874eeddf587d938cb5d6213a0b84)
Jun 16
parent reply Dukc <ajieskola gmail.com> writes:
On Tuesday, 16 June 2020 at 16:26:14 UTC, Paul Backus wrote:
 Here's how I would write it:

     float common1(float value)
     {
         // Lots of code that manipulates 'value'
     }

     float common2(float value)
     {
         // More code that manupulates 'value'
     }

     float special(string s, float value)
     {
         // Do something special for only one of the types,
         // but after doing lots of common things
     }

     float myFun(SumType!(float, int, string, int[]) a)
     {
         import std.functional: pipe;

         return a.match!(
             number => cast(float) number,
             (string s) =>
                 s.process
                     .common1
                     .pipe!(value => special(s, value))
                     .common2
             (int[] ia) =>
                 ia.process
                     .common1
                     .common2
         );
     }
Despite my need to use the tag value directly, I do agree that this style should be preferable. I am not generally very fond of littering code with extra variables, that converting `switch`es directly to `match`s or `visit`s requires. But keeping the general code architecture as good as in your example, should mostly avoid that need. And even if I do need extra variables, it should be no problem when such logic is encapsulated well enough. Something to consider for me, regardless of which of the two DUB packages I end up using in the long term.
Jun 16
parent Dukc <ajieskola gmail.com> writes:
On Tuesday, 16 June 2020 at 19:15:24 UTC, Dukc wrote:
 I am not generally very fond of littering code with extra 
 variables, that converting `switch`es directly to `match`s or 
 `visit`s requires. But keeping the general code architecture as 
 good as in your example, should mostly avoid that need.
Meant need for extra variables, not need for converting the `switch`es.
Jun 16
prev sibling parent reply Dukc <ajieskola gmail.com> writes:
On Tuesday, 16 June 2020 at 12:21:56 UTC, Paul Backus wrote:
 If it's not too much trouble, can you give an example of the 
 kind of thing you'd like to be able to do?
No problem. This is directly from real code. The idea is that I have a vector shape `vecImage.front` and I want to figure out whether I need to reverse it, so that it will run in wanted direction `newDir`. However, the `determineCycleDirection` function is not perfect, so this resulted: ``` const currentDir = vecImage.front.save.determineCycleDirection; final switch(currentDir.kind) { case DetectedCycleDir.Kind.indifferent: { turnNeeded = 0; break; } case DetectedCycleDir.Kind.selfCrossed: { auto errRes = Message.directionSearchSelfcross(fileName).only.array; return WaypointsOrError.errors(errRes); } case DetectedCycleDir.Kind.timeout: { auto errRes = Message.directionSearchTimeout(fileName).only.array; return WaypointsOrError.errors(errRes); } case DetectedCycleDir.Kind.known: { turnNeeded = currentDir.knownValue == newDir? 0: 1; break; } } ``` DetectedCycleDir definition (comments translated to English from the project): ``` union _DetectedCycleDir { import taggedalgebraic.taggedunion : Void; Void timeout; //exection broken, as it seemed to end up in infinite loop Void selfCrossed; //The shape outline crossed itself Void indifferent; //The direction is meaningless (0 - 2 points) CycleDirection known; } alias DetectedCycleDir = from!"taggedalgebraic".TaggedUnion!_DetectedCycleDir; ```
Jun 16
next sibling parent Dukc <ajieskola gmail.com> writes:
On Tuesday, 16 June 2020 at 12:58:50 UTC, Dukc wrote:
 so this resulted: [snip]
I might add that I should have used the `with` statement to cut on verbosity: ``` with (DetectedCycleDir.Kind) final switch(currentDir.kind) { case indifferent: ... case known: ... } ```
Jun 16
prev sibling parent Paul Backus <snarwin gmail.com> writes:
On Tuesday, 16 June 2020 at 12:58:50 UTC, Dukc wrote:
 On Tuesday, 16 June 2020 at 12:21:56 UTC, Paul Backus wrote:
 If it's not too much trouble, can you give an example of the 
 kind of thing you'd like to be able to do?
No problem. This is directly from real code. The idea is that I have a vector shape `vecImage.front` and I want to figure out whether I need to reverse it, so that it will run in wanted direction `newDir`. However, the `determineCycleDirection` function is not perfect, so this resulted:
The most straightforward way to do this with SumType is to use a variable to track whether an early return is needed: const currentDir = vecImage.front.save.determineCycleDirection; auto errRes; // replace with appropriate type bool err = currentDir.match!( (Indifferent _) { turnNeeded = 0; return false; }, (SelfCrossed _) { errRes = Message.directionSearchSelfCross(fileName).only.array; return true; }, (Timeout _) { errRes = Message.directionSearchTimeout(fileName).only.array; return true; }, (CycleDirection known) { turnNeeded = currentDir.knownValue == newDir ? 0 : 1; return false; } ); if (err) return WaypointsOrError.errors(errRes); Where DetectedCycleDir is defined as something like this: struct Timeout {} struct SelfCrossed {} struct Indifferent {} alias DetectedCycleDir = SumType!( Timeout, SelfCrossed, Indifferent, CycleDirection ); Of course, this is only looking at a single fragment in isolation. With knowledge of the full function this is taken from, it would probably be possible to come up with a more elegant way of refactoring it--perhaps by using an `Optional` or `Result` type, or by splitting the parts before and after the early return into independent functions.
Jun 16
prev sibling parent reply Paolo Invernizzi <paolo.invernizzi gmail.com> writes:
On Sunday, 14 June 2020 at 18:04:57 UTC, Paul Backus wrote:
 On Sunday, 14 June 2020 at 17:31:32 UTC, Dukc wrote:
 [...]
FYI, SumType works with nothrow and nogc, and can solve all of your other problems with std.variant as well. :) Duplicate types are not supported out-of-the-box, but can very easily be accomplished using `std.typecons.Typedef`: alias InPixels = Typedef!(int[2], int[2].init, "InPixels"); alias InMeters = Typedef!(float[2], float[2].init, "InMeters"); alias SourceSizes = Typedef!(float[2], float[2].init, "SourceSizes"); alias PictureSize = SumType!(InPixels, InMeters, SourceSizes);
+1 We are using sumtype instead of std.variant exactly for that, attributes are not working with std.variant, and that's a show stopper. /P
Jun 14
parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 6/15/20 2:27 AM, Paolo Invernizzi wrote:
 On Sunday, 14 June 2020 at 18:04:57 UTC, Paul Backus wrote:
 On Sunday, 14 June 2020 at 17:31:32 UTC, Dukc wrote:
 [...]
FYI, SumType works with nothrow and nogc, and can solve all of your other problems with std.variant as well. :) Duplicate types are not supported out-of-the-box, but can very easily be accomplished using `std.typecons.Typedef`:     alias InPixels = Typedef!(int[2], int[2].init, "InPixels");     alias InMeters = Typedef!(float[2], float[2].init, "InMeters");     alias SourceSizes = Typedef!(float[2], float[2].init, "SourceSizes");     alias PictureSize = SumType!(InPixels, InMeters, SourceSizes);
+1 We are using sumtype instead of std.variant exactly for that, attributes are not working with std.variant, and that's a show stopper.
I have switched mysql-native to using TaggedAlgebraic specifically because Variant does not support safe. I agree with most of the stuff in this thread. What I would like to see is: a) Algebraic should be more supportive of merging types (probably don't depend on Variant guts). b) Variant should provide a mechanism to restrict all calls on it to conform to specified attributes. e.g. I should be able to declare a SafeVariant which can only be assigned to types that support safe operations. Note that even with TaggedAlgebraic I had to add a safety feature to ensure everything was safe: https://github.com/s-ludwig/taggedalgebraic/pull/40 Note that a big user of Variant (and rightfully so) is std.concurrency. You can look at a lot of std.concurrency complaints to find deficiencies with Variant. -Steve
Jun 15
prev sibling next sibling parent Justin Whear <justin.whear gmail.com> writes:
On Saturday, 13 June 2020 at 19:10:04 UTC, Andrei Alexandrescu 
wrote:
 I was curious about collecting a list of grievances about 
 Variant. It's the oldest piece of generic code in std, and it 
 predates a lot of good language additions.

 So what's wrong with Variant? One thing I collected from a 
 coworker is that it doesn't work with Windows DLLs, because in 
 turn typeof() comparison does not work across Windows DLLs.

 What are other problems with it?
I don't recall ever having a need for Variant; BUT, I have often needed an sum type implementation. The fact that Algebraic is implemented as a specialization of Variant (VariantN technically) has made it a non-starter for me. Specifically, the overhead of how the type being stored is checked and retrieved seems super heavy-weight compared to a simple tagged union approach. Last I checked (admittedly a while ago), it was not better-C compatible and sum types are the more common approach in the kind of code that needs to be better-C. Fortunately, implementing a clean, featureful sum type is really easy and fun in D which is probably why there are quite a few implementations floating around out there. That said, the D idiom which I generally write in eschews inheritance and classes generally and I think is fairly common, so strong standard library support would be helpful in preventing unnecessary drift and incompatibility; a quick search of code.dlang.com reveals these alternatives and I assume that there are many more unpublished (I think I have three or four implementations myself): https://code.dlang.org/packages/sumtype https://code.dlang.org/packages/tagged_union https://code.dlang.org/packages/tag https://code.dlang.org/packages/dadt
Jun 15
prev sibling parent Guillaume Piolat <first.last gmail.com> writes:
On Saturday, 13 June 2020 at 19:10:04 UTC, Andrei Alexandrescu 
wrote:
 What are other problems with it?
It's not very useful? Honestly I've never needed a Variant. But one time I used Algebraic, and even then changing it to a manual tagged union wasn't that bad.
Jun 15