Thread overview
Is std.variant useful for types only known at run time?
Sep 08, 2021
Chris Piker
Sep 08, 2021
jfondren
Sep 08, 2021
Chris Piker
Sep 08, 2021
jfondren
September 08, 2021

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,

September 08, 2021

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:

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:

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);
}
September 08, 2021

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?

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?

September 08, 2021

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?

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.

September 08, 2021

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?

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 does this for you.

-Steve