www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.announce - taggedalgebraic 0.11.0 adds TaggedUnion

reply =?UTF-8?Q?S=c3=b6nke_Ludwig?= <sludwig+d outerproduct.org> writes:
TaggedUnion is the low level tagged union functionality separated out 
from TaggedAlgebraic. Because it doesn't support transparent access of 
methods and operators of the contained value, it is able to provide a 
number of convenience features. On top of that, visit and tryVisit is 
now supported for pattern matching, analogous to the version for 
std.variant.Algebraic. This is the basic usage:

     union U {
         string caption;
         int height;
         int width;
     }
     alias Value = TaggedUnion!U;

     auto val = Value.caption("foo");
     assert(val.kind == Value.Kind.caption);
     assert(val.value!(Value.Kind.caption) == "foo");

     // shorthand syntax:
     assert(val.isCaption);
     assert(val.captionValue == "foo");

     // set a different type/kind
     val.setHeight(10);
     assert(val.isHeight && val.heightValue == 10);
     assert(!val.isWidth);

     assert(cast(short)val == 10);

     val.visit!(
         (int i) { assert(i == 10); }
         (string s) { assert(false); }
     );

     assert(val.visit!((v) => v.to!string) == "10");


Another addition is a new placeholder "This" type to enable defining 
self-referential types without having to write additional boilerplate code:

     union U {
         bool boolean;
         string text;
         float number;
         TaggedUnion!This[] array;
         TaggedUnion!This[string] object;
     }
     alias JsonValue = TaggedUnion!U;


The general advantages of TaggedUnion/TaggedAlgebraic over Algebraic 
result from its use of a numeric type tag instead of relying on runtime 
TypeInfo:

  - Allows compiler checked handling of all possible types/kinds using
    "final switch"
  - Enables inlining and function attribute inference ( safe, nothrow,
     nogc)
  - Allows to use the same type with multiple tags to avoid the overhead
    of defining separate wrapper types
  - Behaves like a POD if only POD fields exist, is non-copyable if any
    contained type is non-copyable etc.


DUB: https://code.dlang.org/packages/taggedalgebraic
GitHub: https://github.com/s-ludwig/taggedalgebraic
Feb 22
next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2019-02-22 18:09, Sönke Ludwig wrote:
 TaggedUnion is the low level tagged union functionality separated out 
 from TaggedAlgebraic. Because it doesn't support transparent access of 
 methods and operators of the contained value, it is able to provide a 
 number of convenience features. On top of that, visit and tryVisit is 
 now supported for pattern matching, analogous to the version for 
 std.variant.Algebraic. This is the basic usage:
 
      union U {
          string caption;
          int height;
          int width;
      }
      alias Value = TaggedUnion!U;
 
      auto val = Value.caption("foo");
      assert(val.kind == Value.Kind.caption);
      assert(val.value!(Value.Kind.caption) == "foo");
 
      // shorthand syntax:
      assert(val.isCaption);
      assert(val.captionValue == "foo");
 
      // set a different type/kind
      val.setHeight(10);
Why not using property syntax, i.e. `val.height = 10`?
      val.visit!(
          (int i) { assert(i == 10); }
          (string s) { assert(false); }
      );
How does this handle the above case when there are two `int`? How do I know if it's the "width" or "height" that has been set? -- /Jacob Carlborg
Feb 23
parent reply =?UTF-8?Q?S=c3=b6nke_Ludwig?= <sludwig+d outerproduct.org> writes:
Am 23.02.2019 um 09:51 schrieb Jacob Carlborg:
 On 2019-02-22 18:09, Sönke Ludwig wrote:
 TaggedUnion is the low level tagged union functionality separated out 
 from TaggedAlgebraic. Because it doesn't support transparent access of 
 methods and operators of the contained value, it is able to provide a 
 number of convenience features. On top of that, visit and tryVisit is 
 now supported for pattern matching, analogous to the version for 
 std.variant.Algebraic. This is the basic usage:

      union U {
          string caption;
          int height;
          int width;
      }
      alias Value = TaggedUnion!U;

      auto val = Value.caption("foo");
      assert(val.kind == Value.Kind.caption);
      assert(val.value!(Value.Kind.caption) == "foo");

      // shorthand syntax:
      assert(val.isCaption);
      assert(val.captionValue == "foo");

      // set a different type/kind
      val.setHeight(10);
Why not using property syntax, i.e. `val.height = 10`?
The main reason is to distinguish the act of setting a new value from the one of modifying an existing value. In the former case it is okay if the currently stored kind/type differs, but in the latter case that would be a programming error. This would have worked with a setter and an r-value getter, but that would have unnecessarily impeded performance for working with complex types, at least for the primary access syntax. Following from the above, the raw name is used instead as a static property to enable a short construction syntax for a specific kind (e.g. `Value.width(10)`).
      val.visit!(
          (int i) { assert(i == 10); }
          (string s) { assert(false); }
      );
How does this handle the above case when there are two `int`? How do I know if it's the "width" or "height" that has been set?
I'm still thinking about a nice syntax for kind based pattern matching*, but for now this will call the same visitor for both, width and height. You'd have to query val.isWidth/val.kind to check the specific kind. * really unfortunate that a template has to be instantiated first to be able to query the parameters of the contained function, otherwise `( (Kind.width) i) { ... }` would be possible.
Feb 23
parent =?UTF-8?Q?S=c3=b6nke_Ludwig?= <sludwig+d outerproduct.org> writes:
Am 23.02.2019 um 14:18 schrieb Sönke Ludwig:
 Am 23.02.2019 um 09:51 schrieb Jacob Carlborg:
 (...)

 Why not using property syntax, i.e. `val.height = 10`?
The main reason is to distinguish the act of setting a new value from the one of modifying an existing value. In the former case it is okay if the currently stored kind/type differs, but in the latter case that would be a programming error.
To follow up on that, in the example above it would be possible to do this: val.setHeight(10); val.heightValue++; assert(val.heightValue == 11); //val.captionValue = "foo"; // throws an AssertError Then there is also a generic variant of the above: val.set!(Value.Kind.height)(10); val.value!(Value.Kind.height)++; val.value!int++;
Feb 23
prev sibling parent reply Paul Backus <snarwin gmail.com> writes:
On Friday, 22 February 2019 at 17:09:41 UTC, Sönke Ludwig wrote:
  - Behaves like a POD if only POD fields exist, is non-copyable 
 if any
    contained type is non-copyable etc.
In fact, if a contained type is non-copyable, it fails to compile altogether. (Example code below.) Granted, so do Algebraic and SumType, so this isn't a knock against taggedalgebraic in particular. But it does indicate poor test coverage that you listed this feature without realizing it didn't work. --- example.d /+ dub.sdl: dependency "taggedalgebraic" version="~>0.11.2" +/ import taggedalgebraic; struct NoCopy { disable this(this); } union U { NoCopy member; } alias Test = TaggedUnion!U; // kaboom void main() {}
Feb 23
parent reply =?UTF-8?Q?S=c3=b6nke_Ludwig?= <sludwig+d outerproduct.org> writes:
Am 23.02.2019 um 20:19 schrieb Paul Backus:
 On Friday, 22 February 2019 at 17:09:41 UTC, Sönke Ludwig wrote:
  - Behaves like a POD if only POD fields exist, is non-copyable if any
    contained type is non-copyable etc.
In fact, if a contained type is non-copyable, it fails to compile altogether. (Example code below.) Granted, so do Algebraic and SumType, so this isn't a knock against taggedalgebraic in particular. But it does indicate poor test coverage that you listed this feature without realizing it didn't work. --- example.d /+ dub.sdl: dependency "taggedalgebraic" version="~>0.11.2" +/ import taggedalgebraic; struct NoCopy { disable this(this); } union U { NoCopy member; } alias Test = TaggedUnion!U; // kaboom void main() {}
You are right, I was actually aware of some places where the value is not moved/swapped properly, but I forgot about it when rushing the post before leaving. The test coverage is actually pretty good, though, even if it is hard to give a meaningful number for template-heavy code like that.
Feb 23
parent =?UTF-8?Q?S=c3=b6nke_Ludwig?= <sludwig+d outerproduct.org> writes:
Am 23.02.2019 um 21:46 schrieb Sönke Ludwig:
 Am 23.02.2019 um 20:19 schrieb Paul Backus:
 On Friday, 22 February 2019 at 17:09:41 UTC, Sönke Ludwig wrote:
  - Behaves like a POD if only POD fields exist, is non-copyable if any
    contained type is non-copyable etc.
In fact, if a contained type is non-copyable, it fails to compile altogether. (Example code below.) Granted, so do Algebraic and SumType, so this isn't a knock against taggedalgebraic in particular. But it does indicate poor test coverage that you listed this feature without realizing it didn't work. --- example.d /+ dub.sdl: dependency "taggedalgebraic" version="~>0.11.2" +/ import taggedalgebraic; struct NoCopy { disable this(this); } union U { NoCopy member; } alias Test = TaggedUnion!U; // kaboom void main() {}
You are right, I was actually aware of some places where the value is not moved/swapped properly, but I forgot about it when rushing the post before leaving. The test coverage is actually pretty good, though, even if it is hard to give a meaningful number for template-heavy code like that.
https://github.com/s-ludwig/taggedalgebraic/pull/26
Feb 23