www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Does `is` expression with template alias need fixing.

reply Elfstone <elfstone yeah.net> writes:
```D
struct Matrix(S, size_t M, size_t N)
{
}

alias Vec3(S) = Matrix!(S, 3, 1);

void foo(U)(U v) if (is(U == Vec3!S, S))
{
}

void bar(U)(Vec3!U v) {
}

void main()
{
     import std.stdio;

     Vec3!float v;
     writeln(is(typeof(v) == Vec3!S, S)); // false
     // bar(v); // Error, no bar callable using Matrix!(float, 3, 
1). huh!
     // foo(v); // Error, doesn't satisfy constraint.
}
```

*This is related to DIP1023.
Since we can't use template alias parameters at the moment, how 
about making writing a constraint more straightforward?

Honestly, this looks like a bugged design:
```D
writeln(is(Vec3!float == Vec3!S, S)); // false
```
I can live with macros than this.

I have no idea how the compiler works, but I have the feeling 
fixing `is` also fixes template alias parameters but anyways, 
even if I 'm OK with the excuse that "constraints are enough", I 
don't like the idea of writing `Matrix!(S, 3, 1)` a second time 
to create the constraint.
Mar 14 2023
next sibling parent reply SHOO <zan77137 nifty.com> writes:
On Wednesday, 15 March 2023 at 06:47:38 UTC, Elfstone wrote:
 ```D
 writeln(is(Vec3!float == Vec3!S, S)); // false
 ```
I recently faced a similar problem. ```d import std; static assert(isInstanceOf!(Array, Array!char)); // true static assert(isInstanceOf!(Regex, Regex!char)); // false ```
Mar 15 2023
parent reply Elfstone <elfstone yeah.net> writes:
On Wednesday, 15 March 2023 at 11:59:00 UTC, SHOO wrote:
 On Wednesday, 15 March 2023 at 06:47:38 UTC, Elfstone wrote:
 ```D
 writeln(is(Vec3!float == Vec3!S, S)); // false
 ```
I recently faced a similar problem. ```d import std; static assert(isInstanceOf!(Array, Array!char)); // true static assert(isInstanceOf!(Regex, Regex!char)); // false ```
The compiler should at least report it as an error: `is` cannot handle template alias.
Mar 15 2023
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On 3/16/23 12:10 AM, Elfstone wrote:
 On Wednesday, 15 March 2023 at 11:59:00 UTC, SHOO wrote:
 On Wednesday, 15 March 2023 at 06:47:38 UTC, Elfstone wrote:
 ```D
 writeln(is(Vec3!float == Vec3!S, S)); // false
 ```
I recently faced a similar problem. ```d import std; static assert(isInstanceOf!(Array, Array!char)); // true static assert(isInstanceOf!(Regex, Regex!char)); // false ```
The compiler should at least report it as an error: `is` cannot handle template alias.
It can't see that. The issue is that you are looking through a one-way window. In the general case, the compiler can't solve the puzzle, because it doesn't know the relationship between the template and the thing it gets: ```d alias Foo(T) = int; // the following are equivalent to the compiler is(Foo!float == Foo!T, T); is(int == Foo!T, T); ``` How is the compiler supposed to figure out what T you used? By the time it sees the type, it's coming in as `int`, the alias template itself is completely gone. Now, I believe the compiler could make a special case for simple aliases, but the rules have to be defined, and the language designers need to agree to add it. It is a frustrating limitation. One I've brought up myself almost 16 years ago: https://issues.dlang.org/show_bug.cgi?id=1653 I hope some day someone can find a nice way to solve it, at least for IFTI. -Steve
Mar 16 2023
parent reply Elfstone <elfstone yeah.net> writes:
On Friday, 17 March 2023 at 00:40:02 UTC, Steven Schveighoffer 
wrote:
 On 3/16/23 12:10 AM, Elfstone wrote:
 On Wednesday, 15 March 2023 at 11:59:00 UTC, SHOO wrote:
 On Wednesday, 15 March 2023 at 06:47:38 UTC, Elfstone wrote:
 ```D
 writeln(is(Vec3!float == Vec3!S, S)); // false
 ```
I recently faced a similar problem. ```d import std; static assert(isInstanceOf!(Array, Array!char)); // true static assert(isInstanceOf!(Regex, Regex!char)); // false ```
The compiler should at least report it as an error: `is` cannot handle template alias.
It can't see that. The issue is that you are looking through a one-way window. In the general case, the compiler can't solve the puzzle, because it doesn't know the relationship between the template and the thing it gets: ```d alias Foo(T) = int; // the following are equivalent to the compiler is(Foo!float == Foo!T, T); is(int == Foo!T, T); ``` How is the compiler supposed to figure out what T you used? By the time it sees the type, it's coming in as `int`, the alias template itself is completely gone. Now, I believe the compiler could make a special case for simple aliases, but the rules have to be defined, and the language designers need to agree to add it. It is a frustrating limitation. One I've brought up myself almost 16 years ago: https://issues.dlang.org/show_bug.cgi?id=1653 I hope some day someone can find a nice way to solve it, at least for IFTI. -Steve
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. 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. 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?
Mar 16 2023
parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
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. ```d 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
Mar 17 2023
parent reply Elfstone <elfstone yeah.net> writes:
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. ```d 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.
Mar 17 2023
parent reply FeepingCreature <feepingcreature gmail.com> writes:
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](https://github.com/dlang/dmd/blob/v2.102.2/compiler/src/dm /dtemplate.d#L4312) and [classes](https://github.com/dlang/dmd/blob/v2.102.2/compiler/src/dmd/dtemplate.d#L4450): ``` 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.
Mar 21 2023
next sibling parent reply Dom Disc <dominikus scherkl.de> writes:
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).
Mar 21 2023
parent FeepingCreature <feepingcreature gmail.com> writes:
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.
Mar 21 2023
prev sibling next sibling parent reply Nick Treleaven <nick geany.org> writes:
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).
Mar 21 2023
parent FeepingCreature <feepingcreature gmail.com> writes:
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.
Mar 21 2023
prev sibling next sibling parent Elfstone <elfstone yeah.net> writes:
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](https://github.com/dlang/dmd/blob/v2.102.2/compiler/src/dm /dtemplate.d#L4312) and [classes](https://github.com/dlang/dmd/blob/v2.102.2/compiler/src/dmd/dtemplate.d#L4450): ``` 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.
Mar 22 2023
prev sibling next sibling parent reply Elfstone <elfstone yeah.net> writes:
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](https://github.com/dlang/dmd/blob/v2.102.2/compiler/src/dm /dtemplate.d#L4312) and [classes](https://github.com/dlang/dmd/blob/v2.102.2/compiler/src/dmd/dtemplate.d#L4450): ``` 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.
Mar 22 2023
parent reply FeepingCreature <feepingcreature gmail.com> writes:
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.
Mar 22 2023
parent reply Elfstone <elfstone yeah.net> writes:
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. ```C++ 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?
Mar 22 2023
next sibling parent reply zjh <fqbqrr 163.com> writes:
On Wednesday, 22 March 2023 at 10:43:15 UTC, Elfstone wrote:
 ```C++
 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 you have questions, please file a bugzilla. D is only for `specific problems`, please describe the problem carefully.
Mar 22 2023
next sibling parent zjh <fqbqrr 163.com> writes:
On Wednesday, 22 March 2023 at 11:00:40 UTC, zjh wrote:

```d
alias Vec3(S) = Matrix!(S, 3, 1);
void foo(U)(U v)
if (is(U == Matrix!(S,3,1), S)) { }

void foo2(U)(U v)
if (is(U == Vec3!S, S)) { }

```
`Foo` can compile, but `foo2` cannot compile, which is very 
strange!
`C++` has no problem.
Mar 22 2023
prev sibling next sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
On Wednesday, 22 March 2023 at 11:00:40 UTC, zjh wrote:
 On Wednesday, 22 March 2023 at 10:43:15 UTC, Elfstone wrote:
 ```C++
 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 you have questions, please file a bugzilla. D is only for `specific problems`, please describe the problem carefully.
What the OP is referring to is an old D bug [1]. In this thread, he's not really asking questions so much as highlighting that it is still an issue. [1] https://issues.dlang.org/show_bug.cgi?id=1807
Mar 22 2023
parent FeepingCreature <feepingcreature gmail.com> writes:
On Wednesday, 22 March 2023 at 12:54:52 UTC, jmh530 wrote:
 What the OP is referring to is an old D bug [1]. In this 
 thread, he's not really asking questions so much as 
 highlighting that it is still an issue.

 [1] https://issues.dlang.org/show_bug.cgi?id=1807
Ah, thank you! Yeah now that I see it's more about specifically recognizing and dissolving the alias usage on the *callee* side during type inference, it makes a lot more sense. (Accidentally filed a dupe :) )
Mar 22 2023
prev sibling parent reply Elfstone <elfstone yeah.net> writes:
On Wednesday, 22 March 2023 at 11:00:40 UTC, zjh wrote:
 On Wednesday, 22 March 2023 at 10:43:15 UTC, Elfstone wrote:
 ```C++
 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 you have questions, please file a bugzilla. D is only for `specific problems`, please describe the problem carefully.
There are already a ton issues about this filed, including Steven's 16 years old one. It seems the community lacks the motivation to fix it. That's why I started this thread.
Mar 22 2023
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 3/23/23 01:23, Elfstone wrote:
 On Wednesday, 22 March 2023 at 11:00:40 UTC, zjh wrote:
 On Wednesday, 22 March 2023 at 10:43:15 UTC, Elfstone wrote:
 ```C++
 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 you have questions, please file a bugzilla. D is only for `specific problems`, please describe the problem carefully.
There are already a ton issues about this filed, including Steven's 16 years old one. It seems the community lacks the motivation to fix it. That's why I started this thread.
That's just not true. People are motivated enough to fix it technically, but not to go through the motions needed to get their contribution actually accepted into the language. https://forum.dlang.org/post/kvcrsoqozrflxibgxtlo forum.dlang.org https://github.com/dlang/dmd/pull/9778 https://github.com/dlang/DIPs/blob/master/DIPs/other/DIP1023.md#reviews However, unfortunately the DIP process is often where contributions go to die. I guess one reason for this is because it is not so easy to enforce your own quality standards on the volunteer work of others and people have different priorities.
Mar 27 2023
next sibling parent Elfstone <elfstone yeah.net> writes:
On Monday, 27 March 2023 at 22:33:14 UTC, Timon Gehr wrote:
 On 3/23/23 01:23, Elfstone wrote:
 On Wednesday, 22 March 2023 at 11:00:40 UTC, zjh wrote:
 On Wednesday, 22 March 2023 at 10:43:15 UTC, Elfstone wrote:
 ```C++
 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 you have questions, please file a bugzilla. D is only for `specific problems`, please describe the problem carefully.
There are already a ton issues about this filed, including Steven's 16 years old one. It seems the community lacks the motivation to fix it. That's why I started this thread.
That's just not true. People are motivated enough to fix it technically, but not to go through the motions needed to get their contribution actually accepted into the language. https://forum.dlang.org/post/kvcrsoqozrflxibgxtlo forum.dlang.org https://github.com/dlang/dmd/pull/9778 https://github.com/dlang/DIPs/blob/master/DIPs/other/DIP1023.md#reviews However, unfortunately the DIP process is often where contributions go to die. I guess one reason for this is because it is not so easy to enforce your own quality standards on the volunteer work of others and people have different priorities.
I understand. I'm aware of DIP1023 and I did mention it in my original post. Not trying to overlook the attempt, but it seemingly died. This is where we are at. All I can do is voice myself, hoping more people will notice and agree this is a problem - 'cause that's how I reason why D has offered so much but left such a hole in `alias` - still not enough people see it as a big hole. Well it's not that big but it's a scar in the core. People do fall into it, and the error message is nonsense.
Mar 28 2023
prev sibling parent Elfstone <elfstone yeah.net> writes:
On Monday, 27 March 2023 at 22:33:14 UTC, Timon Gehr wrote:
 On 3/23/23 01:23, Elfstone wrote:
 On Wednesday, 22 March 2023 at 11:00:40 UTC, zjh wrote:
 On Wednesday, 22 March 2023 at 10:43:15 UTC, Elfstone wrote:
 ```C++
 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 you have questions, please file a bugzilla. D is only for `specific problems`, please describe the problem carefully.
There are already a ton issues about this filed, including Steven's 16 years old one. It seems the community lacks the motivation to fix it. That's why I started this thread.
That's just not true. People are motivated enough to fix it technically, but not to go through the motions needed to get their contribution actually accepted into the language. https://forum.dlang.org/post/kvcrsoqozrflxibgxtlo forum.dlang.org https://github.com/dlang/dmd/pull/9778 https://github.com/dlang/DIPs/blob/master/DIPs/other/DIP1023.md#reviews However, unfortunately the DIP process is often where contributions go to die. I guess one reason for this is because it is not so easy to enforce your own quality standards on the volunteer work of others and people have different priorities.
I understand. I'm aware of DIP1023 and I did mention it in my original post. Not trying to overlook the attempt, but it seemingly died. This is where we are at. All I can do is voice myself, hoping more people will notice and agree this is a problem - 'cause that's how I reason why D has offered so much but left such a hole in `alias` - still not enough people see it as a big hole. Well it's not that big but it's a scar in the core. People do fall into it, and the error message is nonsense.
Mar 28 2023
prev sibling parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Wednesday, 22 March 2023 at 10:43:15 UTC, Elfstone wrote:
 I don't know how C++ compilers resolve `template using` just 
 fine.

 ```C++
 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?
You can perfectly fine use `alias Vector3(T) = Matrix!(T, 3, 1)` in D. You cannot *constrain a type to be Vector3* in D, but you cannot in C++ either. (I don't think so? I'm poking at C++20 concepts, but I don't see a way to do it.)
Mar 22 2023
next sibling parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Wednesday, 22 March 2023 at 12:30:46 UTC, FeepingCreature 
wrote:
 On Wednesday, 22 March 2023 at 10:43:15 UTC, Elfstone wrote:
 I don't know how C++ compilers resolve `template using` just 
 fine.

 ```C++
 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?
You can perfectly fine use `alias Vector3(T) = Matrix!(T, 3, 1)` in D. You cannot *constrain a type to be Vector3* in D, but you cannot in C++ either. (I don't think so? I'm poking at C++20 concepts, but I don't see a way to do it.)
Oh wait, correction: you *can* do it! Neat! I wonder how that works. Probably it's because `using` is more syntactically constrained? ``` template <typename T, int M, int N> struct Matrix { }; template <typename T> using Vector3 = Matrix<T, 3, 1>; template<typename T> void baz(Vector3<T>& vector) { } int main() { auto v = Vector3<int>(); baz(v); } ```
Mar 22 2023
parent FeepingCreature <feepingcreature gmail.com> writes:
On Wednesday, 22 March 2023 at 12:34:26 UTC, FeepingCreature 
wrote:
 On Wednesday, 22 March 2023 at 12:30:46 UTC, FeepingCreature 
 wrote:
 On Wednesday, 22 March 2023 at 10:43:15 UTC, Elfstone wrote:
 I don't know how C++ compilers resolve `template using` just 
 fine.

 ```C++
 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?
You can perfectly fine use `alias Vector3(T) = Matrix!(T, 3, 1)` in D. You cannot *constrain a type to be Vector3* in D, but you cannot in C++ either. (I don't think so? I'm poking at C++20 concepts, but I don't see a way to do it.)
Oh wait, correction: you *can* do it! Neat! I wonder how that works. Probably it's because `using` is more syntactically constrained? ``` template <typename T, int M, int N> struct Matrix { }; template <typename T> using Vector3 = Matrix<T, 3, 1>; template<typename T> void baz(Vector3<T>& vector) { } int main() { auto v = Vector3<int>(); baz(v); } ```
Looks like it substitutes through simple cases but fails with more complex ones? ``` #include <type_traits> template <typename T, int M, int N> struct Matrix { }; template <typename T> using Vector3 = typename std::conditional<true, Matrix<T, 3, 1>, void>::type; template<typename T> void baz(Vector3<T>& vector) { } int main() { auto v = Vector3<int>(); /* test.cpp:17:6: error: no matching function for call to ‘baz(Matrix<int, 3, 1>&)’ 17 | baz(v); | ~~~^~~ test.cpp:11:6: note: candidate: ‘template<class T> void baz(Vector3<T>&)’ 11 | void baz(Vector3<T>& vector) | ^~~ test.cpp:11:6: note: template argument deduction/substitution failed: test.cpp:17:6: note: couldn’t deduce template parameter ‘T’ */ baz(v); } ``` That much we can probably implement in D, granted: specifically recognize trivial aliases in the template resolution code. Do file a bug for that.
Mar 22 2023
prev sibling parent reply Elfstone <elfstone yeah.net> writes:
On Wednesday, 22 March 2023 at 12:30:46 UTC, FeepingCreature 
wrote:

 You cannot *constrain a type to be Vector3* in D
That's the problem.
 you cannot in C++ either.
Yes, I can. ```C++ template <typename T> using Vector3 = Matrix<T, 3, 1>; template <typename T> void foo(const Vector3<T>& v) { } int main() { foo(Vector3<float>()); } ``` I can declare parameters with Vector3\<T\>, and it gets me the right T, which is all I need. D allows me to declare template alias parameter but it matches nothing at all. Then why allow people to write template alias parameters at all?
Mar 22 2023
parent reply kdevel <kdevel vogtner.de> writes:
On Thursday, 23 March 2023 at 00:36:12 UTC, Elfstone wrote:
 Yes, I can.

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

 template <typename T>
 void foo(const Vector3<T>& v) {
 }

 int main() {
 	foo(Vector3<float>());
 }
 ```
 I can declare parameters with Vector3\<T\>, and it gets me the 
 right T, which is all I need.

 D allows me to declare template alias parameter but it matches 
 nothing at all. Then why allow people to write template alias 
 parameters at all?
It is interesting that this ``` struct B (T, V) { } alias A = B!(double, void); void f (A) { } void main () { A a; f (a); B!(double, void) b; f (b); // works } ``` compiles while the version with incomplete specialization requires a version of `f` not using the alias template in the parameter list: ``` struct B (T, V) { } alias A (T) = B!(T, void); // void f (T) (A!(T)) { } // fail void f (T) (B!(T, void)) { } // cannot use A!T here, why not? void main () { A!double a; f (a); // does not call void f (T) (A!(T)) B!(double, void) b; f (b); // does not call void f (T) (A!(T)) } ``` In the failing case dmd says ``` vm.d(11): Error: none of the overloads of template `vm.f` are callable using argument types `!()(B!(double, void))` vm.d(5): Candidate is: `f(T)(A!T)` vm.d(13): Error: none of the overloads of template `vm.f` are callable using argument types `!()(B!(double, void))` vm.d(5): Candidate is: `f(T)(A!T)` ``` It seems that dmd views `A!double` and `B!(double, void)` as different types which they aren't. Isn't there an alias-expansion phase during compilation? [1] https://issues.dlang.org/show_bug.cgi?id=23798
Mar 23 2023
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Thursday, 23 March 2023 at 12:40:47 UTC, kdevel wrote:
 On Thursday, 23 March 2023 at 00:36:12 UTC, Elfstone wrote:
 Yes, I can.

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

 template <typename T>
 void foo(const Vector3<T>& v) {
 }

 int main() {
 	foo(Vector3<float>());
 }
 ```
 I can declare parameters with Vector3\<T\>, and it gets me the 
 right T, which is all I need.

 D allows me to declare template alias parameter but it matches 
 nothing at all. Then why allow people to write template alias 
 parameters at all?
It is interesting that this ``` struct B (T, V) { } alias A = B!(double, void); void f (A) { } void main () { A a; f (a); B!(double, void) b; f (b); // works } ``` compiles while the version with incomplete specialization requires a version of `f` not using the alias template in the parameter list: ``` struct B (T, V) { } alias A (T) = B!(T, void); // void f (T) (A!(T)) { } // fail void f (T) (B!(T, void)) { } // cannot use A!T here, why not? void main () { A!double a; f (a); // does not call void f (T) (A!(T)) B!(double, void) b; f (b); // does not call void f (T) (A!(T)) } ``` In the failing case dmd says ``` vm.d(11): Error: none of the overloads of template `vm.f` are callable using argument types `!()(B!(double, void))` vm.d(5): Candidate is: `f(T)(A!T)` vm.d(13): Error: none of the overloads of template `vm.f` are callable using argument types `!()(B!(double, void))` vm.d(5): Candidate is: `f(T)(A!T)` ``` It seems that dmd views `A!double` and `B!(double, void)` as different types which they aren't. Isn't there an alias-expansion phase during compilation? [1] https://issues.dlang.org/show_bug.cgi?id=23798
You're misunderstanding the problem. When you call a template function, DMD (and C++) performs IFTI, implicit function template instantiation. This requires *reverse-engineering* a type T that fulfills the requirement for the function call. For `void f(T)(A!T)`, DMD has to unify the given *parameter type* with the abstract type expression `A!T` and figure out a matching `T`. If `A` is a struct, it can do this because for a struct type, DMD *knows which template instance it came from* and can just look up the parameter type that was used to create it. It looks it up, to be clear, *in the caller's type*. But an alias is not itself a type! So in the case where `A` is an alias, C++ and DMD have to do the *opposite* and figure out that `A` - **not** the type `A!T`, because we don't know `T` yet, but the `A` from the *syntax node* `A!T` in the *callee!* - is an alias template with a trivial expansion, so that *for the purpose of type inference only*, the abstract expression `A!T` can be treated as equivalent to `B!(T, void)` - still without having any concrete `T`, just a syntax node `T`. So in C++, it's just as if you wrote `void f(T)(B!(T, void)) {}`, because it substitutes `A!T`, *sight unseen*, in the *called function's parameter list*. That's why this works: ``` template <typename T, int M, int N> struct Matrix { }; template <typename T> using Vector3 = Matrix<T, 3, 1>; /**Exactly equivalent to template<typename T> void foo(Vector<T> vector) {}*/ template<typename T> void foo(Matrix<T, 3, 1> vector) {} int main() { foo(Vector3<int>()); } ``` D does not do this yet.
Mar 24 2023
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Friday, 24 March 2023 at 09:30:30 UTC, FeepingCreature wrote:
 You're misunderstanding the problem.

 When you call a template function, DMD (and C++) performs IFTI, 
 implicit function template instantiation. This requires 
 *reverse-engineering* a type T that fulfills the requirement 
 for the function call.
It's important to understand that IFTI does something *completely different* than normal type inference: it does *backwards* type inference. It's more similar to [Hindley-Milner] than D's normal "forward" type resolution. For instance, if you write a template like this: ``` void foo(T)(void delegate(T) dg) { } foo((int i) {}); ``` Then the compiler infers `T` as `int` by recursing on the expression "`void delegate(T)` = `void delegate(int)`". It takes the explicit template parameters and calling parameter types and uses them as nodes in an algebraic system, that it can use to *solve for* the template parameter types. Or if you write a template like this: ``` T foo(T)(T a, T b) { } foo(2, 3.5); ``` Then the compiler starts with two algebraic expressions: `int : T` and `double : T` (`:` meaning "is convertible to"), which it solves by *unifying* `int` and `double` into `double`. It is important to understand that `T` here is a **free variable**, not yet a type; the point is to *assign it* a type. So we cannot instantiate a template with `T`, because we can only instantiate templates with concrete types. Finally, in our example: ``` alias A(T) = B!T; void foo(T)(A!T) {} foo(B!int); ``` The compiler starts with an expression `B!int = A!T`, and is trying to solve for T. It looks at `A!T`, and it thinks: "hm, the other term could be a template instantiation of the template `A` with something." But `B!T` is a template instantiation of `B`, not `A`, so it fails to match. If we insert the missing step, what the compiler should do is: "Ah, `B` is a 'trivial alias' (like `using` in C++), so I can *syntactically substitute* it with its content", and create the new expression `B!int = B!T`. Then it can resolve this by noting that *the concrete type* `B!int` is an instance of `B` with the type `int`, and thus gain the new expression `T = int`, which solves the algebraic system. [Hindley-Milner]: https://en.wikipedia.org/wiki/Hindley%E2%80%93Milner_type_system
Mar 24 2023
next sibling parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Friday, 24 March 2023 at 09:44:28 UTC, FeepingCreature wrote:
 If we insert the missing step, what the compiler should do is: 
 "Ah, `B` is a 'trivial alias'
My apologies, correction: `A` is a 'trivial alias'. Sorry for the minipost, but this topic demands precision.
Mar 24 2023
parent reply kdevel <kdevel vogtner.de> writes:
On Friday, 24 March 2023 at 09:47:59 UTC, FeepingCreature wrote:
 On Friday, 24 March 2023 at 09:44:28 UTC, FeepingCreature wrote:
 If we insert the missing step, what the compiler should do is: 
 "Ah, `B` is a 'trivial alias'
My apologies, correction: `A` is a 'trivial alias'. Sorry for the minipost, but this topic demands precision.
Thanks for explaining what goes on under the hood. I would like to branch to something entirely different, namely the original code Elfstone drafted in the first post: ``` struct Matrix(S, size_t M, size_t N) { } alias Vec3(S) = Matrix!(S, 3, 1); ``` What is conceptually wrong with this code? Can't we read everywhere [1] that matrices with only one column ‘are’ (column) vectors? However, a matrix is a two-dimensional aggregate, its elements are refered to with two indices. By contrast vectors are one-dimensional. Of course it is elegant to define the dot product of a (row) vector and a (column) vector in terms of matrix multiplication [2] but the interesting part here is the the ‘identification’ of the result (dot product) with the element (1, 1) of the resulting 1×1 matrix. Sometimes this precision gets lost, e.g. [3]. Hence: A vector should not be defined as a matrix with only column (or row). [1] https://www.google.de/search?q=vectors+are+matrices+with+one+column [2] https://en.wikipedia.org/wiki/Dot_product [3] https://yutsumura.com/a-relation-between-the-dot-product-and-the-trace/ - "Recall that v^Tw is, by definition, the dot product of the vectors v and w."
Mar 24 2023
next sibling parent jmh530 <john.michael.hall gmail.com> writes:
On Friday, 24 March 2023 at 12:29:02 UTC, kdevel wrote:
 [snip]

 Thanks for explaining what goes on under the hood. I would like 
 to branch to something entirely different, namely the original 
 code Elfstone drafted in the first post:

 ```
 struct Matrix(S, size_t M, size_t N)
 {
 }

 alias Vec3(S) = Matrix!(S, 3, 1);
 ```

 What is conceptually wrong with this code? Can't we read 
 everywhere [1] that matrices with only one column ‘are’ 
 (column) vectors?

 [snip]
There's a lot that can get hidden in `Matrix` since this uses template parameters to define the dimensions of a `Matrix` (like a static array). For instance, it could specialize code for the case where `N=1` so that it really behaves like a vector. It could have a special dot product function that behaves differently in this case vs. normal matrix multiplication (and could call that dot product code when doing a matrix multiplication instead, BLAS has ddot and dgemm and D is powerful enough to call different ones at compile time).
Mar 24 2023
prev sibling parent Elfstone <elfstone yeah.net> writes:
On Friday, 24 March 2023 at 12:29:02 UTC, kdevel wrote:
 On Friday, 24 March 2023 at 09:47:59 UTC, FeepingCreature wrote:
 On Friday, 24 March 2023 at 09:44:28 UTC, FeepingCreature 
 wrote:
 If we insert the missing step, what the compiler should do 
 is: "Ah, `B` is a 'trivial alias'
My apologies, correction: `A` is a 'trivial alias'. Sorry for the minipost, but this topic demands precision.
Thanks for explaining what goes on under the hood. I would like to branch to something entirely different, namely the original code Elfstone drafted in the first post: ``` struct Matrix(S, size_t M, size_t N) { } alias Vec3(S) = Matrix!(S, 3, 1); ``` What is conceptually wrong with this code? Can't we read everywhere [1] that matrices with only one column ‘are’ (column) vectors? However, a matrix is a two-dimensional aggregate, its elements are refered to with two indices. By contrast vectors are one-dimensional. Of course it is elegant to define the dot product of a (row) vector and a (column) vector in terms of matrix multiplication [2] but the interesting part here is the the ‘identification’ of the result (dot product) with the element (1, 1) of the resulting 1×1 matrix. Sometimes this precision gets lost, e.g. [3]. Hence: A vector should not be defined as a matrix with only column (or row). [1] https://www.google.de/search?q=vectors+are+matrices+with+one+column [2] https://en.wikipedia.org/wiki/Dot_product [3] https://yutsumura.com/a-relation-between-the-dot-product-and-the-trace/ - "Recall that v^Tw is, by definition, the dot product of the vectors v and w."
The design can't be perfect for everything but it's good enough for many things. `Eigen` in C++ does the same and I think it's still one of the best libraries out there. Actually the main benifit I see is that I don't have to specialize heavy operations such as `Matrix * Vector` with largely the same code as the general `Matrix * Matrix`. But I do happily define `opIndex(size_t i)` when `M == 1 || N == 1`, which is trivial. It's but a convenient example to demonstrate the problem with D's `alias` anyway.
Mar 24 2023
prev sibling next sibling parent Elfstone <elfstone yeah.net> writes:
On Friday, 24 March 2023 at 09:44:28 UTC, FeepingCreature wrote:

 If we insert the missing step, what the compiler should do is: 
 "Ah, `B` is a 'trivial alias' (like `using` in C++), so I can 
 *syntactically substitute* it with its content", and create the 
 new expression `B!int = B!T`. Then it can resolve this by 
 noting that *the concrete type* `B!int` is an instance of `B` 
 with the type `int`, and thus gain the new expression `T = 
 int`, which solves the algebraic system.
That alone will be a huge improvement. There's no need to resolve all cases. DIP1023 should not try to do that, if it's ever picked up. ```C++ template <typename T> using Identity = int; template <typename T> void bar(Identity<T> id) { } int main() { bar(Identity<int>()); } ``` C++ doesn't automatically resolve this either, and I bet no one will ask it to. When you think about it: for now, D allows writing template alias paramters, but AFAIK they will match absolutely **nothing**! Nothing you pass will ever be accepted.
Mar 24 2023
prev sibling parent jmh530 <john.michael.hall gmail.com> writes:
On Friday, 24 March 2023 at 09:44:28 UTC, FeepingCreature wrote:
 [snip]

 It's important to understand that IFTI does something 
 *completely different* than normal type inference: it does 
 *backwards* type inference. It's more similar to 
 [Hindley-Milner] than D's normal "forward" type resolution.
I agree that it needs some kind of backwards type resolution. Using the same initial example of the OP, consider something like below (that's not valid D code currently). ```d template Vec3(T) { alias Vec3 = Matrix!(T, 3, 1); template opResolveAlias(alias U) { static if(is(U == Matrix!(V, 3, 1), V)) alias opResolveAlias = Vec3!V; else static assert(0); } } ``` The `Vec3` function has another template alias associated with it that handles the resolution, call it `opResolveAlias`. It can take some type `U`, check if it matches the right hand side of the original alias and then tells the compiler that it is a `Vec3!V`. For simple aliases, the `opResolveAlias` could be auto-generated by the compiler if not provided by the user. Regardless, the important thing would be that `Vec3!V` is kept as Vec3!V and not rewritten to `Matrix!(V, 3, 1)`. You could even give it another notation, like `this!V` to make that effect clearer. Then, when the compiler is doing template alias resolution, it can tell that it is a template alias and do a re-write. So for instance, if you have `is(T == Vec3!S, S)` then you can re-write it `is(Vec3.opResolveAlias!T == Vec3!S, S)`. Functions might be a little more complicated, but the idea is the same: if you pass some `T t` into a function `foo(S)(Vec3!S x) {}`, then the resolution can similar check that `Vec3!S` is a template alias and apply `Vec3.opResolveAlias!T` to any incoming `T`s.
Mar 24 2023
prev sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
On Tuesday, 21 March 2023 at 07:51:50 UTC, FeepingCreature wrote:
 [snip]
 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.
There are a lot of things that D's template aliases could do if resolution were a solved problem (I get that a big problem is that it is too powerful). Below (taken from [1]) is a way to use something like concepts in D (b/c you can impose any constraint you want on the template or on static if's within the template). You can place requirements on T (in this case just that it is an int) and if it succeeds, then you have your alias. In a more complicated case, static if's could provide better error messages. ```d template Foo(T) if (is(T == int)) { alias Foo = T; } void foo(T)(Foo!T x) { } void main() { Foo!int x; foo!int(x); //compiles foo(x); //doesn't compile but ideally should foo(1.0); //doesn't compile and shouldn't } ``` Unfortunately, an alias this approach wouldn't be sufficient in this case since alias this requires creating a whole new type. That means if you want the functionality to work with the original type, then you need to have it be implicitly convertible to the original type and you're still stuck writing that type out. ```d struct Matrix(S, size_t M, size_t N) {} struct Vec3(S) { Matrix!(S, 3, 1) data; alias data this; } void foo(U : Matrix!(V, 3, 1), V)(U u) {} void bar(U)(Vec3!U v) {} void main() { import std.stdio; Matrix!(float, 3, 1) u; Vec3!float v; foo(u); foo(v); //bar(u); //fails to compile bar(v); } ``` [1] https://forum.dlang.org/post/uzvxrqywxfqmoplurfcf forum.dlang.org
Mar 22 2023
parent zjh <fqbqrr 163.com> writes:
On Wednesday, 22 March 2023 at 13:29:56 UTC, jmh530 wrote:


 There are a lot of things that D's template aliases could do if 
 resolution were a solved problem (I get that a big problem is 
 that it is too powerful).
Right,`template alias` is very important!
Mar 22 2023
prev sibling parent reply jmh530 <john.michael.hall gmail.com> writes:
On Wednesday, 15 March 2023 at 06:47:38 UTC, Elfstone wrote:
 [snip]
 void main()
 {
     import std.stdio;

     Vec3!float v;
     writeln(is(typeof(v) == Vec3!S, S)); // false
 ```
The problem is that `typeof(v)` gets re-written very quickly by the compiler to `Matrix!(float, 3, 1)`, but the compiler can't tell that a `Matrix!(float, 3, 1)` is actually a `Vec3!S` for some generic `S` (it can't figure out what `S` should be). For a specific `S` it can. For instance `writeln(is(typeof(v) == Vec3!float));` prints true. So something like `void foo(U)(U v) if (is(U == Vec3!float) || is(U == Vec3!double)) {}` works, even though it is more awkward. What we need is some way to inform the compiler of the reverse, i.e. that a Matrix!(float, 3, 1) is also a Vec3!float. I had sketched out an idea in the comments on issue 1807 [1], but it was rather half-baked and I'm not sure that's the right way to go. The underlying issue is that you need some kind of way to tell the compiler about the mapping from some concrete type to the template alias. [1] https://issues.dlang.org/show_bug.cgi?id=1807
Mar 15 2023
parent reply Elfstone <elfstone yeah.net> writes:
On Wednesday, 15 March 2023 at 13:07:53 UTC, jmh530 wrote:
 On Wednesday, 15 March 2023 at 06:47:38 UTC, Elfstone wrote:
 [snip]
 void main()
 {
     import std.stdio;

     Vec3!float v;
     writeln(is(typeof(v) == Vec3!S, S)); // false
 ```
The problem is that `typeof(v)` gets re-written very quickly by the compiler to `Matrix!(float, 3, 1)`, but the compiler can't tell that a `Matrix!(float, 3, 1)` is actually a `Vec3!S` for some generic `S` (it can't figure out what `S` should be). For a specific `S` it can. For instance `writeln(is(typeof(v) == Vec3!float));` prints true. So something like `void foo(U)(U v) if (is(U == Vec3!float) || is(U == Vec3!double)) {}` works, even though it is more awkward. What we need is some way to inform the compiler of the reverse, i.e. that a Matrix!(float, 3, 1) is also a Vec3!float. I had sketched out an idea in the comments on issue 1807 [1], but it was rather half-baked and I'm not sure that's the right way to go. The underlying issue is that you need some kind of way to tell the compiler about the mapping from some concrete type to the template alias. [1] https://issues.dlang.org/show_bug.cgi?id=1807
Can't the compiler also rewrite or expand `Vec3!S` to `Matrix!(S, 3, 1)` then comfortably evaluate ```D is(Matrix!(float, 3, 1) == Matrix!(S, 3, 1), S) // true ``` ? It looks easier than the reverse approach, but again I don't know anything about D compiler. It'd be better if the compiler would just report an error: "don't you ever put template alias in `is(...)`". The current behaviour is confusing and of zero use to anyone.
Mar 15 2023
parent reply jmh530 <john.michael.hall gmail.com> writes:
On Thursday, 16 March 2023 at 01:57:34 UTC, Elfstone wrote:
 [snip]

 Can't the compiler also rewrite or expand `Vec3!S` to 
 `Matrix!(S, 3, 1)` then comfortably evaluate
 ```D
 is(Matrix!(float, 3, 1) == Matrix!(S, 3, 1), S) // true
 ```
 ?

 It looks easier than the reverse approach, but again I don't 
 know anything about D compiler.

 It'd be better if the compiler would just report an error: 
 "don't you ever put template alias in `is(...)`". The current 
 behaviour is confusing and of zero use to anyone.
DIP1023 was all about resolving template aliases. The compiler currently treats `Vec3!S` and `Matrix!(S, 3, 1)` as different things for the purpose of calling functions. DIP1023 tries to re-write `Vec!S` into `Matrix!(S, 3, 1)`. The problem is that ```d alias Vec3(S) = Matrix!(S, 3, 1); ``` is actually shorthand for ```d template Vec3(S) { alias Vec3 = Matrix!(S, 3, 1); } ``` and it turns out that this is very powerful. Not that you'd want to, but you could just as easily write ```d template Vec3_alt(S) { static if (!is(S == int)) alias Vec3_alt = Matrix!(S, 3, 1); else alias Vec3_alt = real; } ``` In this case, how do you resolve `Vec3!S`? It's not as simple as just re-writing it to `Matrix!(S, 3, 1)`. The solution in DIP1023 only works for a limited--albeit common--subset of the functionality that D supports for template aliases, so it is essentially special casing for template alias deduction. That and running up against the question of "why not just use template constraints", it struggled to gain support.
Mar 16 2023
parent Elfstone <elfstone yeah.net> writes:
On Thursday, 16 March 2023 at 12:51:39 UTC, jmh530 wrote:
 On Thursday, 16 March 2023 at 01:57:34 UTC, Elfstone wrote:
 [snip]

 Can't the compiler also rewrite or expand `Vec3!S` to 
 `Matrix!(S, 3, 1)` then comfortably evaluate
 ```D
 is(Matrix!(float, 3, 1) == Matrix!(S, 3, 1), S) // true
 ```
 ?

 It looks easier than the reverse approach, but again I don't 
 know anything about D compiler.

 It'd be better if the compiler would just report an error: 
 "don't you ever put template alias in `is(...)`". The current 
 behaviour is confusing and of zero use to anyone.
DIP1023 was all about resolving template aliases. The compiler currently treats `Vec3!S` and `Matrix!(S, 3, 1)` as different things for the purpose of calling functions. DIP1023 tries to re-write `Vec!S` into `Matrix!(S, 3, 1)`. The problem is that ```d alias Vec3(S) = Matrix!(S, 3, 1); ``` is actually shorthand for ```d template Vec3(S) { alias Vec3 = Matrix!(S, 3, 1); } ``` and it turns out that this is very powerful. Not that you'd want to, but you could just as easily write ```d template Vec3_alt(S) { static if (!is(S == int)) alias Vec3_alt = Matrix!(S, 3, 1); else alias Vec3_alt = real; } ``` In this case, how do you resolve `Vec3!S`? It's not as simple as just re-writing it to `Matrix!(S, 3, 1)`. The solution in DIP1023 only works for a limited--albeit common--subset of the functionality that D supports for template aliases, so it is essentially special casing for template alias deduction. That and running up against the question of "why not just use template constraints", it struggled to gain support.
Hm, maybe D should at least be able to resolve limited simple cases such as mine, and for cases it can't, report an error or warning. Or just give up and report an error whenever it sees a template alias in `is` expression, because currently the result is just nonsense. Just look at SHOO's code. What would a new user think when s/he finally finds out `isInstanceOf` doesn't work as expected, because `Regex` is an alias, and `isInstanceOf` internally uses `is` to resolve something it just can't? I know what I think: there should be an error message. For every alias I create, I have to create a matching constraint for it, that's just sad. ```D alias Vec2(S) = Matrix!(S, 2, 1); enum isVec2(V) = is(V == Matrix!(S, 2, 1), S); alias Vec3(S) = Matrix!(S, 3, 1); enum isVec3(V) = is(V == Matrix!(S, 3, 1), S); alias Vec4(S) = Matrix!(S, 4, 1); enum isVec4(V) = is(V == Matrix!(S, 4, 1), S); ```
Mar 16 2023