www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Puzzled

reply Don Allen <donaldcallen gmail.com> writes:
Since ceasing my use of D out of concern for the direction and 
health of the project, I've nonetheless been following these 
forums and have seen what I consider to be sincere efforts to 
improve things.

So I've started doing a bit of D work again and ran into this:

````
import std.stdio;

struct Foo {
     int bar;
     int baz;
}

void bletch(alias field)(Foo s, int x) {
	s.field = x;
	writeln("%d", s.field);
}

int main(string[] args)
{
     Foo s;
     bletch!bar(s);
     bletch!baz(s);
     return 0;
}
````

Compiling with dmd 2.106.0 on an up-to-date NixOS system results 
in

````
(dmd-2.106.0)dca giovanni:/home/dca/Software/d_tests$ dmd test.d
test.d(16): Error: undefined identifier `bar`
test.d(17): Error: undefined identifier `baz`
````

So it appears that symbols passed to a function template alias 
parameter need to be in scope at the instantiation site as 
opposed to within the instantiated code.

This makes no sense to me. The symbols at the instantiation site 
are not being evaluated in that scope. They will be evaluated by 
the compiler when it gets its hands on the instantiated code. If 
a simple compile-time substitution were done in the above code, 
those symbols would both be defined in the instantiated code.

Reading the documentation, I'm guessing that template mixins can 
be used to address this problem, so it's probably the case (I 
haven't tried it) that I can achieve what I want that way.

But I'm failing to understand why the problem exists in the first 
place. Perhaps inherited from C++, which I do not know, having 
had some early exposure to it and concluded it was awful?

I'm probably missing something here, perhaps some important cases 
where the behavior I'm seeing *does* make sense. I'd be 
interested in the comments of those who know D (and perhaps C++) 
better than I do.
Dec 12 2023
next sibling parent reply monkyyy <crazymonkyyy gmail.com> writes:
On Tuesday, 12 December 2023 at 19:02:11 UTC, Don Allen wrote:
 
 I'm probably missing something here, perhaps some important 
 cases where the behavior I'm seeing *does* make sense. I'd be 
 interested in the comments of those who know D (and perhaps 
 C++) better than I do.
Your imagining aliases as words rather than references to definitions As far as I know, aliases are optimized to work with "overload sets" ```d void foo(int); void foo(bool); template bar(alias A){} bar!foo; ``` A is now all foo's, foo's need to exist, and foo mostly pretends to stop knowing where it comes from do: ```d struct Foo { int bar; int baz; } void bletch(alias field)(int x) { field = x; writeln("%d", s.field); } int main(string[] args) { Foo s; bletch!(s.bar); bletch!(s.baz); return 0; } ```
Dec 12 2023
parent reply Don Allen <donaldcallen gmail.com> writes:
On Tuesday, 12 December 2023 at 19:50:25 UTC, monkyyy wrote:
 On Tuesday, 12 December 2023 at 19:02:11 UTC, Don Allen wrote:
 
 I'm probably missing something here, perhaps some important 
 cases where the behavior I'm seeing *does* make sense. I'd be 
 interested in the comments of those who know D (and perhaps 
 C++) better than I do.
Your imagining aliases as words rather than references to definitions As far as I know, aliases are optimized to work with "overload sets" ```d void foo(int); void foo(bool); template bar(alias A){} bar!foo; ``` A is now all foo's, foo's need to exist, and foo mostly pretends to stop knowing where it comes from do: ```d struct Foo { int bar; int baz; } void bletch(alias field)(int x) { field = x; writeln("%d", s.field); } int main(string[] args) { Foo s; bletch!(s.bar); bletch!(s.baz); return 0; } ```
I regard templates as a form of more hygienic macros than simple text substitution. To me, they are a form of meta-programming -- code writing code. My view may be wrong and if so, I'd like to know why. If not, I have not seen an answer that explains why a symbol passed to an alias parameter needs a definition in the scope of the template instantiation, as opposed to within the instantiated template body. What you have suggested above is a way to make my little example work. While less interested in that, I'm not un-interested :-) So I played with this a bit. Taking your approach, cleaned up a bit (the bletch calls need the int argument), I tried ```` import std.stdio; struct Foo { int bar; int baz; } void bletch(alias field)(int n) { field = n; writeln("%d", field); } int main(string[] args) { Foo s; bletch!(s.bar)(5); bletch!(s.baz)(6); return 0; } ```` This results in ```` [nix-shell:~/Software/d_tests]$ dmd test.d test.d(16): Error: calling non-static function `bletch` requires an instance of type `Foo` test.d(17): Error: calling non-static function `bletch` requires an instance of type `Foo` (dmd-2.106.0) ```` Since field is aliased to s.bar or s.baz, I tried this: ```` import std.stdio; struct Foo { int bar; int baz; } void bletch(alias field)(Foo s, int n) { field = n; writeln("%d", field); } int main(string[] args) { Foo s; bletch!(s.bar)(s, 5); bletch!(s.baz)(s, 6); return 0; } ```` Same complaints from the compiler: ```` [nix-shell:~/Software/d_tests]$ dmd test.d test.d(16): Error: calling non-static function `bletch` requires an instance of type `Foo` test.d(17): Error: calling non-static function `bletch` requires an instance of type `Foo` (dmd-2.106.0) ````
Dec 12 2023
next sibling parent Max Samukha <maxsamukha gmail.com> writes:
On Wednesday, 13 December 2023 at 03:37:25 UTC, Don Allen wrote:

 Same complaints from the compiler:
 ````
This works (the explicit template is due to a 'need this' bug): ``` import std.stdio; struct Foo { int bar; int baz; } template bletch(alias field) { static void bletch(T)(ref T self, int x) { auto pField = &__traits(child, self, field); *pField = x; writefln("%d", *pField); } } int main(string[] args) { Foo s; bletch!(s.bar)(s, 1); bletch!(s.baz)(s, 2); return 0; } ```
Dec 12 2023
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/12/2023 7:37 PM, Don Allen wrote:
 ````
 import std.stdio;
 
 struct Foo {
      int bar;
      int baz;
 }
 
 void bletch(alias field)(Foo s, int n) {
      field = n;
      writeln("%d", field);
 }
 
 int main(string[] args)
 {
      Foo s;
      bletch!(s.bar)(s, 5);
      bletch!(s.baz)(s, 6);
      return 0;
 }
 
 ````
 
 Same complaints from the compiler:
 ````
 [nix-shell:~/Software/d_tests]$ dmd test.d
 test.d(16): Error: calling non-static function `bletch` requires an instance
of 
 type `Foo`
 test.d(17): Error: calling non-static function `bletch` requires an instance
of 
 type `Foo`
 (dmd-2.106.0)
 ````
The trouble here is that `s` is a local variable of `main`. The `bletch!(s.bar)` is passing `s` not by reference, and not by value, but by symbol. Inside the body of `bletch`, there is no way that it can get at the runtime value of the symbol `main.s`. Hence, the complaint that `bletch` requires a way to get at the instance of `main.s`. D has 3 ways of passing arguments: 1. by value 2. by pointer (aka by reference) 3. by symbol (aka by alias) Passing by symbol is strictly a compile time phenomenon, it cannot be a runtime one. C++'s method of passing by alias is called a "template template parameter", and is limited to passing the names of template symbols. D generalized it so any symbol could be passed that way. C macros can have arguments, and they are always passed by name, which is roughly similar to by alias. They are also a compile-time phenomenon, not a runtime one.
Dec 12 2023
parent reply Don Allen <donaldcallen gmail.com> writes:
On Wednesday, 13 December 2023 at 07:05:10 UTC, Walter Bright 
wrote:
 On 12/12/2023 7:37 PM, Don Allen wrote:
 ````
 import std.stdio;
 
 struct Foo {
      int bar;
      int baz;
 }
 
 void bletch(alias field)(Foo s, int n) {
      field = n;
      writeln("%d", field);
 }
 
 int main(string[] args)
 {
      Foo s;
      bletch!(s.bar)(s, 5);
      bletch!(s.baz)(s, 6);
      return 0;
 }
 
 ````
 
 Same complaints from the compiler:
 ````
 [nix-shell:~/Software/d_tests]$ dmd test.d
 test.d(16): Error: calling non-static function `bletch` 
 requires an instance of type `Foo`
 test.d(17): Error: calling non-static function `bletch` 
 requires an instance of type `Foo`
 (dmd-2.106.0)
 ````
The trouble here is that `s` is a local variable of `main`. The `bletch!(s.bar)` is passing `s` not by reference, and not by value, but by symbol. Inside the body of `bletch`, there is no way that it can get at the runtime value of the symbol `main.s`. Hence, the complaint that `bletch` requires a way to get at the instance of `main.s`.
Note that in the message to which you are replying, I sent two attempts to resolve this. In the first, having seen the compilation errors I got, I came to the same conclusion you describe above. In the second, I added a 'Foo s' parameter and passed main.s to it in the two calls to bletch, which I thought would fix this problem, since now there is an s of type Foo defined in the scope of the field references. To my surprise, it didn't.
 D has 3 ways of passing arguments:

 1. by value
 2. by pointer (aka by reference)
 3. by symbol (aka by alias)
I assume this is much like passing a quoted symbol in Lisp/Scheme, where the receiving function gets the symbol, not its value, e.g., (foo 'bar)? Your comment below about C macros and passing symbols by alias suggests that is true.
 Passing by symbol is strictly a compile time phenomenon, it 
 cannot be a runtime one.
Understood.
 C++'s method of passing by alias is called a "template template 
 parameter", and is limited to passing the names of template 
 symbols. D generalized it so any symbol could be passed that 
 way.

 C macros can have arguments, and they are always passed by 
 name, which is roughly similar to by alias. They are also a 
 compile-time phenomenon, not a runtime one.
Dec 13 2023
next sibling parent reply Don Allen <donaldcallen gmail.com> writes:
On Wednesday, 13 December 2023 at 15:46:12 UTC, Don Allen wrote:
 On Wednesday, 13 December 2023 at 07:05:10 UTC, Walter Bright 
 wrote:
 On 12/12/2023 7:37 PM, Don Allen wrote:
 ````
 import std.stdio;
 
 struct Foo {
      int bar;
      int baz;
 }
 
 void bletch(alias field)(Foo s, int n) {
      field = n;
      writeln("%d", field);
 }
 
 int main(string[] args)
 {
      Foo s;
      bletch!(s.bar)(s, 5);
      bletch!(s.baz)(s, 6);
      return 0;
 }
 
 ````
 
 Same complaints from the compiler:
 ````
 [nix-shell:~/Software/d_tests]$ dmd test.d
 test.d(16): Error: calling non-static function `bletch` 
 requires an instance of type `Foo`
 test.d(17): Error: calling non-static function `bletch` 
 requires an instance of type `Foo`
 (dmd-2.106.0)
 ````
The trouble here is that `s` is a local variable of `main`. The `bletch!(s.bar)` is passing `s` not by reference, and not by value, but by symbol. Inside the body of `bletch`, there is no way that it can get at the runtime value of the symbol `main.s`. Hence, the complaint that `bletch` requires a way to get at the instance of `main.s`.
Note that in the message to which you are replying, I sent two attempts to resolve this. In the first, having seen the compilation errors I got, I came to the same conclusion you describe above. In the second, I added a 'Foo s' parameter and passed main.s to it in the two calls to bletch, which I thought would fix this problem, since now there is an s of type Foo defined in the scope of the field references. To my surprise, it didn't.
I'm going to add a guess here: the field references passed by alias to bletch are to main.s.bar and main.s.baz, so passing 's' to bletch at runtime doesn't help, since the field references are not to bletch.s.bar, etc.
Dec 13 2023
parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/13/2023 7:55 AM, Don Allen wrote:
 I'm going to add a guess here: the field references passed by alias to bletch 
 are to main.s.bar and main.s.baz, so passing 's' to bletch at runtime doesn't 
 help, since the field references are not to bletch.s.bar, etc.
Right.
Dec 13 2023
parent reply Don Allen <donaldcallen gmail.com> writes:
On Wednesday, 13 December 2023 at 17:59:14 UTC, Walter Bright 
wrote:
 On 12/13/2023 7:55 AM, Don Allen wrote:
 I'm going to add a guess here: the field references passed by 
 alias to bletch are to main.s.bar and main.s.baz, so passing 
 's' to bletch at runtime doesn't help, since the field 
 references are not to bletch.s.bar, etc.
Right.
Walter -- I wanted to thank you for your responses. They have helped me to better understand what's happening. Perhaps I'm thinking of templates too much as a macro pre-processor; I clearly need to spend more time with template documentation.
Dec 13 2023
parent Walter Bright <newshound2 digitalmars.com> writes:
On 12/13/2023 2:31 PM, Don Allen wrote:
 Walter -- I wanted to thank you for your responses. They have helped me to 
 better understand what's happening. Perhaps I'm thinking of templates too much 
 as a macro pre-processor; I clearly need to spend more time with template 
 documentation.
You're welcome!
Dec 14 2023
prev sibling parent Walter Bright <newshound2 digitalmars.com> writes:
On 12/13/2023 7:46 AM, Don Allen wrote:
 Note that in the message to which you are replying, I sent two attempts to 
 resolve this. In the first, having seen the compilation errors I got, I came
to 
 the same conclusion you describe above. In the second, I added a 'Foo s' 
 parameter and passed main.s to it in the two calls to bletch, which I thought 
 would fix this problem, since now there is an s of type Foo defined in the
scope 
 of the field references. To my surprise, it didn't.
Yes, but the compiler does not have a way to semantically connect the two.
Dec 13 2023
prev sibling parent Petar Kirov [ZombineDev] <petar.p.kirov gmail.com> writes:
On Wednesday, 13 December 2023 at 03:37:25 UTC, Don Allen wrote:
 On Tuesday, 12 December 2023 at 19:50:25 UTC, monkyyy wrote:
 On Tuesday, 12 December 2023 at 19:02:11 UTC, Don Allen wrote:
 [..]
Try this, it works since dmd 2.087: ```d struct Foo { int bar; double baz; void bletch(alias field)(typeof(field) newValue) { field = newValue; } } void main(string[] args) { import std.stdio; Foo s; s.bletch!(Foo.bar)(3); s.bletch!(s.baz)(4); // this also works - the same as above writeln(s); auto setBar = &s.bletch!(Foo.bar); auto setBaz = &s.bletch!(Foo.baz); pragma (msg, typeof(setBar)); pragma (msg, typeof(setBaz)); setBar(18); setBaz(19.5); writeln(s); } ``` By making `bletch` a member function of `Foo` it will naturally have "an instance of type `Foo`" - the implicit `this` parameter that all member functions receive. Afterwards, you can see that we can take the address of the member function template instance `&s.bletch!(Foo.bar)` which results in a `delegate` (and not a function pointer). This allows us to call `bletch` through this delegate as it will supply `s` as the context pointer.
Dec 13 2023
prev sibling next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 12/12/23 20:02, Don Allen wrote:
 Since ceasing my use of D out of concern for the direction and health of 
 the project, I've nonetheless been following these forums and have seen 
 what I consider to be sincere efforts to improve things.
 
 So I've started doing a bit of D work again and ran into this:
 
 ````
 import std.stdio;
 
 struct Foo {
      int bar;
      int baz;
 }
 ...
This defines three symbols: Foo Foo.bar Foo.baz There is however no `bar` and no `baz` at module scope.
 void bletch(alias field)(Foo s, int x) {
      s.field = x;
      writeln("%d", s.field);
 }
 ...
This attempts to access `s.field`, but `field` is not a member of `Foo`.
 int main(string[] args)
 {
      Foo s;
      bletch!bar(s);
      bletch!baz(s);
      return 0;
 }
 ````
 ...
This find neither `bar` nor `baz`, because those are nested in `Foo`. There are symbols `Foo.bar` and `Foo.baz`, but not `bar` and `baz`. The idiomatic way to achieve what you want is this: ```d import std.stdio; struct Foo{ int bar; int baz; } void bletch(string field)(Foo s, int x){ __traits(getMember, s, field) = x; writeln(__traits(getMember, s, field)); } void main(){ Foo s; bletch!"bar"(s, 1); bletch!"baz"(s, 2); } ``` Arguably this is not very nice, but it does the job. You can pass `Foo.bar` and `Foo.baz` as aliases, but I think currently there is no good way to access a field of an object based on an alias to the field symbol. (Other than `__traits(getMember, self, __traits(identifier, field))`, but that bypasses all the typechecking you might get anyway.)
Dec 12 2023
next sibling parent monkyyy <crazymonkyyy gmail.com> writes:
On Tuesday, 12 December 2023 at 21:48:59 UTC, Timon Gehr wrote:
 
 The idiomatic way to achieve what you want is this:
     __traits(getMember, s, field) = x;
No that will break eventually because strings are not reliable references; also __traits are ugly and for other abstractions
Dec 12 2023
prev sibling parent reply Max Samukha <maxsamukha gmail.com> writes:
On Tuesday, 12 December 2023 at 21:48:59 UTC, Timon Gehr wrote:

 You can pass `Foo.bar` and `Foo.baz` as aliases, but I think 
 currently there is no good way to access a field of an object 
 based on an alias to the field symbol. (Other than 
 `__traits(getMember, self, __traits(identifier, field))`, but 
 that bypasses all the typechecking you might get anyway.)
`__traits(child, ...)` works sometimes.
Dec 12 2023
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 12/13/23 07:31, Max Samukha wrote:
 On Tuesday, 12 December 2023 at 21:48:59 UTC, Timon Gehr wrote:
 
 You can pass `Foo.bar` and `Foo.baz` as aliases, but I think currently 
 there is no good way to access a field of an object based on an alias 
 to the field symbol. (Other than `__traits(getMember, self, 
 __traits(identifier, field))`, but that bypasses all the typechecking 
 you might get anyway.)
`__traits(child, ...)` works sometimes.
Thanks!
Dec 13 2023
prev sibling next sibling parent reply Walter Bright <newshound2 digitalmars.com> writes:
On 12/12/2023 11:02 AM, Don Allen wrote:
 Since ceasing my use of D out of concern for the direction and health of the 
 project, I've nonetheless been following these forums and have seen what I 
 consider to be sincere efforts to improve things.
Welcome back!
 So I've started doing a bit of D work again and ran into this:
 
 ````
 import std.stdio;
 
 struct Foo {
      int bar;
      int baz;
 }
 
 void bletch(alias field)(Foo s, int x) {
      s.field = x;
      writeln("%d", s.field);
 }
 
 int main(string[] args)
 {
      Foo s;
      bletch!bar(s); // undefined identifier `bar`
bar is looked up in this scope, meaning it is looked up in main(), and then looked up in the module. It is not found, because bar is in the scope of Foo. Hence the error message for this line
      bletch!baz(s);
      return 0;
 }
 ````
 But I'm failing to understand why the problem exists in the first place.
The misunderstanding appears to be the assumption that `field` is resolved inside the definition of `bletch`. It is not, it is resolved at the point of instantiation of the template `bletch`.
 Perhaps 
 inherited from C++, which I do not know, having had some early exposure to it 
 and concluded it was awful?
C++ has Argument Dependent Lookup (ADL) which follows different rules that I have largely forgotten, despite having implemented them in my C++ compiler. ADL was invented by Andrew Koenig to get around lookup problems with operator overloading where `this` was on the right hand side of the operator. I never liked ADL, it is confusing and a giant ugly kludge. I know of no other language that has an equivalent.
Dec 12 2023
parent Don Allen <donaldcallen gmail.com> writes:
On Wednesday, 13 December 2023 at 06:52:59 UTC, Walter Bright 
wrote:
 On 12/12/2023 11:02 AM, Don Allen wrote:
 Since ceasing my use of D out of concern for the direction and 
 health of the project, I've nonetheless been following these 
 forums and have seen what I consider to be sincere efforts to 
 improve things.
Welcome back!
[snip]
 But I'm failing to understand why the problem exists in the 
 first place.
The misunderstanding appears to be the assumption that `field` is resolved inside the definition of `bletch`. It is not, it is resolved at the point of instantiation of the template `bletch`.
Not quite. I do understand the above, because it is evident from the behavior I am seeing. What I'm puzzled by is why template processing behaves this way, requiring that the symbol be defined at the point where it is *passed (by symbol)*, rather than where it is *used*, which is the place where having a valid definition really matters. More in my reply to your second message.
Dec 13 2023
prev sibling parent monkyyy <crazymonkyyy gmail.com> writes:
On Tuesday, 12 December 2023 at 19:02:11 UTC, Don Allen wrote:
 __traits, __traits, __traits, __traits,
you should use a mixin instead of traits that use strings to store references. The issue with either is that strings in different contexts may not keep referencing the same thing. The mixin will just be smaller. ```d void bletch(string field)(ref Foo s,int x){ mixin("s."~field~"=x;"); } ``` if you want to be *idiomatic* you should figure out how to make an alias survive the compiler warnings for your use case and given I started an argument I suggest this is better and least prone to failure ```d void bletch(alias f, string s)(int x){ mixin("f."~s)= x; } bletch!(s,"bar")(2); ```
Dec 13 2023