Thread overview
type set
Feb 28, 2011
spir
Feb 28, 2011
spir
Feb 28, 2011
spir
Feb 28, 2011
bearophile
February 28, 2011
Hello,

I have a template condition that looks like this:

    T check (T) () if (
            is(T == DLogical) ||
            is(T == DNumber) ||
            is(T == DText) ||
            is(T == DList) ||
            is(T == DUnit)
        ) {
        ...
    }

Is there a way to "factor out" such an expression using a kind of type set? If only for cleaning the code; but also because such a set may get long.

    T check (T) () if (is (T in validTypeSet)) {
        ...
    }

Denis
-- 
_________________
vita es estrany
spir.wikidot.com

February 28, 2011
On Mon, 28 Feb 2011 08:22:58 -0500, spir <denis.spir@gmail.com> wrote:

> Hello,
>
> I have a template condition that looks like this:
>
>      T check (T) () if (
>              is(T == DLogical) ||
>              is(T == DNumber) ||
>              is(T == DText) ||
>              is(T == DList) ||
>              is(T == DUnit)
>          ) {
>          ...
>      }
>
> Is there a way to "factor out" such an expression using a kind of type set? If only for cleaning the code; but also because such a set may get long.
>
>      T check (T) () if (is (T in validTypeSet)) {
>          ...
>      }

This should probably work:

template isOneOf(X, T...)
{
    static if(!T.length)
       enum bool isOneOf = false;
    else static if(is(X == T[0]))
       enum bool isOneOf = true;
    else
       enum bool isOneOf = isOneOf!(X, T[1..$]);
}

T check(T) () if(isOneOf!(T, DLogical, DNumber, DText, TList, DUnit))
{
   ...
}

Not sure if this exists in std.traits or not, but that's where I'd look.

-Steve
February 28, 2011
On 02/28/2011 02:32 PM, Steven Schveighoffer wrote:
> On Mon, 28 Feb 2011 08:22:58 -0500, spir <denis.spir@gmail.com> wrote:
>
>> Hello,
>>
>> I have a template condition that looks like this:
>>
>> T check (T) () if (
>> is(T == DLogical) ||
>> is(T == DNumber) ||
>> is(T == DText) ||
>> is(T == DList) ||
>> is(T == DUnit)
>> ) {
>> ...
>> }
>>
>> Is there a way to "factor out" such an expression using a kind of type set?
>> If only for cleaning the code; but also because such a set may get long.
>>
>> T check (T) () if (is (T in validTypeSet)) {
>> ...
>> }
>
> This should probably work:
>
> template isOneOf(X, T...)
> {
> static if(!T.length)
> enum bool isOneOf = false;
> else static if(is(X == T[0]))
> enum bool isOneOf = true;
> else
> enum bool isOneOf = isOneOf!(X, T[1..$]);
> }
>
> T check(T) () if(isOneOf!(T, DLogical, DNumber, DText, TList, DUnit))
> {
> ...
> }
>
> Not sure if this exists in std.traits or not, but that's where I'd look.
>
> -Steve

Waow, great anyway! Didn't even know one can write variadic type/template param lists.

By the way, the block of the function is a series of static if-s, one for each allowed type. Is there any static switch? Or any other nicer way to write it than:

    T check (T) () if (
            is(T == DLogical) ||
            is(T == DNumber) ||
            is(T == DText) ||
            is(T == DList) ||
            is(T == DUnit)
        ) {
        TypeCode type;

        static if (is(T == DLogical))
            if (this.code == LOGICAL)
                return this.logical;
            else
                type == LOGICAL;
        static if (is(T == DNumber))
            if (this.code == NUMBER)
                return this.number;
            else
                type == NUMBER;
        static if (is(T == DText))
            if (this.code == TEXT)
                return this.text;
            else
                type == TEXT;
        static if (is(T == DList))
            if (this.code == LOGICAL)
                return this.list;
            else
                type == LOGICAL;
        static if (is(T == DUnit))
            if (this.code == UNIT)
                return this.unit;
            else
                type == UNIT;

        // type error
        throw new TypeError(type, this);
    }

This func type-checks and returns the current value of a tagged union. I would be very pleased with a mapping from types to type codes (tags). I can't do without the type param, I guess, because it's the return value's type... or can I? But the discriminating code of the union cannot be the type itself (*), instead it's a plain code.
I thought at using TypeInfo-s as codes, which can then be mapped from types using typeid(). But according to sizeof, this makes the code weigh 1 word instead of one byte.

Denis

(*) Indeed. Else it would be a template generating N distinct types, which is precisely the opposite of what a union provides.
-- 
_________________
vita es estrany
spir.wikidot.com

February 28, 2011
On Mon, 28 Feb 2011 09:27:36 -0500, spir <denis.spir@gmail.com> wrote:

> By the way, the block of the function is a series of static if-s, one for each allowed type. Is there any static switch? Or any other nicer way to write it than:
>
>      T check (T) () if (
>              is(T == DLogical) ||
>              is(T == DNumber) ||
>              is(T == DText) ||
>              is(T == DList) ||
>              is(T == DUnit)
>          ) {
>          TypeCode type;
>
>          static if (is(T == DLogical))
>              if (this.code == LOGICAL)
>                  return this.logical;
>              else
>                  type == LOGICAL;
>          static if (is(T == DNumber))
>              if (this.code == NUMBER)
>                  return this.number;
>              else
>                  type == NUMBER;
>          static if (is(T == DText))
>              if (this.code == TEXT)
>                  return this.text;
>              else
>                  type == TEXT;
>          static if (is(T == DList))
>              if (this.code == LOGICAL)
>                  return this.list;
>              else
>                  type == LOGICAL;
>          static if (is(T == DUnit))
>              if (this.code == UNIT)
>                  return this.unit;
>              else
>                  type == UNIT;
>
>          // type error
>          throw new TypeError(type, this);
>      }

There is a final switch, but I don't know if that works on types.  You may be stuck with static if.

When doing things like this, I'd recommend using a mapping template.  For example:

private template typeCode(T)
{
   static if(is(T == DLogical)) enum typeCode = LOGICAL;
   else static if(is(T == DNumber)) enum typeCode = NUMBER;
   ...
   else static assert(0);
}

then you almost can use this to generate the right code:

if(this.code == typeCode!T)
{
   static if(is(T == DUnit)) return this.unit;
   else static if(...
}

You can probably replace the inner static if with a mixin, if you name your union members properly (i.e. if you can generate the name of the union member based on its type name or code, like DUnit dunit_val).

But at least it gives you an idea of how this can be done efficiently.

Plus avoiding large repetitive static ifs can save you from tedious copy-pasting bugs like the one in your DList branch ;)

-Steve
February 28, 2011
On 02/28/2011 03:50 PM, Steven Schveighoffer wrote:
> On Mon, 28 Feb 2011 09:27:36 -0500, spir <denis.spir@gmail.com> wrote:
>
>> By the way, the block of the function is a series of static if-s, one for
>> each allowed type. Is there any static switch? Or any other nicer way to
>> write it than:
>>
>> T check (T) () if (
>> is(T == DLogical) ||
>> is(T == DNumber) ||
>> is(T == DText) ||
>> is(T == DList) ||
>> is(T == DUnit)
>> ) {
>> TypeCode type;
>>
>> static if (is(T == DLogical))
>> if (this.code == LOGICAL)
>> return this.logical;
>> else
>> type == LOGICAL;
>> static if (is(T == DNumber))
>> if (this.code == NUMBER)
>> return this.number;
>> else
>> type == NUMBER;
>> static if (is(T == DText))
>> if (this.code == TEXT)
>> return this.text;
>> else
>> type == TEXT;
>> static if (is(T == DList))
>> if (this.code == LOGICAL)
>> return this.list;
>> else
>> type == LOGICAL;
>> static if (is(T == DUnit))
>> if (this.code == UNIT)
>> return this.unit;
>> else
>> type == UNIT;
>>
>> // type error
>> throw new TypeError(type, this);
>> }
>
> There is a final switch, but I don't know if that works on types. You may be
> stuck with static if.
>
> When doing things like this, I'd recommend using a mapping template. For example:
>
> private template typeCode(T)
> {
> static if(is(T == DLogical)) enum typeCode = LOGICAL;
> else static if(is(T == DNumber)) enum typeCode = NUMBER;
> ...
> else static assert(0);
> }
>
> then you almost can use this to generate the right code:
>
> if(this.code == typeCode!T)
> {
> static if(is(T == DUnit)) return this.unit;
> else static if(...
> }

That's it! This actually builds an the equivalent of an AA which keys are types. I was looking for such a functionality for a while already.
I am in fact *contantly* annoyed in D by the fact there are no types (I mean at runtime). On the other hand, this forces me looking for workaround, to express my models in "distorted" ways, which lets me discover unusual features and idioms in D's semantic dark corners ;-)

> You can probably replace the inner static if with a mixin, if you name your
> union members properly (i.e. if you can generate the name of the union member
> based on its type name or code, like DUnit dunit_val).

Yeah, I could, as shown by the code above: lang type Xyz <--> code XYZ <--> D impl type DXyz <--> union member xyz.
Could not be more regular, I guess ;-)
But as you may know, I 100% against string mixins. I prefere keeping the mapping explicite without string sorcery.
(Precisely, in the toy lang I'm starting to realise, one could do that trivially by manipulating the AST, without any dark magic. But this is another story...)

> But at least it gives you an idea of how this can be done efficiently.
>
> Plus avoiding large repetitive static ifs can save you from tedious
> copy-pasting bugs like the one in your DList branch ;)

Good catch, Steve! And thank you again.

PS: Is it your email client that eats whitespace (see my code above)?

Denis
-- 
_________________
vita es estrany
spir.wikidot.com

February 28, 2011
On Mon, 28 Feb 2011 12:50:21 -0500, spir <denis.spir@gmail.com> wrote:

> PS: Is it your email client that eats whitespace (see my code above)?

Only in your reply does the code appear indented improperly, so I think it's your client, not mine.

-steve
February 28, 2011
Steven Schveighoffer:

> Not sure if this exists in std.traits or not, but that's where I'd look.

std.typetuple.anySatisfy helps here.

-------

spir:

> Is there any static switch?

foreach done on a typetuple is a static foreach.


> Or any other nicer way to write it than:

I suggest to put the types in a typetuple and use a foreach(i, T, TypeTuple!(t1, t2, ...)), put your values in another typetuple and use the i index to access and return the values.

import std.stdio, std.typetuple;
int mapper(TX)() {
    alias TypeTuple!(int, float, string) Tkeys;
    enum values = [100, 5, 2];
    //alias TypeTuple!(100, 5, 2) Tvalues; // alternative

    foreach (i, T; Tkeys)
        if (is(T == TX))
            return values[i];
    assert(0, "Can't happen");
}
enum r = mapper!float();
static assert(r == 5);
void main() {}

Bye,
bearophile