www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - May I introduce `lazy with`?

reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
[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
parent reply Paul Backus <snarwin gmail.com> writes:
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
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 24.11.22 20:27, Paul Backus wrote:
 On Thursday, 24 November 2022 at 17:41:20 UTC, Quirin Schroll 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:
I guess the OP missed Walter's subsequent post: On 19.11.22 19:46, Walter Bright wrote:
 On 11/19/2022 10:26 AM, H. S. Teoh wrote:
 Implicitly enclose the switch body with a `with (WordLetterOfTheDay)`
 resulting in:
Upon more reflection, the implicit `with` should only enclose the case expression, not the other statements in the switch body.
Nov 24 2022
parent Tejas <notrealemail gmail.com> writes:
 Upon more reflection, the implicit `with` should only enclose 
 the case expression, not the other statements in the switch 
 body.
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 😕
Nov 24 2022
prev sibling parent reply Quirin Schroll <qs.il.paperinik gmail.com> writes:
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:
 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:
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:
 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
parent Steven Schveighoffer <schveiguy gmail.com> writes:
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:
  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 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. -Steve
Nov 25 2022