digitalmars.D - May I introduce `lazy with`?
- Quirin Schroll (46/46) Nov 24 2022 [Enhancement issue
- Paul Backus (23/40) Nov 24 2022 I tried it on run.dlang.io and `member` is correctly resolved as
- Timon Gehr (3/16) Nov 24 2022 I guess the OP missed Walter's subsequent post:
- Tejas (4/7) Nov 24 2022 How does this not complicate the lookup logic? Most of the time
- Quirin Schroll (17/62) Nov 25 2022 Sorry, I gust believed that Steven’s post was based on testing
- Steven Schveighoffer (8/21) Nov 25 2022 I can't test what doesn't exist. The post I responded to relates to a
[Enhancement issue 23503](https://issues.dlang.org/show_bug.cgi?id=23503) In the discussion thread for DIP 1044, because Walter suggested adding an implicit `with (typeof(expr))` to `switch (expr)`, it occurred to me that `with` has an issue that makes it unsuitable for certain use-cases, including generic code, and that issue can be solved. The essence of the problem is that `with (TypeOrExpr)` will prefer resolving an identifier `id` as `TypeOrExpr.id` whenever `TypeOrExpr.id` is viable: ```d with (EnumType) switch (enumValue) { static foreach (member; EnumMembers!EnumType) { case member: // What if EnumType has a member named `member`? ...; } } ``` When `EnumType` happens to have a member named `member`, `with` will greedily take it and the loop will produce nonsense. What would be useful here is a version of `with` that only resolves `id` as `TypeOrExpr.id` whenever `id` is not otherwise resolvable, i.e. as a last resort. I’d suggest using `lazy with` for that construct. It is otherwise exactly like `with`. Its advantage is that it can be used in generic code and thus be implicitly added. Were the `with` statement above a `lazy with`, the identifier `member` would always resolve to the `foreach` iteration. Following Walter’s suggestion, a `lazy with` can be added implicitly to `switch` statements and declarations with spelled-out type: ```d EnumType e = enumMember; // as if `EnumType e = EnumType.enumMember`, unless `enumMember` is in scope int x = max - 1; // as if `int x = int.max - 1`, unless another `max` is in scope. double myEps = 2 * epsilon; // as if `= 2 * double.epsilon`, unless... ``` Note that `with (int)` currently does not work. What do you think?
Nov 24 2022
On Thursday, 24 November 2022 at 17:41:20 UTC, Quirin Schroll wrote:The essence of the problem is that `with (TypeOrExpr)` will prefer resolving an identifier `id` as `TypeOrExpr.id` whenever `TypeOrExpr.id` is viable: ```d with (EnumType) switch (enumValue) { static foreach (member; EnumMembers!EnumType) { case member: // What if EnumType has a member named `member`? ...; } } ``` When `EnumType` happens to have a member named `member`, `with` will greedily take it and the loop will produce nonsense.I tried it on run.dlang.io and `member` is correctly resolved as referring to the loop variable, not the enum member. Here's a complete, compilable example: ```d enum EnumType { foo, member, bar } void main() { import std.traits; EnumType enumValue; with (EnumType) switch_label: final switch (enumValue) { static foreach (member; EnumMembers!EnumType) { case member: pragma(msg, member); break switch_label; } } } ```
Nov 24 2022
On 24.11.22 20:27, Paul Backus wrote:On Thursday, 24 November 2022 at 17:41:20 UTC, Quirin Schroll wrote:I guess the OP missed Walter's subsequent post: On 19.11.22 19:46, Walter Bright wrote:...I tried it on run.dlang.io and `member` is correctly resolved as referring to the loop variable, not the enum member. Here's a complete, compilable example:On 11/19/2022 10:26 AM, H. S. Teoh wrote:Upon more reflection, the implicit `with` should only enclose the case expression, not the other statements in the switch body.Implicitly enclose the switch body with a `with (WordLetterOfTheDay)` resulting in:
Nov 24 2022
How does this not complicate the lookup logic? Most of the time there is only 1 statement below each case, so having different lookup rules for every second line inside a `switch` expression feels... weird 😕Upon more reflection, the implicit `with` should only enclose the case expression, not the other statements in the switch body.
Nov 24 2022
On Thursday, 24 November 2022 at 19:27:00 UTC, Paul Backus wrote:On Thursday, 24 November 2022 at 17:41:20 UTC, Quirin Schroll wrote:Sorry, I gust believed that Steven’s post was based on testing himself, especially note the sentence I highlighted. On Wednesday, 23 November 2022 at 03:24:30 UTC, Steven Schveighoffer wrote:The essence of the problem is that `with (TypeOrExpr)` will prefer resolving an identifier `id` as `TypeOrExpr.id` whenever `TypeOrExpr.id` is viable: ```d with (EnumType) switch (enumValue) { static foreach (member; EnumMembers!EnumType) { case member: // What if EnumType has a member named `member`? ...; } } ``` When `EnumType` happens to have a member named `member`, `with` will greedily take it and the loop will produce nonsense.I tried it on run.dlang.io and `member` is correctly resolved as referring to the loop variable, not the enum member. Here's a complete, compilable example:To spell it out, when you write `case member:` what does `member` refer to? It currently, in the implementation of Phobos refers to the `member` iteration variable of the foreach inside to!string: ```d foreach(member; EnumMembers!E) { ``` **But if an implicit `with(Role)` is added to the case clause, now it only ever refers to `Role.member`.** Essentially, you will end up with a switch that looks like: ```d switch(e) { case Role.member: ... case Role.member: ... case Role.member: ... default: ... } ```Steven’s claims are based on what the spec [Statement § With Statement](https://dlang.org/spec/statement.html#with-statement) says what should happen:Within the with body the referenced object is searched first for identifier symbols.And a little later:Use of `with` object symbols that shadow local symbols with the same identifier are not allowed.So, Steven seemingly didn’t test and read the spec section completely and the compiler has a bug: The `static foreach` is the greedy one here and `with` never gets to see `member` as a plain identifier. I guess we could reasonably give `static foreach` that high priority officially. Still, the point stands that `with` is greedy enough that it ambiguates local symbols and shadows lower-scope symbols. `lazy with` would not do that.
Nov 25 2022
On 11/25/22 3:58 AM, Quirin Schroll wrote:Steven’s claims are based on what the spec [Statement § With Statement](https://dlang.org/spec/statement.html#with-statement) says what should happen:I can't test what doesn't exist. The post I responded to relates to a proposal for `with` that automatically uses it for the case statements. See here: https://forum.dlang.org/post/tlb8a4$1m2k$1 digitalmars.com I also assume that Timon has worked through the problem correctly. In any case, my goal was to try and shed some light on what Timon was getting at, which Walter seemed not to be understanding. -SteveWithin the with body the referenced object is searched first for identifier symbols.And a little later:Use of `with` object symbols that shadow local symbols with the same identifier are not allowed.So, Steven seemingly didn’t test and read the spec section completely and the compiler has a bug: The `static foreach` is the greedy one here and `with` never gets to see `member` as a plain identifier.
Nov 25 2022