Thread overview
May I introduce `lazy with`?
Nov 24, 2022
Quirin Schroll
Nov 24, 2022
Paul Backus
Nov 24, 2022
Timon Gehr
Nov 25, 2022
Tejas
Nov 25, 2022
Quirin Schroll
November 24, 2022

Enhancement issue 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:

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:

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?

November 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:

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:

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;
        }
    }
}
November 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 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.


November 25, 2022
> >

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 😕

November 25, 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:

>

The essence of the problem is that with (TypeOrExpr) will prefer resolving an identifier id as TypeOrExpr.id whenever TypeOrExpr.id is viable:

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:

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:

switch(e) {
   case Role.member: ...
   case Role.member: ...
   case Role.member: ...
   default: ...
}

Steven’s claims are based on what the spec Statement § 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.

November 25, 2022

On 11/25/22 3:58 AM, Quirin Schroll wrote:

>

Steven’s claims are based on what the spec Statement § 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