www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - `alias x = v.x;` not working in struct bodies?

reply Danilo <codedan aol.com> writes:
I thought this should easily work, but I was wrong:

```d
module app;
import std;

void main() {
     auto x = new Vec3(10, 20, 30);
}

struct Vec2 {
     int x, y;
}

struct Vec3 {
     Vec2 v;
     alias x = v.x;
     alias y = v.y;

     int   z;

      disable this();

     this( typeof(x) _x, typeof(y) _y, typeof(z) _z ) { // works
         z = _z; // works

         x   = _x; // error: accessing non-static variable `x` 
requires an instance of `Vec2`
         v.x = _x; // works

         y   = _y; // error: accessing non-static variable `y` 
requires an instance of `Vec2`
         v.y = _y; // works

         writeln( x, ", ", y, ", ", z );     // error: accessing 
non-static variable `a` requires an instance of `Base`
         writeln( v.x, ", ", v.y, ", ", z ); // works
     }

     void func( typeof(x) _x, typeof(y) _y, typeof(z) _z ) { // 
works
         z = _z; // works

         // doesn't help
         //alias x = this.v.x;
         //alias y = this.v.y;

         x   = _x; // error: accessing non-static variable `x` 
requires an instance of `Vec2`
         v.x = _x; // works

         y   = _y; // error: accessing non-static variable `y` 
requires an instance of `Vec2`
         v.y = _y; // works

         writeln( x, ", ", y, ", ", z );     // error: accessing 
non-static variable `a` requires an instance of `Base`
         writeln( v.x, ", ", v.y, ", ", z ); // works
     }

}
```

Can't `alias` targets be used in method bodies?

In the method/constructor parameters it's working as expected. 
But not inside the bodies?

Documentation:
- https://dlang.org/spec/declaration.html#alias
- https://dlang.org/spec/declaration.html#alias-variable

According to the link `alias-variable` I would expect my example 
to work.
Aliasing a variable (struct member), it's a symbol.
Jan 20
next sibling parent reply Danilo <codedan aol.com> writes:
On Saturday, 20 January 2024 at 09:00:22 UTC, Danilo wrote:
 ```d
 struct Vec2 {
     int x, y;
 }

 struct Vec3 {
     Vec2 v;
     alias x = v.x;
     alias y = v.y;
     ...
 }
 ```

 Can't `alias` targets be used in method bodies?

 In the method/constructor parameters it's working as expected. 
 But not inside the bodies?

 Documentation:
 - https://dlang.org/spec/declaration.html#alias
 - https://dlang.org/spec/declaration.html#alias-variable

 According to the link `alias-variable` I would expect my 
 example to work.
 Aliasing a variable (struct member), it's a symbol.
We figured it out on Discord. `alias` works only with static variables and members: ```d struct Vec2 { static int x, y; } struct Vec3 { static Vec2 v; ... ``` The documentation i mentioned above didn't explicitely say it's for use with `static` variables/members only.
Jan 20
next sibling parent reply Danilo <codedan aol.com> writes:
All of this advanced stuff in D seems to be implemented for very 
specific and very limited use cases only.
It could be much more better if it would be implemented for 
general and broader use cases.
Jan 20
parent evilrat <evilrat666 gmail.com> writes:
On Saturday, 20 January 2024 at 10:00:15 UTC, Danilo wrote:
 All of this advanced stuff in D seems to be implemented for 
 very specific and very limited use cases only.
 It could be much more better if it would be implemented for 
 general and broader use cases.
In your vec3 example, while it is tempting to automate this using cool smart features, you will end up regretting using it. And in this example you can use union to join vec2 and add extra synonyms members. I see there is a lot of libraries that using similar smart metaprogramming for basic types in D, but when I tried to do "smart" things using alias this in the end I often got bitten by it and regretting using templates and stuff. Alias this has annoying property to mess up with your RAII wrappers. The fancy metaprogramming engineering results in poor IDE experience and slower compilation(neglectible in this case) times along with even more horrible debugging. ```d struct Vec3 { union { struct { float x = 0; float y = 0; float z = 0; } struct { Vec2 _xy; float _z; } float[3] values; } // ... } ```
Jan 20
prev sibling parent reply Nick Treleaven <nick geany.org> writes:
On Saturday, 20 January 2024 at 09:49:06 UTC, Danilo wrote:
 On Saturday, 20 January 2024 at 09:00:22 UTC, Danilo wrote:
 Documentation:
 - https://dlang.org/spec/declaration.html#alias
 - https://dlang.org/spec/declaration.html#alias-variable

 According to the link `alias-variable` I would expect my 
 example to work.
 Aliasing a variable (struct member), it's a symbol.
We figured it out on Discord. `alias` works only with static variables and members: ```d struct Vec2 { static int x, y; } struct Vec3 { static Vec2 v; ... ``` The documentation i mentioned above didn't explicitely say it's for use with `static` variables/members only.
You can't alias fields of an aggregate type instance outside of the type definition. You can alias members of an aggregate type, such as static member variables. You can alias non-static members inside the aggregate definition. ```d struct S { int j; alias k = j; } ``` I'll look at updating the spec as you're right, it's not clear. BTW I have a PR which deprecates aliasing a member of a type instance: https://github.com/dlang/dmd/pull/15863 I added a test case similar to your example.
Jan 20
next sibling parent Nick Treleaven <nick geany.org> writes:
On Saturday, 20 January 2024 at 13:39:37 UTC, Nick Treleaven 
wrote:
 I'll look at updating the spec as you're right, it's not clear.
https://github.com/dlang/dlang.org/pull/3758
Jan 20
prev sibling parent reply Max Samukha <maxsamukha gmail.com> writes:
On Saturday, 20 January 2024 at 13:39:37 UTC, Nick Treleaven 
wrote:

 You can't alias fields of an aggregate type instance outside of 
 the type definition.
It's not an alias of some arbitrary symbol. The field is accessible through 'this' of the deriving struct, and it is reasonable to expect it should be accessible though an alias in the context of that 'this'.
Jan 20
parent reply Max Samukha <maxsamukha gmail.com> writes:
On Saturday, 20 January 2024 at 14:56:52 UTC, Max Samukha wrote:

 It's not an alias of some arbitrary symbol. The field is 
 accessible through 'this' of the deriving struct, and it is 
 reasonable to expect it should be accessible though an alias in 
 the context of that 'this'.
Disregard that. I assumed there was 'alias this' in the OP's example. Now the question is, should this work: ``` struct S { int x; } struct S2 { S s; alias this = s; alias x = S.x; } auto x = S2().x; // Error: need 'this' ``` ?
Jan 20
parent reply Paul Backus <snarwin gmail.com> writes:
On Saturday, 20 January 2024 at 15:34:59 UTC, Max Samukha wrote:
 Disregard that. I assumed there was 'alias this' in the OP's 
 example. Now the question is, should this work:

 ```
 struct S
 {
     int x;
 }

 struct S2
 {
     S s;
     alias this = s;
     alias x = S.x;
 }

 auto x = S2().x; // Error: need 'this'
 ```
 ?
`alias this` is used as a fallback for member lookup when no symbol with the requested name exists. Since you declared a symbol named `x` in `S2`, there is no need to fall back to `alias this` when attempting to look up `S2().x`.
Jan 20
parent reply Max Samukha <maxsamukha gmail.com> writes:
On Saturday, 20 January 2024 at 15:55:13 UTC, Paul Backus wrote:

 `alias this` is used as a fallback for member lookup when no 
 symbol with the requested name exists. Since you declared a 
 symbol named `x` in `S2`, there is no need to fall back to 
 `alias this` when attempting to look up `S2().x`.
Arguable but I don't have the energy to argue. Let it be.
Jan 20
parent Max Samukha <maxsamukha gmail.com> writes:
On Saturday, 20 January 2024 at 20:25:21 UTC, Max Samukha wrote:
 On Saturday, 20 January 2024 at 15:55:13 UTC, Paul Backus wrote:

 `alias this` is used as a fallback for member lookup when no 
 symbol with the requested name exists. Since you declared a 
 symbol named `x` in `S2`, there is no need to fall back to 
 `alias this` when attempting to look up `S2().x`.
Arguable but I don't have the energy to argue. Let it be.
I've filed a bug report for this, just in case https://issues.dlang.org/show_bug.cgi?id=24350
Jan 21
prev sibling next sibling parent reply Basile B. <b2.temp gmx.com> writes:
On Saturday, 20 January 2024 at 09:00:22 UTC, Danilo wrote:
 I thought this should easily work, but I was wrong:

 [...]
Those actually would not be aliases but more _"parameter-less expression macros"_. In order to make those aliases working, it would be necessary to monomorphize and recontextualize the source expression for each use: ```d struct Vec3 { Vec2 v; alias x = v.x; // cannot contextualize, no "this" } Vec3 v1, v2; v1.x = 0; // -> create v1.v.x, contextualize v2.x = 0; // cannot reuse, "this" has changed // you have to copy and recontextualize ``` So you think that it's nice but it's actually more like a mixin. An old PR would have allowed that but quickly a reviewer [noticed](https://github.com/dlang/dmd/pull/11273#issuecomment-660675187) that these are not classic aliases, but something more complex, bringing possible issues. On top of that, as there's no "scope", contextualization can lead to ambiguous situations, reproducing the problem of what is called "unhygienic macros"... although for dot chains, that should not happen too much 🤞😐🤞.
Jan 20
parent Danilo <codedan aol.com> writes:
On Saturday, 20 January 2024 at 20:48:46 UTC, Basile B. wrote:
 Those actually would not be aliases but more _"parameter-less 
 expression macros"_.
Yeah, it is just intuitive to assume that `alias` can be used to give an additional `alias name` to something and then access the entity using the `alias name`. It's just intuitive, nothing special i tried here. ;) - https://www.merriam-webster.com/dictionary/alias
Jan 20
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On Saturday, 20 January 2024 at 09:00:22 UTC, Danilo wrote:

 Can't `alias` targets be used in method bodies?

 In the method/constructor parameters it's working as expected. 
 But not inside the bodies?
alias is always about a *symbol*. It is not about an *expression*. Yes, in some cases (such as template parameters) you can alias an expression. This is not the same thing. So when you do: ```d alias x = v.x; ``` What you are doing is saying `x` is an alias to `Vec2.x`, the symbol. The `v` plays no part in it except to be a namespace -- it does not help clarify which x you are talking about. Why does it work for normal members? To be clear, I'm talking about: ```d struct S { int x; alias x2 = x; } ``` It's actually the same thing thing as your `v.x` example! `x2` is an alias to the *symbol* `S.x`, not to the instance variable `this.x`. It works because when you access it, you are accessing it *through* the instance variable, and the compiler says "aha! the symbol `x` on the instance, I know how to access that". But when you access the symbol `Vec2.x` on a `Vec3`, it doesn't know how to resolve that without a (proper) instance (remember, the `v` is just used for namespacing, not directing how to access the alias). So how do you do it? ```d struct Vec3 { Vec2 v; ref x() => v.x; ref y() => v.y; } ``` It's not perfect, as taking the address of `x` will yield a delegate, and not a pointer to the member, but it's the best you can do. Now, you might wonder, why does alias work when passed to a template function? Because the compiler adds a *hidden context* parameter, which then tells it how to access the variable. This is a feature which does not apply everywhere alias is used -- you are not always calling a function where a context parameter can be passed. -Steve
Jan 20
parent reply Danilo <codedan aol.com> writes:
On Saturday, 20 January 2024 at 22:16:35 UTC, Steven 
Schveighoffer wrote:
 So how do you do it?

 ```d
 struct Vec3
 {
    Vec2 v;
    ref x() => v.x;
    ref y() => v.y;
 }
 ```

 It's not perfect, as taking the address of `x` will yield a 
 delegate, and not a pointer to the member, but it's the best 
 you can do.
Thanks Steven! Of course you all are able to concentrate on the details of how it is implemented, because you work on that level every day. If you step back to see the bigger picture, it is very intuitive to write: ```d alias x = v.x; alias y = this.v.y; ``` It means giving an `alias name` to an entity, and I think it's not hard to understand that. It's just logical in a broader sense, when you look at the bigger picture. ;)
Jan 20
next sibling parent Danilo <codedan aol.com> writes:
On Saturday, 20 January 2024 at 22:25:35 UTC, Danilo wrote:
 If you step back to see the bigger picture, it is very 
 intuitive to write:
 ```d
 alias x = v.x;
 alias y = this.v.y;
 ```
 It means giving an `alias name` to an entity, and I think it's 
 not hard to understand that.
 It's just logical in a broader sense, when you look at the 
 bigger picture. ;)
`alias` is already reserved for special purposes in D, so we probably need a new keyword for this: `pseudonym` ```d pseudonym x = v.x; pseudonym y = this.v.y; ``` Don't take that too seriously, just came to my mind... lol ;)
Jan 20
prev sibling parent reply Steven Schveighoffer <schveiguy gmail.com> writes:
On Saturday, 20 January 2024 at 22:25:35 UTC, Danilo wrote:
 Thanks Steven! Of course you all are able to concentrate on the
 details of how it is implemented, because you work on that 
 level every day.

 If you step back to see the bigger picture, it is very 
 intuitive to write:
 ```d
 alias x = v.x;
 alias y = this.v.y;
 ```
 It means giving an `alias name` to an entity, and I think it's 
 not hard to understand that.
 It's just logical in a broader sense, when you look at the 
 bigger picture. ;)
I agree that it's intuitive to do. It just doesn't do the thing you are expecting. It's a code smell in D. I even filed an issue on it. https://issues.dlang.org/show_bug.cgi?id=16123 -Steve
Jan 20
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Saturday, 20 January 2024 at 22:41:30 UTC, Steven 
Schveighoffer wrote:
 On Saturday, 20 January 2024 at 22:25:35 UTC, Danilo wrote:
 If you step back to see the bigger picture, it is very 
 intuitive to write:
 ```d
 alias x = v.x;
 alias y = this.v.y;
 ```
 It means giving an `alias name` to an entity, and I think it's 
 not hard to understand that.
 It's just logical in a broader sense, when you look at the 
 bigger picture. ;)
I agree that it's intuitive to do. It just doesn't do the thing you are expecting. It's a code smell in D. I even filed an issue on it. https://issues.dlang.org/show_bug.cgi?id=16123 -Steve
Works in Neat, btw! :) You can even alias expressions. For example, https://github.com/Neat-Lang/neat/blob/master/src/neat/base.nt#L747
Jan 20
parent reply Basile B. <b2.temp gmx.com> writes:
On Sunday, 21 January 2024 at 04:50:03 UTC, FeepingCreature wrote:
 Works in Neat, btw! :) You can even alias expressions.

 For example, 
 https://github.com/Neat-Lang/neat/blob/master/src/neat/base.nt#L747
Expressions aliases have a lot of problems when postfixes are involved. Assuming Neat grammar is similar to D, things like alias memberCall = member.call(); would give, as used in the source code context.memberCall; which is then lowered to context.(member.call()); while it should be (context.member).call(); So it's no only the unintentional capture problem here, it also now about precedence. Unless you'be already aware of the problem (or maybe you'd use your macro system ?) I have more examples [here](https://gitlab.com/styx-lang/styx/-/blob/master/src/styx/semantic/expressions.sx?ref_type=heads#L521).
Jan 20
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Sunday, 21 January 2024 at 05:54:15 UTC, Basile B. wrote:
 Assuming Neat grammar is similar to D, things like

     alias memberCall = member.call();

 would give, as used in the source code

     context.memberCall;

 which is then lowered to

     context.(member.call());

 while it should be

     (context.member).call();

 So it's no only the unintentional capture problem here, it also 
 now about precedence. Unless you'be already aware of the 
 problem (or maybe you'd use your macro system ?) I have more 
 examples 
 [here](https://gitlab.com/styx-lang/styx/-/blob/master/src/styx/semantic/expressions.sx?ref_type=heads#L521).
I have a hard time understanding what problem you're describing, but it's probably fine? Expression aliases are evaluated in the context of the declaration site, not the call site. This doesn't involve the grammar at all.
Jan 21
parent reply Basile B. <b2.temp gmx.com> writes:
On Sunday, 21 January 2024 at 13:18:07 UTC, FeepingCreature wrote:
 I have a hard time understanding what problem you're 
 describing, but it's probably fine? Expression aliases are 
 evaluated in the context of the declaration site, not the call 
 site. This doesn't involve the grammar at all.
I'm talking about that: ```d struct B { int i; } struct A { B b; alias cmp = (b.i == 0); } void main() { A a; if (a.cmp) {} // a.(b.i == 0) // but user wants (a.b.i) == 0 } ``` You cant just rewrite them as DotExp otherwise operands are not evaluated properly, or like in the example, invalid expressions are generated.
Jan 21
parent reply FeepingCreature <feepingcreature gmail.com> writes:
On Sunday, 21 January 2024 at 15:03:15 UTC, Basile B. wrote:
 On Sunday, 21 January 2024 at 13:18:07 UTC, FeepingCreature 
 wrote:
 I have a hard time understanding what problem you're 
 describing, but it's probably fine? Expression aliases are 
 evaluated in the context of the declaration site, not the call 
 site. This doesn't involve the grammar at all.
I'm talking about that: ```d struct B { int i; } struct A { B b; alias cmp = (b.i == 0); } void main() { A a; if (a.cmp) {} // a.(b.i == 0) // but user wants (a.b.i) == 0 } ``` You cant just rewrite them as DotExp otherwise operands are not evaluated properly, or like in the example, invalid expressions are generated.
`if (a.(b.i == 0))` is actually syntactically correct in Neat and will result in the same outcome. :-) But I think you're still looking at it at too high a level. `a.cmp` is not *rewritten* as `a.(b.i == 0)`, rather syntactically it stays as `a.cmp`, and the *lookup* for "cmp" results in the *evaluation* of `(b.i == 0)`, or `(this.b.i == 0)`, in the context of `thisptr = &a`. It's not a term rewriting system.
Jan 21
parent Basile B. <b2.temp gmx.com> writes:
On Sunday, 21 January 2024 at 19:45:03 UTC, FeepingCreature wrote:
 On Sunday, 21 January 2024 at 15:03:15 UTC, Basile B. wrote:
 On Sunday, 21 January 2024 at 13:18:07 UTC, FeepingCreature 
 wrote:
 I have a hard time understanding what problem you're 
 describing, but it's probably fine? Expression aliases are 
 evaluated in the context of the declaration site, not the 
 call site. This doesn't involve the grammar at all.
I'm talking about that: ```d struct B { int i; } struct A { B b; alias cmp = (b.i == 0); } void main() { A a; if (a.cmp) {} // a.(b.i == 0) // but user wants (a.b.i) == 0 } ``` You cant just rewrite them as DotExp otherwise operands are not evaluated properly, or like in the example, invalid expressions are generated.
`if (a.(b.i == 0))` is actually syntactically correct in Neat and will result in the same outcome. :-) But I think you're still looking at it at too high a level. `a.cmp` is not *rewritten* as `a.(b.i == 0)`, rather syntactically it stays as `a.cmp`, and the *lookup* for "cmp" results in the *evaluation* of `(b.i == 0)`, or `(this.b.i == 0)`, in the context of `thisptr = &a`. It's not a term rewriting system.
I see, you handle the context more like it would be a with-stmt. I tend to forgot that not all compilers are not designed like DMD, to conclude on this off topic ;)
Jan 22