September 24, 2020
On Thursday, 24 September 2020 at 04:39:43 UTC, Paul Backus wrote:
>
> I agree that tuple auto-expansion is weird, but what are we supposed to do about it, just never use tuples? It's not like the type function version gets rid of them either--the alias[] only exists inside the function, so you still have to deal with tuples if you want to use the result.

nope. staticMap_tf!sizeOf returns a regular size_t[].
not a tuple.

September 24, 2020
On 9/23/20 5:46 AM, Stefan Koch wrote:
> struct DummyType {} // just a dummy to get the right inference
> 
> auto getUDAs(alias T) { return __traits(getAttributes, T); } // needed because that's the shortest thing that'll return alias[]
> 
> alias alias_array = typeof(getUDAs(DummyType));
> 
> auto static_map_tf(alias F)(alias_array types ...)
> {
>      typeof(F(DummyType))[] result;
>      result.length = types.length;
> 
>      foreach(i, t;types)
>      {
>          result[i] = F(t);
>      }
> 
>      return result;
> }
> 
> size_t sizeOf(alias t)
> {
>      return t.sizeof;
> }
> 
> alias Int = int;
> alias Ushort = ushort; // we need these aliases because the parser won't allow us to call a function with a reserved type identifier.
> 
> static assert(static_map_tf!sizeOf(Int, Ushort) == [4, 2]);

Was looking at this example thinking, if only we had an object available for each type. And then it struck me - we already do. It's typeid. For each type T, typeid(T) yields an object (of type class TypeInfo) that can be copied, compared etc.

So the example could be written with typeid as such:

TypeInfo[] static_map_tf(alias F)(TypeInfo[] types...)
{
    typeof(F(types[0]))[] result;
    result.length = types.length;
    foreach(i, t; types)
    {
        result[i] = F(t);
    }
    return result;
}

size_t sizeOf(TypeInfo t)
{
    return mixin("(" ~ t.toString ~ ").sizeof");
}

static assert(static_map_tf!sizeOf(typeid(int), typeid(ushort)) == [4, 2]);

A few comments:

* Currently mixin closes the circle by taking back the typeid to the type it started from. It would be nice to have something better, e.g. t.Type would just be the type.

* In the current implementation values returned by typeid() cannot be read during compilation. So one question is if they could. Per https://github.com/dlang/druntime/pull/3174, that can be done largely at the library level.

* This could work with no change to the language definition. All that's needed to get lift is make TypeInfo values usable during compilation.

September 23, 2020
On Thu, Sep 24, 2020 at 03:04:46AM +0000, Stefan Koch via Digitalmars-d wrote:
> On Thursday, 24 September 2020 at 02:42:00 UTC, Paul Backus wrote:
> > 
> > On the other hand, if you can fix recursive template bloat somehow (tail-call elimination?), you get good performance *and* nice code. It's the best of both worlds.
> 
> good performance yes.
> But where is the nice code, when you are confined to recursive
> templates?

What we need is imperative syntactic sugar that lowers to recursive templates under the hood.  (After all, in theory, every Turing-complete language is just syntactic sugar over lambda calculus. :-P)

That, plus implement template optimization schemes to lower the cost of recursive templates.  The template analogue of tail-call optimization is a good first step, for example.  Another good direction to investigate is instantiation elimination: i.e., defer the actual instantiation of a template (with its associated costs of copying the AST and substituting arguments, generating symbols, etc.) until it's determined to be necessary.


T

-- 
Many open minds should be closed for repairs. -- K5 user
September 24, 2020
On Thursday, 24 September 2020 at 05:13:49 UTC, Andrei Alexandrescu wrote:
> On 9/23/20 5:46 AM, Stefan Koch wrote:
>> struct DummyType {} // just a dummy to get the right inference
>> 
>> auto getUDAs(alias T) { return __traits(getAttributes, T); } // needed because that's the shortest thing that'll return alias[]
>> 
>> alias alias_array = typeof(getUDAs(DummyType));
>> 
>> auto static_map_tf(alias F)(alias_array types ...)
>> {
>>      typeof(F(DummyType))[] result;
>>      result.length = types.length;
>> 
>>      foreach(i, t;types)
>>      {
>>          result[i] = F(t);
>>      }
>> 
>>      return result;
>> }
>> 
>> size_t sizeOf(alias t)
>> {
>>      return t.sizeof;
>> }
>> 
>> alias Int = int;
>> alias Ushort = ushort; // we need these aliases because the parser won't allow us to call a function with a reserved type identifier.
>> 
>> static assert(static_map_tf!sizeOf(Int, Ushort) == [4, 2]);
>
> Was looking at this example thinking, if only we had an object available for each type. And then it struck me - we already do. It's typeid. For each type T, typeid(T) yields an object (of type class TypeInfo) that can be copied, compared etc.

Well yeah kindof.
What the type function does when it creates the alias from the type is indeed similar to what a complete type info would do.
However what you don't get is the nice syntax that come with type functions.
And you don't get the back-channel from type-info to type.

Getting typeinfo to properly fit in here, will likely mean internal compiler adjustments on the same scale as type functions.

There are also uses such as the type function for fullyQualifiedName which can't be done by converting to typeinfo objects.
September 24, 2020
On Thursday, 24 September 2020 at 05:38:07 UTC, H. S. Teoh wrote:
> On Thu, Sep 24, 2020 at 03:04:46AM +0000, Stefan Koch via Digitalmars-d wrote:
>> On Thursday, 24 September 2020 at 02:42:00 UTC, Paul Backus wrote:
>> > 
>> > On the other hand, if you can fix recursive template bloat somehow (tail-call elimination?), you get good performance *and* nice code. It's the best of both worlds.
>> 
>> good performance yes.
>> But where is the nice code, when you are confined to recursive
>> templates?
>
> What we need is imperative syntactic sugar that lowers to recursive templates under the hood.  (After all, in theory, every Turing-complete language is just syntactic sugar over lambda calculus. :-P)
>
> That, plus implement template optimization schemes to lower the cost of recursive templates.  The template analogue of tail-call optimization is a good first step, for example.  Another good direction to investigate is instantiation elimination: i.e., defer the actual instantiation of a template (with its associated costs of copying the AST and substituting arguments, generating symbols, etc.) until it's determined to be necessary.

How do we get past the, rather severe, constraints Stefan outlined wrt tail recursion?

Also, why "lower" to recursion when iteration is directly available?  Are you proposing an improvement for templates that can not be represented in type functions?

>
>
> T


September 24, 2020
wait ....

On Thursday, 24 September 2020 at 05:13:49 UTC, Andrei Alexandrescu wrote:
>
> size_t sizeOf(TypeInfo t)
> {
>     return mixin("(" ~ t.toString ~ ").sizeof");
> }
>
What the ....

The sizeof function you wrote is POLYMORPHIC!

it changes shape.
This would work in a DYNAMIC langauge.
but it DOES NOT work in a STATIC langauge.

For your approach to work sizeof needs to be a part of the typeinfo object.
September 23, 2020
On Thu, Sep 24, 2020 at 01:13:49AM -0400, Andrei Alexandrescu via Digitalmars-d wrote: [...]
> Was looking at this example thinking, if only we had an object available for each type. And then it struck me - we already do. It's typeid. For each type T, typeid(T) yields an object (of type class TypeInfo) that can be copied, compared etc.

+1.  I've mentioned this before in one of Stefan's threads.  Basically, to promote types to first-class citizen status, all we need to do is to treat typeid specially at compile-time.  At compile-time, typeid becomes something like a glorified alias to the type itself, such that it can be manipulated, passed around, interrogated, etc..  At runtime, typeid becomes the familiar RTTI object.


[...]
> * This could work with no change to the language definition. All that's needed to get lift is make TypeInfo values usable during compilation.

Or treat typeid specially at compile-time, as a compile-time object not necessarily tied to TypeInfo (but "collapses" into TypeInfo at runtime).


T

-- 
There are 10 kinds of people in the world: those who can count in binary, and those who can't.
September 24, 2020
On Thursday, 24 September 2020 at 05:51:30 UTC, H. S. Teoh wrote:
I've mentioned this before in one of Stefan's threads.
> Basically, to promote types to first-class citizen status, all we need to do is to treat typeid specially at compile-time.  At compile-time, typeid becomes something like a glorified alias to the type itself, such that it can be manipulated, passed around, interrogated, etc..  At runtime, typeid becomes the familiar RTTI object.

Whether you call it `alias` or `typeid` you need the same functionality.
Infact type functions work in that way.
Just that type functions have some more functionality to take care of more commonly needed meta-programming exercises.

I don't see where the difference is other than a syntactic one.
September 24, 2020
On Thursday, 24 September 2020 at 05:13:49 UTC, Andrei Alexandrescu wrote:
>
> * Currently mixin closes the circle by taking back the typeid to the type it started from. It would be nice to have something better, e.g. t.Type would just be the type.

This would make typeid polymorphic in other words a template.

> * In the current implementation values returned by typeid() cannot be read during compilation.

Wrong. parts of typeid can be read by ctfe.
I extended this functionality myself.

> So one question is if they could. Per https://github.com/dlang/druntime/pull/3174, that can be done largely at the library level.

Perhaps but I don't see how to do that without introducing polymorphic types (meaning template types)

> * This could work with no change to the language definition. All that's needed to get lift is make TypeInfo values usable during compilation.

Wrong. The polymorphic nature you need to get back to the type from typeid
would be a massive change.

September 24, 2020
On 24.09.20 05:51, Paul Backus wrote:
> 
> I feel the same way about the naive recursive version:
> 
> template staticMap!(alias F, Args...) {
>      static if (Args.length == 0)
>          alias staticMap = AliasSeq!();
>      else
>          alias staticMap = AliasSeq!(F!(Args[0]), staticMap!(F, Args[1 .. $]));
> }
> 
> One base case, one recursive case, one line for each. The code practically writes itself. How could it be any clearer or more obvious?
> 
> Making it tail-recursive takes away some of the elegance, but doesn't make it any more "interesting":
> 
> template staticMap(alias F, Args...) {
>      template loop(size_t i, Args...) {
>          static if (start == Args.length)
>              alias loop = Args;
>          else
>              alias loop = loop!(i + 1, Args[0 .. i], F!(Args[i]), Args[i+1 .. $]);
>      }
>      alias staticMap = loop!(0, Args);
> }
> 
> If you have spent any time at all writing code in a functional language with tail-call elimination, this pattern will be immediately familiar to you. There's nothing about it that's hard to understand, or difficult to follow. It's completely textbook.
> ...

True, only a novice would use explicit recursion. map=(`foldr`[]).((:).)

> Of course, for someone who lacks that background, it might very well look bewildering--in the same way that, say, the Visitor pattern might look bewildering to someone unfamiliar with the idioms of OOP. Does that mean it's a bad pattern? Or does it just mean it's a pattern they haven't learned yet?

It means your programming language lacks features to write the code in a better way. Note that D templates form a very ugly programming language with a semantics that is hard to implement efficiently.