October 01, 2020
On 10/1/2020 12:51 AM, Timon Gehr wrote:
> Maybe this is related to what you mean?
> 
> type t = readln().strip()=="int"?int:double;
> auto f = (t x)=>x;


As D is a statically typed language, I don't see how that can ever work.
October 01, 2020
On Thursday, 1 October 2020 at 08:56:58 UTC, Walter Bright wrote:
>
> I had looked into this. Unfortunately, many aspects of types are simply too complex to try and represent at run time. For example, is type T1 implicitly convertible to T2? This seemingly simple question is very complex. Yes, `alias this` makes it much worse :-/
>


The my latest post please!
The example on type functions with the conversion matrix.

This is all you need:

    override void visit(IsExp e)
    {
        auto targe = e.targ.isTypeExpression();
        if(!targe)
        {
            e.error("is expressions within type functions may only use type expressions");
            result = ErrorExp.get();
            return ;
        }
        auto targ = ctfeInterpret(targe.exp);
        auto te = targ.isTypeExp();

        result = IntegerExp.createBool(te && te.type && te.type.ty != Terror);

        auto tspece = e.tspec ? e.tspec.isTypeExpression() : null;
        auto tspec = tspece ? ctfeInterpret(tspece.exp) : null;
        auto ts = tspec ? tspec.isTypeExp() : null;

        // handling of == and &&
        // See IsExp handling in expressionsem.d
        if (e.tspec && !e.id && !(e.parameters && e.parameters.dim))
        {
            if (e.tok == TOK.colon)
            {
                result = IntegerExp.createBool(te.type.implicitConvTo(ts.type) != MATCH.nomatch);
            }
            else if(e.tok == TOK.equal)
            {
                result = IntegerExp.createBool(te.type.equals(ts.type));
            }
        }
        else if (e.tok2 != TOK.reserved)
        {
            e.error("Complex pattern matching forms of is expressions are not supported in TypeFunctions yet");
            result = IntegerExp.createBool(false);
        }
    }
October 01, 2020
On 01.10.20 10:59, Walter Bright wrote:
> On 10/1/2020 12:51 AM, Timon Gehr wrote:
>> Maybe this is related to what you mean?
>>
>> type t = readln().strip()=="int"?int:double;
>> auto f = (t x)=>x;
> 
> 
> As D is a statically typed language, I don't see how that can ever work.

The example is easy to type check statically. In terms of ABI, you just have to make sure that `f` can find the context pointer without already knowing the value of `t`, then it can reconstruct everything it needs to know.
October 01, 2020
On 01.10.20 10:56, Walter Bright wrote:
> On 10/1/2020 12:48 AM, Timon Gehr wrote:
>> (In terms of compiler implementation, the runtime representation of `T` would contain all information that is necessary to figure out calling conventions of functions that take a T, it would contain sizeof, pointers to destructor/postblit, etc, so basically it's typeid.)
> 
> I had looked into this. Unfortunately, many aspects of types are simply too complex to try and represent at run time. For example, is type T1 implicitly convertible to T2? This seemingly simple question is very complex. Yes, `alias this` makes it much worse :-/
> ...

The compiler can figure it out, so you *could* just put that logic into druntime. However, for first-class types, you don't need to check implicit conversions at run time, therefore this would not be necessary.

> I initially had high hopes for typeid back when it was originally designed 20 years ago. It ably fulfills its original purpose of enabling the GC and associative arrays in a language with no templates.
> ...

And a monomorphic type system.

> I don't see much future for it other than being kept around for legacy compatibility.

I was explaining conditions for types to be "first-class". So unless that is a goal, typeid might not see a revival.
October 01, 2020
On Thursday, 1 October 2020 at 07:51:28 UTC, Timon Gehr wrote:
> On 01.10.20 07:37, Bruce Carneal wrote:
>> On Wednesday, 30 September 2020 at 23:17:27 UTC, Timon Gehr wrote:
>>> On 29.09.20 01:37, Stefan Koch wrote:
>>>> ...
>>>
>>> It's not a first-class type if you can't declare a variable of that type. If this does not work, it's not first-class, it's syntax sugar for reification:
>>>
>>> type t = int;
>>> auto f = (t x)=>x;
>> 
>> Can anything computable using just the source as input be considered a first class type?
>> 
>> If so, what do we call type variables that can not be?
>> 
>
> Unfortunately I am not sure understand the question. What is an example of a "type variable that cannot be computed using just the source"? (Which source?)
>
> Maybe this is related to what you mean?
>
> type t = readln().strip()=="int"?int:double;
> auto f = (t x)=>x;

Yes.

The common understanding of a "statically typed language" is quite a bit more restrictive than "anything which can be determined from a fixed 'source' input" yet that is the bounding condition, it seems, for "static" compilation.

I lack the terminology to discuss this succinctly so I asked.  Note, my lack of understanding is sufficient that I may not have posed a well formed question.  Illumination is requested.



October 01, 2020
On 10/1/20 2:51 AM, Stefan Koch wrote:
> On Thursday, 1 October 2020 at 04:08:26 UTC, Walter Bright wrote:
>> On 9/28/2020 3:46 PM, Bruce Carneal wrote:
>>> Finally, I'd love to hear your comments on type functions vs reify/dereify.  It's certainly possible that I've missed something.  Maybe it's a type functions+ solution we should be seeking (type functions for everything they can do, and some LDM for anything beyond their capability).
>>
>> Andrei and I have been mulling over what to do for a while, and have gone through several ideas.
>>
>> What we're looking for is not a top level solution. We want a fundamental building block, upon which the rest of D's features can exploit naturally and productively. Something that can be explained in 30 seconds.
> 
> Type functions take less than 30 seconds.
> They take zero seconds.
> 
> Do you remember Andrei's non working example?
> 
>> alias MostDerived(Args...) = dereify!({
>>     auto ids = reify!Args;
>>     sort!((a, b) => is(dereify!a : dereify!b))(ids);
>>     return ids;
>> }());
> 
> Here is the correct type function which actually works!
> 
> alias[] MostDerived(alias[] types ...)
> {
>      sort!((alias a, alias b) => is(a : b))(types);
>      return types;
> }
> 
> It is Andrei thought would work.
> With type functions it just works.

From https://gist.github.com/andralex/0d85df38be2d9ffbe89cf1fb51c44213:

alias DerivedToFront(Args...) = dereify!({
    auto ids = reifyArray!Args;
    ids.sort;
    return ids;
}());

That code defines opCmp such that a < b iff a is a subtype of b, a nice touch reminiscent of the <: operator used in type theory. To implement that, the reified type stores the bases (conversion targets) of the type at construction time. This is easy to generalize to numeric and array types, and some more work to get going for things like covariant functions.

One litmus test is to redo Variant "the right way" (btw it should be called Any and moved to druntime). Currently Variant reifies the type into a pointer to function, and uses that pointer to function to dispatch type-dependent work. It is quite messy and incorrect in places. "The right way" would be for the reified type to have enough information to allow things like testing for subtyping/convertibility. Currently Variant is essentially incomplete and incorrect (see https://github.com/dlang/phobos/blob/master/std/variant.d#L285) because it builds on the shaky ground of https://github.com/dlang/phobos/blob/master/std/traits.d#L5027, a sort of a best-effort approach to figuring out the definition of implicit conversions scattered across the language definition (if documented in entirety at all). Having a clear definition of what implicitly converts to what would be a nice perk.

One interesting thing about Variant's primitives is that information is half-and-half: one half is present at compile-time, i.e. "Can I read a value of type T?" and the other half is present at runtime, in the reified format stored in the Variant. Given that the latter is dynamic, there's no way around reifying T as well and then using the reified types for testing.
October 01, 2020
On Thursday, 1 October 2020 at 15:21:40 UTC, Andrei Alexandrescu wrote:
...
>
> One litmus test is to redo Variant "the right way" (btw it should be called Any and moved to druntime). Currently Variant reifies the type into a pointer to function, and uses that pointer to function to dispatch type-dependent work. It is quite messy and incorrect in places. "The right way" would be for the reified type to have enough information to allow things like testing for subtyping/convertibility. Currently Variant is essentially incomplete and incorrect ...

Litmus test for what?  Utility at run-time or as a simple base language addition for use at compile time?

My concern here is not with our ability to make something like what you're proposing "work".  After all, C++ "works".  My concern is that the proposal, by my lights anyway, is already a good deal past "simple" and it's not converging.

On a related note, if the reify/dereify code expands as much as I believe it will in order to cover all cases, expands in to a "shadow" front end, you'll want to look at forcing the actual front end to use it so that you can eliminate divergence issues. That could also set us up for an embedded-compiler/jit future which would be another justification for the work.

If that's where you're headed, if you want to destroy the compile-time/run-time separation, I'd be interested in hearing more.  I'm not a fan of embedding the compiler or exposing the type system for that matter, but it would be worth a listen, no doubt.

October 01, 2020
On Thursday, 1 October 2020 at 08:59:04 UTC, Walter Bright wrote:
> On 10/1/2020 12:51 AM, Timon Gehr wrote:
>> Maybe this is related to what you mean?
>> 
>> type t = readln().strip()=="int"?int:double;
>> auto f = (t x)=>x;
>
>
> As D is a statically typed language, I don't see how that can ever work.

Flow analysis. You can deduce that t can only be int|double at the second line and then expand the code into a switch over the type (encoded as enum values).

It cannot work with separate compilation though.

October 02, 2020
On Thursday, 1 October 2020 at 15:21:40 UTC, Andrei Alexandrescu wrote:
> On 10/1/20 2:51 AM, Stefan Koch wrote:
>> On Thursday, 1 October 2020 at 04:08:26 UTC, Walter Bright wrote:
>>>
>> Here is the correct type function which actually works!
>> 
>> alias[] MostDerived(alias[] types ...)
>> {
>>      sort!((alias a, alias b) => is(a : b))(types);
>>      return types;
>> }
>> 
>> It is Andrei thought would work.
>> With type functions it just works.
>
> From https://gist.github.com/andralex/0d85df38be2d9ffbe89cf1fb51c44213:
>
> alias DerivedToFront(Args...) = dereify!({
>     auto ids = reifyArray!Args;
>     ids.sort;
>     return ids;
> }());

Try explaining that to a newbie who's never used D meta programming before.

It's a function that sorts a list of types from most to least derived.
You do it with an alias template. Here we've use the shorthand for an eponymous alias template. So it sort of looks like a regular function call but you need an equals sign after the template parentheses.
And the right had side needs to be an expression because its an alias declaration not a function declaration.
Oh and you need to wrap the right had side in a call to dereify(), you dont need to know why for now, you just do.
And you need to wrap the code you want to execute in an anonymous lamba that is called immediately.
And before you can do anything with the args passed to the template you need to call reify() on them.

I mean seriously? 30 seconds with a straight face?

(Im not even sure ive described it correctly)

vs a type function...

It's a function that sorts a list of types from most to least derived.
You write it just like a regular function but with types.
October 01, 2020
On 10/1/2020 5:41 AM, Timon Gehr wrote:
> On 01.10.20 10:59, Walter Bright wrote:
>> On 10/1/2020 12:51 AM, Timon Gehr wrote:
>>> Maybe this is related to what you mean?
>>>
>>> type t = readln().strip()=="int"?int:double;
>>> auto f = (t x)=>x;
>>
>>
>> As D is a statically typed language, I don't see how that can ever work.
> 
> The example is easy to type check statically. In terms of ABI, you just have to make sure that `f` can find the context pointer without already knowing the value of `t`, then it can reconstruct everything it needs to know.

I think you described D's "class" type. To make it work with int and double, you'd need to "box" them with a class. Like Java.