www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Is std.variant useful for types only known at run time?

reply Chris Piker <chris hoopjump.com> writes:
Hi D

I'm working on data streaming reading module where the encoding 
of each input array isn't known until runtime.  For example 
date-time column values may be encoded as:

    * An ISO-8601 UTC time string (aka char[])
    * A ASCII floating point value with an indicated unit size and 
epoch (aka char[])
    * A IEEE double with an indicated endianness, unit size, and 
epoch. (aka double[])
    * A 64-bit signed in with an indicated endianness, unit size, 
and epoch. (aka long[])

My job when encountering a date-time array in the stream is to 
just to properly convert the info into a broken down time 
structure, regardless of the encoding.

Initially I've been reading chunks of the stream into a `ubyte[]` 
and then using `cast` and `to` as needed, but then I stumbled 
across std.variant which can hold any type.

I'm wondering if std.variant is useful in cases where type 
information is only known at run-time, since many of the flexible 
data structures I've run across so far in D require compile-time 
information.

Thanks,
Sep 08 2021
parent reply jfondren <julian.fondren gmail.com> writes:
On Wednesday, 8 September 2021 at 07:10:21 UTC, Chris Piker wrote:
 Hi D

 I'm working on data streaming reading module where the encoding 
 of each input array isn't known until runtime.  For example 
 date-time column values may be encoded as:

    * An ISO-8601 UTC time string (aka char[])
    * A ASCII floating point value with an indicated unit size 
 and epoch (aka char[])
    * A IEEE double with an indicated endianness, unit size, and 
 epoch. (aka double[])
    * A 64-bit signed in with an indicated endianness, unit 
 size, and epoch. (aka long[])
a std.variant.Variant can contain *any* type, but this only four types, so I'd look at a std.sumtype of them first: ```d import std.sumtype; struct ISO8601 { } struct FloatEpoch { } struct DoubleEpoch { } struct LongEpoch { } alias Time = SumType!(ISO8601, FloatEpoch, DoubleEpoch, LongEpoch); void main() { import std.stdio : writeln; import std.format : format; Time e = ISO8601(); writeln(e.match!( (FloatEpoch _) => "doesn't happen", (DoubleEpoch _) => "doesn't happen", (LongEpoch _) => "an error to omit, unlike the next example", (ISO8601 time) => format!"%s"(time), )); } ``` ...
 I'm wondering if std.variant is useful in cases where type 
 information is only known at run-time, since many of the 
 flexible data structures I've run across so far in D require 
 compile-time information.
It is. It's used for message passing in std.concurrency for example, where an actor can receive any kind of type into its messagebox. If std.sumtype didn't exist then I might look at std.variant or a novel discriminated union of my own or OOP, where an option is to try casting to the different subtypes until the cast works: ```d class Encoding { } class ISO8601 : Encoding { } class FloatEpoch : Encoding { } class DoubleEpoch : Encoding { } class LongEpoch : Encoding { } void main() { import std.stdio : writeln; Encoding e = new ISO8601; if (!cast(FloatEpoch) e) writeln("it's null"); if (!cast(LongEpoch) e) writeln("it's null"); if (auto time = cast(ISO8601) e) writeln(time); } ```
Sep 08 2021
parent reply Chris Piker <chris hoopjump.com> writes:
On Wednesday, 8 September 2021 at 08:39:53 UTC, jfondren wrote:
 so I'd look at a std.sumtype of them first:
Wow, this forum is like a CS department with infinite office hours! Interesting. I presume that the big win for using std.sumtype over a class set is value semantics instead of reference semantics? So out of curiosity, say each structure implemented a function to provide the desired broken-down-time, would the following "virtual function" style call work? ```d import std.sumtype; struct BDTime { int y, int m, int d, int h, int m, double s }; struct ISO8601 { BDTime bdTime(){ ... } } struct FloatEpoch { BDTime bdTime(){ ... } } struct DoubleEpoch { BDTime bdTime(){ ... } } struct LongEpoch { BDTime bdTime(){ ... } } alias Time = SumType!(ISO8601, FloatEpoch, DoubleEpoch, LongEpoch); void main() { import std.stdio : writeln; import std.format : format; Time e = ISO8601(); BDTime = e.bdTime(); } ``` or would I need to use `match!` to get the right structure type and then generate the internal time representation?
Sep 08 2021
next sibling parent jfondren <julian.fondren gmail.com> writes:
On Wednesday, 8 September 2021 at 09:55:20 UTC, Chris Piker wrote:
 Interesting.  I presume that the big win for using std.sumtype 
 over a class set is value semantics instead of reference 
 semantics?
There's a lot to say about the precise differences. One practical difference that I alluded to earlier is that an incomplete `match!` is a compile-time error, so if you later add a fifth kind of time encoding to your sumtype, the compiler will give you a laundry list of parts of your code to update to handle the new case.
 So out of curiosity, say each structure implemented a function 
 to provide the desired broken-down-time, would the following 
 "virtual function" style call work?

 ```d
 import std.sumtype;
 struct BDTime { int y, int m, int d, int h, int m, double s };

 struct ISO8601 { BDTime bdTime(){ ... }  }
 struct FloatEpoch { BDTime bdTime(){ ... } }
 struct DoubleEpoch { BDTime bdTime(){ ... } }
 struct LongEpoch { BDTime bdTime(){ ... }  }
 alias Time = SumType!(ISO8601, FloatEpoch, DoubleEpoch, 
 LongEpoch);

 void main() {
     import std.stdio : writeln;
     import std.format : format;

     Time e = ISO8601();
     BDTime = e.bdTime();
 }
 ```
 or would I need to use `match!` to get the right structure type 
 and then generate the internal time representation?
You'd get an error like ``` Error: no property `bdTime` for type `std.sumtype.SumType!(ISO8601, ...)` ``` bdTime is defined for the individual times and not for the sumtype. You could have a match! that pulls out each member and calls its individual .bdTime(), but probably a more natural solution is to define bdTime only once, against the sumtype, where it has an internal `match!` that pulls out the different properties of the members that are necessary for each to construct a BDTime.
Sep 08 2021
prev sibling parent Steven Schveighoffer <schveiguy gmail.com> writes:
On 9/8/21 5:55 AM, Chris Piker wrote:
 On Wednesday, 8 September 2021 at 08:39:53 UTC, jfondren wrote:
 so I'd look at a std.sumtype of them first:
Wow, this forum is like a CS department with infinite office hours! Interesting.  I presume that the big win for using std.sumtype over a class set is value semantics instead of reference semantics? So out of curiosity, say each structure implemented a function to provide the desired broken-down-time, would the following "virtual function" style call work? ```d import std.sumtype; struct BDTime { int y, int m, int d, int h, int m, double s }; struct ISO8601 { BDTime bdTime(){ ... }  } struct FloatEpoch { BDTime bdTime(){ ... } } struct DoubleEpoch { BDTime bdTime(){ ... } } struct LongEpoch { BDTime bdTime(){ ... }  } alias Time = SumType!(ISO8601, FloatEpoch, DoubleEpoch, LongEpoch); void main() {     import std.stdio : writeln;     import std.format : format;     Time e = ISO8601();     BDTime = e.bdTime(); } ``` or would I need to use `match!` to get the right structure type and then generate the internal time representation?
Just as an aside, [taggedalgebraic](https://code.dlang.org/packages/taggedalgebraic) does this for you. -Steve
Sep 08 2021