October 01, 2020
On 10/1/20 3:08 PM, Steven Schveighoffer wrote:
> On 10/1/20 2:30 PM, Andrei Alexandrescu wrote:
> 
>> The mechanics of carrying the conversion are also needed, especially if a change in format is necessary (e.g. int to long or float to double). It looks like a pointer to function needs to be part of the solution.
> 
> Yes, Variant already does this. And it does this by a static unrolled loop over the actual types. Not the reified types.

Yah, the difficulty is not execution as much as figuring out the correct set of types. That's where the discussion started - why redo "is" during compilation, turns out Variant needs kinda half of that, etc.

> So you'd have to build that mechanism into reification to "replace" Variant's usage of ImplicitConversionTargets. In other words, you can't just hoist this one piece into reification. You need to hoist ALL OF IT.
> 
> At some point, you are going to end up with a complete runtime reflection system.

And a compile-time reflection system that would allow a runtime reflection system would be fantastic. Unclear on where the completeness should stop, but this has been a D mantra for a long time now - don't build runtime reflection into the language because a good compile-time reflection can help you build any runtime reflection you want. We are sadly still in the early innings of that.

>>> BTW, Variant already does a *limited* form of this. reification doesn't change what needs to happen.
>>>
>>> I don't see why Variant's needs have to dictate how you need to reason about types at compile time in CTFE.
>>
>> The more applicability to difficult problems a mechanism has, the more useful it is. Looking good on cherry-picked examples is not enough. Difficult related problems that we have are introspection, std.meta, std.traits, and std.variant. The latter can be considered a problem of having the right primitives in std.traits.
> 
> Variant already has a solution. And it's tailored to Variant. I do not consider *at all* the problems Variant has to be related to a nicer compile time usage of types inside CTFE.

Hmmm... do we want to eliminate a use case because it doesn't fit the narrative?

> FWIW, type functions can replace ImplicitConversionTargets quite easily.

A proof of concept would be helpful.
October 01, 2020
On Thursday, 1 October 2020 at 19:33:09 UTC, Andrei Alexandrescu wrote:

>
> A proof of concept would be helpful.

You mean this one:

template ImplicitConversionTargets(T)
{
    static if (is(T == bool))
        alias ImplicitConversionTargets =
            AliasSeq!(byte, ubyte, short, ushort, int, uint, long, ulong, CentTypeList,
                       float, double, real, char, wchar, dchar);
    else static if (is(T == byte))
        alias ImplicitConversionTargets =
            AliasSeq!(short, ushort, int, uint, long, ulong, CentTypeList,
                       float, double, real, char, wchar, dchar);
    else static if (is(T == ubyte))
        alias ImplicitConversionTargets =
            AliasSeq!(short, ushort, int, uint, long, ulong, CentTypeList,
                       float, double, real, char, wchar, dchar);
    else static if (is(T == short))
        alias ImplicitConversionTargets =
            AliasSeq!(int, uint, long, ulong, CentTypeList, float, double, real);
    else static if (is(T == ushort))
        alias ImplicitConversionTargets =
            AliasSeq!(int, uint, long, ulong, CentTypeList, float, double, real);
    else static if (is(T == int))
        alias ImplicitConversionTargets =
            AliasSeq!(long, ulong, CentTypeList, float, double, real);
    else static if (is(T == uint))
        alias ImplicitConversionTargets =
            AliasSeq!(long, ulong, CentTypeList, float, double, real);
    else static if (is(T == long))
        alias ImplicitConversionTargets = AliasSeq!(float, double, real);
    else static if (is(T == ulong))
        alias ImplicitConversionTargets = AliasSeq!(float, double, real);
    else static if (is(cent) && is(T == cent))
        alias ImplicitConversionTargets = AliasSeq!(float, double, real);
    else static if (is(ucent) && is(T == ucent))
        alias ImplicitConversionTargets = AliasSeq!(float, double, real);
    else static if (is(T == float))
        alias ImplicitConversionTargets = AliasSeq!(double, real);
    else static if (is(T == double))
        alias ImplicitConversionTargets = AliasSeq!real;
    else static if (is(T == char))
        alias ImplicitConversionTargets =
            AliasSeq!(wchar, dchar, byte, ubyte, short, ushort,
                       int, uint, long, ulong, CentTypeList, float, double, real);
    else static if (is(T == wchar))
        alias ImplicitConversionTargets =
            AliasSeq!(dchar, short, ushort, int, uint, long, ulong, CentTypeList,
                       float, double, real);
    else static if (is(T == dchar))
        alias ImplicitConversionTargets =
            AliasSeq!(int, uint, long, ulong, CentTypeList, float, double, real);
    else static if (is(T : typeof(null)))
        alias ImplicitConversionTargets = AliasSeq!(typeof(null));
    else static if (is(T == class))
        alias ImplicitConversionTargets = staticMap!(ApplyLeft!(CopyConstness, T), TransitiveBaseTypeTuple!(T));
    else static if (isDynamicArray!T && !is(typeof(T.init[0]) == const))
    {
       static if (is(typeof(T.init[0]) == shared))
           alias ImplicitConversionTargets =
           AliasSeq!(const(shared(Unqual!(typeof(T.init[0]))))[]);
       else
           alias ImplicitConversionTargets =
           AliasSeq!(const(Unqual!(typeof(T.init[0])))[]);
    }
    else static if (is(T : void*))
        alias ImplicitConversionTargets = AliasSeq!(void*);
    else
        alias ImplicitConversionTargets = AliasSeq!();
}

@Andrei could you explain the semantics in detail?
The source code is somewhat obtuse.

October 01, 2020
On 10/1/20 3:37 PM, Stefan Koch wrote:
> 
> @Andrei could you explain the semantics in detail?
> The source code is somewhat obtuse.

I don't understand the question. The code is there and also publicly documented. Are you asking me to read it for you?
October 01, 2020
On 10/1/20 3:15 PM, Andrei Alexandrescu wrote:
> On 10/1/20 3:01 PM, Steven Schveighoffer wrote:
>> "solving the Variant problem" needs a problem definition first.
> 
> Problem definition: Implement Variant.get correctly. :o)
> 
> E.g. this produces a type error somewhere in the innards of std.variant: https://run.dlang.io/is/joIbeV. I'm not sure how many other cases are out there.

There are a lot. And you haven't even looked at contravariance:

alias F1 = void function(A);
alias F2 = void function(const(A));

F1 f1;
F2 f2;

f1 = f2; // ok

Though for some reason function(A) doesn't implicitly convert to function(B) (which would open this up completely to all derivative types in existence).

And it's not straightforward either. For instance, int can convert to long, but int function() cannot convert to long function().

So now you need the following properties:

canBeReturnedInsteadOf(Id)
canBeUsedAsAParameterInsteadOf(Id)

And generate all the possibilities for those (assuming they are bounded in one direction)

The code to make this "work" is going to be insane. And generate huge amounts of static data based on the type.

So let's give a more concrete definition for "implement Variant.get correctly". That is:

Given any two types T and U:

If this compiles:
T t;
U u = t;

Then this should as well:
Variant v = T.init;
U u = v.get!U;

The question now becomes, can we get this to work with reification easier or more efficiently than without reification?

I'd pose a further question: Is this important enough to add to Variant? I picked a typed language for a reason.

> BTW thanks Steve for choosing the right angle. This is not a contest, and not a search for the proverbial nails for the use of a given hammer. The converse approach is best - find what the difficult related problems are, and figure how to solve them well.

I'm fine with examining multiple solutions to the problem. I'm not rooting for a specific solution as much as examining the tradeoffs and enabling mechanisms for both.

I also can't help but be biased by having used TypeInfo (which is essentially a less powerful Id) and seeing the limitations there.

-Steve
October 01, 2020
On Thursday, 1 October 2020 at 19:40:47 UTC, Andrei Alexandrescu wrote:
> On 10/1/20 3:37 PM, Stefan Koch wrote:
>> 
>> @Andrei could you explain the semantics in detail?
>> The source code is somewhat obtuse.
>
> I don't understand the question. The code is there and also publicly documented. Are you asking me to read it for you?

Ah I was looking at the code only not at the documentation.
Which for properly written D code is usually enough.

`An $(REF AliasSeq,std,meta) with all possible target types of an implicit
    conversion `T`.`

is it an implicit conversion from T ?

Yes anything else wouldn't make sense.



October 01, 2020
On 10/1/20 3:33 PM, Andrei Alexandrescu wrote:
> On 10/1/20 3:08 PM, Steven Schveighoffer wrote:
>> On 10/1/20 2:30 PM, Andrei Alexandrescu wrote:
>>
>>> The mechanics of carrying the conversion are also needed, especially if a change in format is necessary (e.g. int to long or float to double). It looks like a pointer to function needs to be part of the solution.
>>
>> Yes, Variant already does this. And it does this by a static unrolled loop over the actual types. Not the reified types.
> 
> Yah, the difficulty is not execution as much as figuring out the correct set of types. That's where the discussion started - why redo "is" during compilation, turns out Variant needs kinda half of that, etc.

My point is that it still needs the list in dereified form. Which you can't do at runtime.

> And a compile-time reflection system that would allow a runtime reflection system would be fantastic. Unclear on where the completeness should stop, but this has been a D mantra for a long time now - don't build runtime reflection into the language because a good compile-time reflection can help you build any runtime reflection you want. We are sadly still in the early innings of that.

Yeah, a full runtime reflection system would be really interesting to build! I don't know if it's sad that we haven't built one, or if it's more indicative that we don't need one.

>>> The more applicability to difficult problems a mechanism has, the more useful it is. Looking good on cherry-picked examples is not enough. Difficult related problems that we have are introspection, std.meta, std.traits, and std.variant. The latter can be considered a problem of having the right primitives in std.traits.
>>
>> Variant already has a solution. And it's tailored to Variant. I do not consider *at all* the problems Variant has to be related to a nicer compile time usage of types inside CTFE.
> 
> Hmmm... do we want to eliminate a use case because it doesn't fit the narrative?

It doesn't fit the scope of the problem. The problem is: I want to write compile-time type manipulation in a runtime style. Not that I want to do everything that I can do at compile time at runtime (in this case at *actual* runtime).

>> FWIW, type functions can replace ImplicitConversionTargets quite easily.
> 
> A proof of concept would be helpful.

I imagine the first section which is comparing just builtin types, you can do like:

alias[] types = [int, char, ...];
import std.algorithm : filter;
import std.array : array;
return types.filter!((alias a) => is(t : a)).array;

The other ones would be similar but use a different way to generate the types other than filtering a finite list.

-Steve
October 01, 2020
On Thursday, 1 October 2020 at 19:40:47 UTC, Andrei Alexandrescu wrote:
> On 10/1/20 3:37 PM, Stefan Koch wrote:
>> 
>> @Andrei could you explain the semantics in detail?
>> The source code is somewhat obtuse.
>
> I don't understand the question. The code is there and also publicly documented. Are you asking me to read it for you?

It'll need a __trait for unqual but that should be about it.
The reason being that Unqual! is polymorphic.

Should be doable.
October 01, 2020
Does the same thing, works today:

string makeConvMatrix(T...)()
{
    string result;
    static foreach(t; T)
    {
        result ~= "\t" ~ t.stringof;
    }
    result ~= "\n";
    static foreach(t1; T)
    {
        result ~= t1.stringof;
        static foreach(t2; T)
        {
            result ~=  "\t" ~ (is(t1:t2) ? "yes" : "no");
        }
        result ~= "\n";
    }
    return result;
}

void main()
{
    import core.stdc.stdio;
    static immutable convMatrix = makeConvMatrix!(byte, ubyte, short, ushort, int, uint, long, ulong)();

    printf("%s\n", convMatrix.ptr);
}

October 01, 2020
On 10/1/20 3:57 PM, Steven Schveighoffer wrote:
> I don't know if it's sad that we haven't built one, or if it's more indicative that we don't need one.

We definitely need one, and generally means to generate boilerplate for interfacing with other languages. That we don't have a solid one, and not for the lack of trying, is indicative that we don't have the right ingredients.
October 01, 2020
On 10/1/2020 2:04 PM, Walter Bright wrote:
> Does the same thing, works today:

Yes, I see another message posted the same thing.