digitalmars.D - `alias x = v.x;` not working in struct bodies?
- Danilo (55/55) Jan 20 I thought this should easily work, but I was wrong:
- Danilo (13/33) Jan 20 We figured it out on Discord. `alias` works only with static
- Danilo (4/4) Jan 20 All of this advanced stuff in D seems to be implemented for very
- evilrat (31/35) Jan 20 In your vec3 example, while it is tempting to automate this using
- Nick Treleaven (17/37) Jan 20 You can't alias fields of an aggregate type instance outside of
- Nick Treleaven (3/4) Jan 20 https://github.com/dlang/dlang.org/pull/3758
- Max Samukha (6/8) Jan 20 It's not an alias of some arbitrary symbol. The field is
- Max Samukha (17/21) Jan 20 Disregard that. I assumed there was 'alias this' in the OP's
- Paul Backus (5/21) Jan 20 `alias this` is used as a fallback for member lookup when no
- Max Samukha (2/6) Jan 20 Arguable but I don't have the energy to argue. Let it be.
- Max Samukha (3/10) Jan 21 I've filed a bug report for this, just in case
- Basile B. (23/25) Jan 20 Those actually would not be aliases but more _"parameter-less
- Danilo (6/8) Jan 20 Yeah, it is just intuitive to assume that `alias` can be used to
- Steven Schveighoffer (48/51) Jan 20 alias is always about a *symbol*. It is not about an
- Danilo (15/27) Jan 20 Thanks Steven! Of course you all are able to concentrate on the
- Danilo (9/19) Jan 20 `alias` is already reserved for special purposes in D, so we
- Steven Schveighoffer (6/19) Jan 20 I agree that it's intuitive to do. It just doesn't do the thing
- FeepingCreature (5/21) Jan 20 Works in Neat, btw! :) You can even alias expressions.
- Basile B. (15/18) Jan 20 Expressions aliases have a lot of problems when postfixes are
- FeepingCreature (5/18) Jan 21 I have a hard time understanding what problem you're describing,
- Basile B. (22/26) Jan 21 I'm talking about that:
- FeepingCreature (9/36) Jan 21 `if (a.(b.i == 0))` is actually syntactically correct in Neat and
- Basile B. (4/45) Jan 22 I see, you handle the context more like it would be a with-stmt.
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
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
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
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
On Saturday, 20 January 2024 at 09:49:06 UTC, Danilo wrote:On Saturday, 20 January 2024 at 09:00:22 UTC, Danilo wrote: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.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
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
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
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
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
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
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:I've filed a bug report for this, just in case https://issues.dlang.org/show_bug.cgi?id=24350`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 21
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
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
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
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
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
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
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:Works in Neat, btw! :) You can even alias expressions. For example, https://github.com/Neat-Lang/neat/blob/master/src/neat/base.nt#L747If 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
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#L747Expressions 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
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
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
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:`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 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
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: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 ;)On Sunday, 21 January 2024 at 13:18:07 UTC, FeepingCreature wrote:`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 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 22