May 10, 2020
On Sunday, 10 May 2020 at 09:50:09 UTC, Stefan Koch wrote:
> youtube changed to only showing SD content it seems like?
> Because of covid-19 ... this is crazy.

I think it defaults to SD, but you can change the resolution to HD. Although for me the text is still too small to read ;-)
May 10, 2020
On Sunday, 10 May 2020 at 10:28:17 UTC, Nick Treleaven wrote:
> On Sunday, 10 May 2020 at 08:39:42 UTC, Nick Treleaven wrote:
>>     alias E;
>>     while (S.length)
>>     {
>>         E = S[0];
>>         if (Tem!E)
>>             return true;
>>         S = S[1..$];
>>     }
>>     return false;
>
> I had assumed that indexing an alias[] with a mutable variable wouldn't work. If it does, then I don't even need to mutate S:
>
> bool anySatisfy(alias Tem, alias[] S)
> {
>     size_t i = 0;
>     while (i != S.length)
>     {
>         if (Tem!(S[i]))
>             return true;
>         i++;
>     }
>     return false;
> }
What do you think about this one?

bool anySatisfy(bool function (alias) pred, alias[] types ...)
{
    foreach(type; types)
    {
        if (pred(type))
            return true;
    }
    return false;
}
May 10, 2020
On Sunday, 10 May 2020 at 10:33:42 UTC, Stefan Koch wrote:
> What do you think about this one?
>
> bool anySatisfy(bool function (alias) pred, alias[] types ...)
> {
>     foreach(type; types)
>     {
>         if (pred(type))
>             return true;
>     }
>     return false;
> }

Nice, this makes me wonder if later we'll get type function lambdas :-D

I was using `while` instead of foreach because I thought the `alias[]` would be treated like a parameter sequence `S...`, and so the loop would be inlined. I suppose that wouldn't be bad here because your version takes a type function instead of a template predicate, so there's no need to avoid instantiating a template after the first true result.

I suppose you can define foreach on an alias[] to still work like a runtime foreach; that way the loop doesn't have to be inlined. (If the programmer wants inlining, use static foreach).
May 11, 2020
On Sunday, 10 May 2020 at 10:45:54 UTC, Nick Treleaven wrote:
> On Sunday, 10 May 2020 at 10:33:42 UTC, Stefan Koch wrote:
>> What do you think about this one?
>>
>> bool anySatisfy(bool function (alias) pred, alias[] types ...)
>> {
>>     foreach(type; types)
>>     {
>>         if (pred(type))
>>             return true;
>>     }
>>     return false;
>> }
>
> Nice, this makes me wonder if later we'll get type function lambdas :-D

That would be massive.

struct S(Ts...)
{
     Ts values;

     bool opCmp()(S!Ts other)
     if (allSatisfy!((alias T) => is(typeof(T.init < T.init)), Ts))
     { /* implementation */ }
}

I wonder if it there's value in not only having aliases as parameters but also specifying types and values in particular. An alias is an unrestricted compile-time name (ctName), so why not have the "subtypes" ctType and ctValue of ctName? That way, a "type function" would signal: That parameter is supposed to be (the alias of) a type, this other one (the alias of) a value. It wouldn't add any power, with this, you can do is(name) and is(typeof(name)) already to find out if `name` is an alias of a type or value respectively.
May 11, 2020
On Monday, 11 May 2020 at 21:14:47 UTC, Q. Schroll wrote:
> On Sunday, 10 May 2020 at 10:45:54 UTC, Nick Treleaven wrote:
>> On Sunday, 10 May 2020 at 10:33:42 UTC, Stefan Koch wrote:
>>> What do you think about this one?
>>>
>>> bool anySatisfy(bool function (alias) pred, alias[] types ...)
>>> {
>>>     foreach(type; types)
>>>     {
>>>         if (pred(type))
>>>             return true;
>>>     }
>>>     return false;
>>> }
>>
>> Nice, this makes me wonder if later we'll get type function lambdas :-D
>
> That would be massive.
>
> struct S(Ts...)
> {
>      Ts values;
>
>      bool opCmp()(S!Ts other)
>      if (allSatisfy!((alias T) => is(typeof(T.init < T.init)), Ts))
>      { /* implementation */ }
> }
>
> I wonder if it there's value in not only having aliases as parameters but also specifying types and values in particular. An alias is an unrestricted compile-time name (ctName), so why not have the "subtypes" ctType and ctValue of ctName? That way, a "type function" would signal: That parameter is supposed to be (the alias of) a type, this other one (the alias of) a value. It wouldn't add any power, with this, you can do is(name) and is(typeof(name)) already to find out if `name` is an alias of a type or value respectively.

In a type function alias cannot bind to values at all.
It can only bind to symbols or types.
If you want to pass values in you have to use the appropriate parameter type.
Which just like in regular functions has to be something the argument can convert to.
all types can implicitly convert to alias, which is a regular type.
and signifies that the variable holds types or symbols.
May 11, 2020
On 5/11/20 5:58 PM, Stefan Koch wrote:
> 
> In a type function alias cannot bind to values at all.
> It can only bind to symbols or types.

This seems like a problematic limitation. Aliases can bind to values when they are part of a tuple. This means that type functions are going to have to do weird template acrobatics to deal with mixed tuples.

For a long time, alias parameters wouldn't bind to int because it was a keyword, not a symbol, even though you could alias int as a declaration. It wouldn't even bind them to aliases of int (even though they are not keywords). That has since been fixed, and everything works great. I think we should avoid arbitrary limitations like this, even if it's harder to get it to work.

-Steve
May 12, 2020
On Tuesday, 12 May 2020 at 01:44:45 UTC, Steven Schveighoffer wrote:
> On 5/11/20 5:58 PM, Stefan Koch wrote:
>> 
>> In a type function alias cannot bind to values at all.
>> It can only bind to symbols or types.
>
> This seems like a problematic limitation. Aliases can bind to values when they are part of a tuple. This means that type functions are going to have to do weird template acrobatics to deal with mixed tuples.
>
> For a long time, alias parameters wouldn't bind to int because it was a keyword, not a symbol, even though you could alias int as a declaration. It wouldn't even bind them to aliases of int (even though they are not keywords). That has since been fixed, and everything works great. I think we should avoid arbitrary limitations like this, even if it's harder to get it to work.
>
> -Steve

The reason that happened is not so much a parser issue.
But it's because Basic Types are not Symbols.
Which is indeed a more or less arbitrary limitation.

Let's go through the reasoning for type functions now.
let's assume alias A would bind to a value, the string foo
for example.

string F(alias A)
{
    pragma(msg, typeof(A).stringof); // prints alias
    pragma(__traits(identifier, A); // error a string has no identifer.
    pragma(msg, is(A == string)); // prints false, because A does not hold the type string
    pragma(msg, A) prints "foo";
    pragma(msg, A == "foo") // error can't compare alias and string
    pragma(msg, is(A, "foo") // doesn't parse. The string "foo" is not a valid identifier
    return A; // error alias does not implicitly convert to string.
}

You see there would not be a point in allowing that.
May 12, 2020
On Tuesday, 12 May 2020 at 06:42:02 UTC, Stefan Koch wrote:
> On Tuesday, 12 May 2020 at 01:44:45 UTC, Steven Schveighoffer wrote:
>> On 5/11/20 5:58 PM, Stefan Koch wrote:
>>> 
>>> In a type function alias cannot bind to values at all.
>>> It can only bind to symbols or types.
>>
>> This seems like a problematic limitation. Aliases can bind to values when they are part of a tuple. This means that type functions are going to have to do weird template acrobatics to deal with mixed tuples.
>>
>> For a long time, alias parameters wouldn't bind to int because it was a keyword, not a symbol, even though you could alias int as a declaration. It wouldn't even bind them to aliases of int (even though they are not keywords). That has since been fixed, and everything works great. I think we should avoid arbitrary limitations like this, even if it's harder to get it to work.
>>
>> -Steve
>
> The reason that happened is not so much a parser issue.
> But it's because Basic Types are not Symbols.
> Which is indeed a more or less arbitrary limitation.
>
> Let's go through the reasoning for type functions now.
> let's assume alias A would bind to a value, the string foo
> for example.
>
> string F(alias A)
> {
>     pragma(msg, typeof(A).stringof); // prints alias
>     pragma(__traits(identifier, A); // error a string has no identifer.
>     pragma(msg, is(A == string)); // prints false, because A does not hold the type string
>     pragma(msg, A) prints "foo";
>     pragma(msg, A == "foo") // error can't compare alias and string
>     pragma(msg, is(A, "foo") // doesn't parse. The string "foo" is not a valid identifier
>     return A; // error alias does not implicitly convert to string.
> }
>
> You see there would not be a point in allowing that.

Honestly, to me this sounds like a total mess. If templates allow binding alias to a value, so should type functions, or they should use a different name than "alias". Otherwise it's just confusing. To me it looks like for type functions, you think of "alias" as something like the "type of types". But for alias template parameters and alias declarations it is something completely different (as Steven  explained, it is an unrestricted compile-time name). For example, consider the following:


enum tpl(alias i) = i == "foo";

void main()
{
    static immutable a = "foo";
    enum b = "foo";
    auto c = "foo";
    static assert(tpl!"foo" == true);
    static assert(tpl!a == true);
    static assert(tpl!b == true);
    static assert(!__traits(compiles, tpl!c));
}

If type functions also use "alias" to specify their parameters, from a user perspective, I'd expect the following to work just as well:

auto tpl(alias i)
{
   return i == "foo";
}

void main()
{
    static immutable a = "foo";
    enum b = "foo";
    auto c = "foo";
    static assert(tpl("foo") == true);
    static assert(tpl(a) == true);
    static assert(tpl(b) == true);
    static assert(!__traits(compiles, tpl(c)));
}

I agree that maybe there is not that much for it (you do intend to allow mixing with "regular" parameters, right? So these could be used most of the time to solve this). But if "alias" is used and this does not work, it just breaks expectations. What about simply using "type" (or something similar) instead? You are calling it _type_ functions after all...


I believe most of these issues misunderstandings come from the fact that people seem to look at this from different angles. From what I understand, you mostly look at it from an implementation perspective (and I am very grateful to you for taking the initiative here) but most others will look at it from a theoretical / language design perspective.

For example, you mentioned somewhere that you believe this could be part of the language much sooner than new CTFE. Personally, I couldn't agree less: The implementation as you envision it now might be done much faster than finishing new CTFE but for CTFE, we already know all the interactions with with other language features and how it should behave. For type functions, we basically have no clue. A _lot_ of thought needs to go into that before we can actually get something like this in the language in order to ensure we still have a consistent language and its features interact well. A "naive" implementation just won't do it.

That said, I don't want to diminish your efforts here at all. On the contrary, I appreciate it very much. This is a very good way to actually learn about possible interactions and how this fits in the language.


May 12, 2020
On 5/12/20 2:42 AM, Stefan Koch wrote:
> On Tuesday, 12 May 2020 at 01:44:45 UTC, Steven Schveighoffer wrote:
>> On 5/11/20 5:58 PM, Stefan Koch wrote:
>>>
>>> In a type function alias cannot bind to values at all.
>>> It can only bind to symbols or types.
>>
>> This seems like a problematic limitation. Aliases can bind to values when they are part of a tuple. This means that type functions are going to have to do weird template acrobatics to deal with mixed tuples.
>>
>> For a long time, alias parameters wouldn't bind to int because it was a keyword, not a symbol, even though you could alias int as a declaration. It wouldn't even bind them to aliases of int (even though they are not keywords). That has since been fixed, and everything works great. I think we should avoid arbitrary limitations like this, even if it's harder to get it to work.
>>
>> -Steve
> 
> The reason that happened is not so much a parser issue.
> But it's because Basic Types are not Symbols.
> Which is indeed a more or less arbitrary limitation.
> 
> Let's go through the reasoning for type functions now.
> let's assume alias A would bind to a value, the string foo
> for example.
> 
> string F(alias A)
> {
>      pragma(msg, typeof(A).stringof); // prints alias
>      pragma(__traits(identifier, A); // error a string has no identifer.
>      pragma(msg, is(A == string)); // prints false, because A does not hold the type string
>      pragma(msg, A) prints "foo";
>      pragma(msg, A == "foo") // error can't compare alias and string
>      pragma(msg, is(A, "foo") // doesn't parse. The string "foo" is not a valid identifier
>      return A; // error alias does not implicitly convert to string.
> }
> 
> You see there would not be a point in allowing that.

This is what happens now:

string F(A...)()
{
    pragma(msg, typeof(A[0]).stringof); // prints string
    //pragma(msg, __traits(identifier, A[0])); // [1] error a string has no identifer.
    pragma(msg, is(A[0] == string)); // prints false, because A does not hold the type string
    pragma(msg, A[0]); // prints "foo"
    pragma(msg, A[0] == "foo"); // prints true
    //pragma(msg, is(A[0], "foo")); // [2] doesn't parse. The string "foo" is not a valid identifier
    return A[0]; // works
}

enum x = F!"foo";

For [1], yeah, I get that it doesn't have an identifier. So what. Make the identifier null if you need it. Or it's an error and we deal with that. There should be mechanisms we can use to find common properties about everything. We already do that quite a bit with template programming.

For [2], there was some kind of syntax error. I'm not sure what it was you were trying to say there. The compiler complained that the comma wasn't valid. I don't want to assume anything for is expressions, there are so many forms.

If I have a function that sorts an alias[], I want it to sort AliasSeq!(62, 56, 23, 77) just as well as it sorts AliasSeq!(int, long, bool), just as well as it sorts AliasSeq!(int, 5, "hello").

The thing I want is a) mutable array instead of tuple, and b) everything is compile-time, so there is no "You can't use this at compile time" nonsense.

I was hoping that your type functions would fulfill this.

-Steve
May 12, 2020
On Tuesday, 12 May 2020 at 11:39:04 UTC, Johannes Loher wrote:

> Honestly, to me this sounds like a total mess. If templates allow binding alias to a value, so should type functions, or they should use a different name than "alias". Otherwise it's just confusing. To me it looks like for type functions, you think of "alias" as something like the "type of types". But for alias template parameters and alias declarations it is something completely different (as Steven  explained, it is an unrestricted compile-time name). For example, consider the following: [ ... ]

are you sure that that is the case for alias declarations?
For me
alias a = "Foo";
causes Error: basic type expected, not "Foo"
Which is identical to how they behave in type functions;