March 18, 2023

On Friday, 17 March 2023 at 13:37:27 UTC, Steven Schveighoffer wrote:

>

On 3/17/23 1:19 AM, Elfstone wrote:

>

Perhaps the compiler doesn't have to completely erase Foo!float before it can solve the equation? I don't know. It definitely looks easy to solve.

It looks easy to solve, until you realize that the part that has to solve it doesn't see the same thing.

It's like trying to work backwards that a C preprocessor macro was used after the macro is done rewriting the code.

I get what you are saying, but there will always be an edge where the compiler just can't figure it out, and so, some confusion is going to happen.

>

Again the current behaviour is of no use to anyone, and the compiler lets the code pass without warning is not OK. Whenever someone use a template alias (which s/he may not know is an alias) in an is expression, or as a function parameter, it should report an error, or warning, whatever.

It might be possible, it might not. The compiler doesn't know that a template is an alias until it instantiates the template. To us, reading the code, it is obvious. But internally it doesn't store things that way.

>

SHOO's case is even more intolerable. Do you expect the user to care about the hidden fact that Regex is an alias, and isInstanceOf can't do its job because Regex is an alias, all because is doesn't work with template aliases?

SHOO's case is even more unsolvable. isInstanceOf is itself a template, and therefore cached. Per the D language rules, aliases cannot cause a different instantiation.

alias Foo(T) = T;
alias Bar(T) = T;

pragma(msg, isInstanceOf!(int, Foo)); // Should this be true?

// these are now cached, since they are equivalent to the first
// Should the evaluation depend on which order you call these?
pragma(msg, isInstanceOf!(Bar!T, Foo));
pragma(msg, isInstanceOf!(Foo!T, Foo));

-Steve

I'll perhaps try to read the compiler code someday, for now it sounds to me like a matter of whether people want to fix the bug or not, even if it involves fundamental redesign of the frontend, or an extra pass to scan for unresolvable cases by design.

The compiler must at some point know that Foo is an alias, and that a particular param of isInstanceOf will be expanded to something inside is(...). Store that info if not already, report an error when Foo is passed to isInstanceOf.

March 21, 2023

On Saturday, 18 March 2023 at 01:15:35 UTC, Elfstone wrote:

>

I'll perhaps try to read the compiler code someday, for now it sounds to me like a matter of whether people want to fix the bug or not, even if it involves fundamental redesign of the frontend, or an extra pass to scan for unresolvable cases by design.

The compiler must at some point know that Foo is an alias, and that a particular param of isInstanceOf will be expanded to something inside is(...). Store that info if not already, report an error when Foo is passed to isInstanceOf.

To explain the mechanism:

The compiler knows what the template parameters are that are used to instantiate a struct that lives inside a template, because this information is stored with the struct. It can do this because - and only because! - the struct lives uniquely inside one particular template instantiation. That is, you can recover template parameters for precisely two cases, structs and classes:

template Foo(T) {
  struct Foo { }
}

You can do this because, and only because, the lexical parent of struct Foo is template Foo with a given T. Template parameter recovery works for types that have the property of uniquely storing a lexical parent; it does not work for any types that don't have this property.

This cannot ever work:

template identity(int i) { enum identity = i; }

void foo() {
    enum five = identity!5;
    static assert(is(five == identity!y, int y) && y == 5);
}

It cannot work because five does not lexically reference identity as a parent, because five is an int, and int is not a type that is or can be uniquely associated with the identity template.

What you want needs a fundamentally new language feature; something like typeclasses. Now there's an argument to be made to add typeclasses to the language, they would make ranges a lot more swanky for instance, but it's just not a matter of making template inference marginally more powerful.

March 21, 2023

On Tuesday, 21 March 2023 at 07:51:50 UTC, FeepingCreature wrote:

>

This cannot ever work:

template identity(int i) { enum identity = i; }

void foo() {
    enum five = identity!5;
    static assert(is(five == identity!y, int y) && y == 5);
}

It cannot work because five does not lexically reference identity as a parent, because five is an int, and int is not a type that is or can be uniquely associated with the identity template.

But if we make it work for struct, it will work with every type (with a little extra boilerplate):

template identity(int i) {
   struct wrapper { int w = i; }
   enum identity = w;
}

void foo() {
    enum five = identity!5;
    static assert(is(five == identity!y, int y) && y == 5);
}

And with some new lowering we could even get rid of that boilerplate.
E.g. simply add the 'parent' property to the template parameters automatically if we detect that it is used in this way somewhere (but that may cost some compile time).

March 21, 2023

On Tuesday, 21 March 2023 at 08:43:59 UTC, Dom Disc wrote:

>

But if we make it work for struct, it will work with every type (with a little extra boilerplate):

template identity(int i) {
   struct wrapper { int w = i; }
   enum identity = w;
}

void foo() {
    enum five = identity!5;
    static assert(is(five == identity!y, int y) && y == 5);
}

And with some new lowering we could even get rid of that boilerplate.
E.g. simply add the 'parent' property to the template parameters automatically if we detect that it is used in this way somewhere (but that may cost some compile time).

Define "somewhere"? Because identity can be compiled in a different compiler call to foo. Anyways, you cannot add a parent property to int, ever, because then it would be a different type. You can wrap int in a struct, you can wrap everything in a struct, and then is will just work today. But that's cumbersome.

Why not just steal traits from Rust? Then Matrix can implement Vector iff N=1, and the function can just take a Vector parameter.

March 21, 2023

On Tuesday, 21 March 2023 at 07:51:50 UTC, FeepingCreature wrote:

>

This cannot ever work:

template identity(int i) { enum identity = i; }

void foo() {
    enum five = identity!5;
    static assert(is(five == identity!y, int y) && y == 5);
}

Just to mention, even is(five) is false because five is not a type. So the static assert above would be false even if the pattern matching would succeed. (I have seen another D user say they thought that is can test a type instance - it can't). To match value patterns, another construct would be needed - perhaps a match expression.

>

It cannot work because five does not lexically reference identity as a parent, because five is an int, and int is not a type that is or can be uniquely associated with the identity template.

OK, and it also wouldn't work with alias five = identity!5;. (If it wasn't an eponymous template, an inference expression could work with that).

March 21, 2023

On Tuesday, 21 March 2023 at 17:21:00 UTC, Nick Treleaven wrote:

>

On Tuesday, 21 March 2023 at 07:51:50 UTC, FeepingCreature wrote:

>

This cannot ever work:

template identity(int i) { enum identity = i; }

void foo() {
    enum five = identity!5;
    static assert(is(five == identity!y, int y) && y == 5);
}

Just to mention, even is(five) is false because five is not a type. So the static assert above would be false even if the pattern matching would succeed. (I have seen another D user say they thought that is can test a type instance - it can't). To match value patterns, another construct would be needed - perhaps a match expression.

>

It cannot work because five does not lexically reference identity as a parent, because five is an int, and int is not a type that is or can be uniquely associated with the identity template.

OK, and it also wouldn't work with alias five = identity!5;. (If it wasn't an eponymous template, an inference expression could work with that).

Well I mean sure, I handwaved a lot here. I think if I give an example that could work, it becomes even more obvious why it can't ever work:

alias identity(int i) = int;

void main() {
    alias five = identity!5;
    static assert(is(five == identity!y, int y) && y == 5);
}

Because five is a type, not an expression, and it's a type (int) that doesn't have a lexical parent.

March 22, 2023

On Tuesday, 21 March 2023 at 07:51:50 UTC, FeepingCreature wrote:

>

On Saturday, 18 March 2023 at 01:15:35 UTC, Elfstone wrote:

>

I'll perhaps try to read the compiler code someday, for now it sounds to me like a matter of whether people want to fix the bug or not, even if it involves fundamental redesign of the frontend, or an extra pass to scan for unresolvable cases by design.

The compiler must at some point know that Foo is an alias, and that a particular param of isInstanceOf will be expanded to something inside is(...). Store that info if not already, report an error when Foo is passed to isInstanceOf.

To explain the mechanism:

The compiler knows what the template parameters are that are used to instantiate a struct that lives inside a template, because this information is stored with the struct. It can do this because - and only because! - the struct lives uniquely inside one particular template instantiation. That is, you can recover template parameters for precisely two cases, structs and classes:

template Foo(T) {
  struct Foo { }
}

You can do this because, and only because, the lexical parent of struct Foo is template Foo with a given T. Template parameter recovery works for types that have the property of uniquely storing a lexical parent; it does not work for any types that don't have this property.

This cannot ever work:

template identity(int i) { enum identity = i; }

void foo() {
    enum five = identity!5;
    static assert(is(five == identity!y, int y) && y == 5);
}

It cannot work because five does not lexically reference identity as a parent, because five is an int, and int is not a type that is or can be uniquely associated with the identity template.

What you want needs a fundamentally new language feature; something like typeclasses. Now there's an argument to be made to add typeclasses to the language, they would make ranges a lot more swanky for instance, but it's just not a matter of making template inference marginally more powerful.

Wow, I didn't know this too wouldn't work. So isInstanceOf!(identity, five) will also yield false. But I can understand because like you said there's no way to infer y, unless 5 is somehow stored with five, which is just an int. It's quite different from Matrix!(float, 3, 1).

I wouldn't call what I want a new feature, but a fix to this gaping hole in the language design (or implementation). People would expect an alias works the same as the original symbol, at least people need not care if they are using an alias from std (Regex), as long as the compiler will generate a warning where an alias might cause problems.

March 22, 2023

On Tuesday, 21 March 2023 at 07:51:50 UTC, FeepingCreature wrote:

>

On Saturday, 18 March 2023 at 01:15:35 UTC, Elfstone wrote:

>

I'll perhaps try to read the compiler code someday, for now it sounds to me like a matter of whether people want to fix the bug or not, even if it involves fundamental redesign of the frontend, or an extra pass to scan for unresolvable cases by design.

The compiler must at some point know that Foo is an alias, and that a particular param of isInstanceOf will be expanded to something inside is(...). Store that info if not already, report an error when Foo is passed to isInstanceOf.

To explain the mechanism:

The compiler knows what the template parameters are that are used to instantiate a struct that lives inside a template, because this information is stored with the struct. It can do this because - and only because! - the struct lives uniquely inside one particular template instantiation. That is, you can recover template parameters for precisely two cases, structs and classes:

template Foo(T) {
  struct Foo { }
}

You can do this because, and only because, the lexical parent of struct Foo is template Foo with a given T. Template parameter recovery works for types that have the property of uniquely storing a lexical parent; it does not work for any types that don't have this property.

This cannot ever work:

template identity(int i) { enum identity = i; }

void foo() {
    enum five = identity!5;
    static assert(is(five == identity!y, int y) && y == 5);
}

It cannot work because five does not lexically reference identity as a parent, because five is an int, and int is not a type that is or can be uniquely associated with the identity template.

What you want needs a fundamentally new language feature; something like typeclasses. Now there's an argument to be made to add typeclasses to the language, they would make ranges a lot more swanky for instance, but it's just not a matter of making template inference marginally more powerful.

Wow, I didn't know this too wouldn't work. So isInstanceOf!(identity, five) will also yield false. But I can understand because like you said there's no way to infer y, unless 5 is somehow stored with five, which is just an int. It's quite different from Matrix!(float, 3, 1).

I wouldn't call what I want a new feature, but a fix to this big hole in the language design (or implementation). People would expect an alias works the same as the original symbol, at least people need not care if they are using an alias from std (Regex), as long as the compiler will generate a warning where an alias might cause problems.

March 22, 2023

On Wednesday, 22 March 2023 at 07:25:04 UTC, Elfstone wrote:

>

I wouldn't call what I want a new feature, but a fix to this big hole in the language design (or implementation). People would expect an alias works the same as the original symbol, at least people need not care if they are using an alias from std (Regex), as long as the compiler will generate a warning where an alias might cause problems.

Well, an alias works the same as the original symbol, that's rather the problem. You can't infer the template instantiation that produced the alias, because the compiler doesn't see the alias after the fact, because "an alias member" is not a lexical member in the same way that "a templated struct" is.

My opinion is that we're several steps into an X-Y problem here, where what we actually want is to indicate that a value fulfills a certain constraint - "is a vector" - in a more freeform fashion than template constraints allow us to, without having to write lots of isVector type predicates, in a way that's legible to the typesystem. I think something like type traits or typeclasses would fulfill this need in a cleaner way and also make a lot of range code better.

March 22, 2023

On Wednesday, 22 March 2023 at 09:22:01 UTC, FeepingCreature wrote:

>

On Wednesday, 22 March 2023 at 07:25:04 UTC, Elfstone wrote:

>

I wouldn't call what I want a new feature, but a fix to this big hole in the language design (or implementation). People would expect an alias works the same as the original symbol, at least people need not care if they are using an alias from std (Regex), as long as the compiler will generate a warning where an alias might cause problems.

Well, an alias works the same as the original symbol, that's rather the problem. You can't infer the template instantiation that produced the alias, because the compiler doesn't see the alias after the fact, because "an alias member" is not a lexical member in the same way that "a templated struct" is.

My opinion is that we're several steps into an X-Y problem here, where what we actually want is to indicate that a value fulfills a certain constraint - "is a vector" - in a more freeform fashion than template constraints allow us to, without having to write lots of isVector type predicates, in a way that's legible to the typesystem. I think something like type traits or typeclasses would fulfill this need in a cleaner way and also make a lot of range code better.

I don't know how C++ compilers resolve template using just fine.

template <typename T>
using Vector3 = Matrix<T, 3, 1>;

I declare an alias and I use it everywhere. It acts as a natural constraint. I never needed isVector3 with my old C++ code. I expected D could do the same, and was really frustrated when I found out it couldn't. Even more frustrated when I read Steven's reply, that the bug with is has been there for 16 years.

When the compiler allows people to use template aliases as template function parameters, it should make it work, or reject it aloud. Maybe some future system can save the day, but does it mean the old alias will just be left bugged?