November 23, 2022

On Friday, 18 November 2022 at 15:37:31 UTC, Mike Parker wrote:

>

https://github.com/dlang/DIPs/blob/e2ca557ab9d3e60305a37da0d5b58299e0a9de0e/DIPs/DIP1044.md

Another bad idea about this is that it reserves «$» for this purpose, which we may in the future wish we had available for some other worthier feature/paradigm. Apparently choosing «.» instead creates other conflicts.

November 23, 2022

On Wednesday, 23 November 2022 at 03:24:30 UTC, Steven Schveighoffer wrote:

>

[snip]

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: ...
}

Which will then be a compiler error.

The fix would be to rename the iteration variable to something the user couldn't possibly use. It's not pretty.

TL;DR: Use a version of with that matches lazily, i.e. when the identifier resolved to nothing else, and not greedily as the current one. [End of TL;DR]

Best thing is, lazy is already an aptly named keyword and can be combined with with to form lazy with (same as static and if or foreach or assert combine to effectively two-word keywords).

The problem is that with(expr) is greedy: If it can resolve an identifier ident as expr.ident, it will take it and not give it up even if that leads to a fail. (Like Regex greedy matching, thus the name.) The implicit with must be lazy to be useful: If ident cannot be resolved otherwise, in the context of lazy with(arg) an attempt for arg.ident is made.

In the above code, even if Role has a member named member, the function local member is considered:

import std.stdio;

enum Role
{
    guest,
    member,
    developer,
}

void main()
{
    Role r;

    lazy with (typeof(r)) // implicitly added?
    switch(r)
    {
        static foreach(member; EnumMembers!Role )
        {
        case member: // sees foreach variable, therefore lazy with does nothing.
            ...
        }
    }

    lazy with (typeof(r)) // implicitly added?
    switch(r)
    {
    case guest:  // as no other `guest` in scope, lazy with makes it Role.guest
        ...
    case member: // as no other `member` in scope, lazy with makes it Role.member
        ...
    }
}

If I’m not missing something, the only sad case is this:

immutable member = Role.developer; // Who does this anyways?
lazy with (typeof(r))
switch(r)
{
case guest:  // lazy with makes it Role.guest
    ...
case member: // `member` in scope, effectively Role.developer.
    ...
}

The same can be done for declarations without auto and maybe assignments:

Role r = member;
r = guest;

is as if:

Role r = () { lazy with(Role) return member; }();
lvalueExpresson = () { lazy with(typeof(lvalueExpresson)) return guest; }(); // maybe?

I guess declarations are unproblematic. For assignments, it might be surprising because lvalue expressions can be complex and a reminder for the type might be appropriate. On the other hand, if that’s the case, nothing stops you from being explicit. Another issue with assignment expressions is that not all left-hand side expressions have a type: Property setters and opIndexAssign don’t trivially give you the supposed right-hand side type. The type might even be a template type parameter. It’s a best effort solution at best.

November 23, 2022

On Wednesday, 23 November 2022 at 09:38:06 UTC, Quirin Schroll wrote:

>

If ident cannot be resolved otherwise, in the context of lazy with(arg) an attempt for arg.ident is made.

A reasonable and intentional, yet noteworthy consequence is that nested lazy with resolving is reversed compared to regular with:

enum A { x, y }
enum B { x, y }

void main()
{
    with (A)
    with (B)
    {
        pragma(msg, typeof(x)); // B
    }

    lazy with (A)
    lazy with (B)
    {
        pragma(msg, typeof(x)); // A
    }
}

Essentially, when resolving x, in the first case, with(B) goes ahead and resolves it (greedily). with(A) has nothing to do.
In the second case, lazy with(B) asks its surrounding context if it can resolve x and lazy with(A) then does the same. The answer for lazy with(A) is that no x is in scope, thus it attempts to resolve x as A.x and succeeds. Finally, lazy with(B) has no identifiers to resolve.

November 23, 2022

On Wednesday, 23 November 2022 at 08:45:43 UTC, XavierAP wrote:

>

On Friday, 18 November 2022 at 15:37:31 UTC, Mike Parker wrote:

>

https://github.com/dlang/DIPs/blob/e2ca557ab9d3e60305a37da0d5b58299e0a9de0e/DIPs/DIP1044.md

Another bad idea about this is that it reserves «$» for this purpose, which we may in the future wish we had available for some other worthier feature/paradigm. Apparently choosing «.» instead creates other conflicts.

This is indeed a concern. C++ ran out of good syntax, so now every single thing is some convoluted mess. We don't have to take the same road.

November 23, 2022

On Tuesday, 22 November 2022 at 21:44:00 UTC, XavierAP wrote:

>

On Friday, 18 November 2022 at 18:54:01 UTC, monkyyy wrote:

>

Ds enum syntax is verbose compared to c; and the code block probaly isnt possible to write

I know I'm not teaching anything that's not known, but languages such as D, modern C++, C# etc requiring enums to be qualified, was absolutely intentional. And IMHO the reasons (against the C syntax) stand.

And wait, there is more: we have anonymous enums:

enum { A, B }

For these who are allergic to named enums.

November 23, 2022

On Wednesday, 23 November 2022 at 11:57:48 UTC, deadalnix wrote:

>

On Tuesday, 22 November 2022 at 21:44:00 UTC, XavierAP wrote:

>

On Friday, 18 November 2022 at 18:54:01 UTC, monkyyy wrote:

>

Ds enum syntax is verbose compared to c; and the code block probaly isnt possible to write

I know I'm not teaching anything that's not known, but languages such as D, modern C++, C# etc requiring enums to be qualified, was absolutely intentional. And IMHO the reasons (against the C syntax) stand.

And wait, there is more: we have anonymous enums:

enum { A, B }

For these who are allergic to named enums.

// verbose API, readable
struct SuperLongStruct
{
    struct SuperLongInnerStruct
    {
        enum SuperLongEnum
        {
            VALUE_A, VALUE_B
        }

        SuperLongEnum super_long_flags;
    }
    SuperLongInnerStruct some_data;
}

// oh shoot

SuperLongStruct super_long_struct = {
    some_data: {
        super_long_flags:  SuperLongStruct.SuperLongInnerStruct.SuperLongEnum.VALUE_A | SuperLongStruct.SuperLongInnerStruct.SuperLongEnum.VALUE_A
    }
};

// oh nice!

SuperLongStruct super_long_struct = {
    some_data: {
        super_long_flags: .VALUE_A | .VALUE_A
    }
};

no need to introduce noise, no need to care about an alias that is leaking, the usage of the API is simple and expressive, i'm not sacrificing the API with simpler and meaningless names

i make fully use of scoped types in D, and i avoid the noisy verbosity that comes with it as a result

Adam's idea to use auto could be the proper compromise

It's not about what other languages do, it's about how it helps the user avoid making their code unreadable

MyType value = MyType.some;

is .some a static function? does it return something? is it a static variable? an enum? you don't know!

so the argument that it Enum Type Inference reduce readability is not valid, if your API is not readable, that's the problem

Helping users be more expressive and less verbose helps them keep and maintain proper naming for their types/variables, wich helps readability

AttackType attack_type = .MELEE;


(..)


switch (attack_type)
{
    case .MELEE:
    break;
}



now your variable name carry more and proper information about what it is,

November 23, 2022

On Wednesday, 23 November 2022 at 13:41:12 UTC, ryuukk_ wrote:

>
// verbose API, readable
struct SuperLongStruct
{
    struct SuperLongInnerStruct
    {
        enum SuperLongEnum
        {
            VALUE_A, VALUE_B
        }

        SuperLongEnum super_long_flags;
    }
    SuperLongInnerStruct some_data;
}

// oh shoot

SuperLongStruct super_long_struct = {
    some_data: {
        super_long_flags:  SuperLongStruct.SuperLongInnerStruct.SuperLongEnum.VALUE_A | SuperLongStruct.SuperLongInnerStruct.SuperLongEnum.VALUE_A
    }
};

// oh nice!

SuperLongStruct super_long_struct = {
    some_data: {
        super_long_flags: .VALUE_A | .VALUE_A
    }
};

Try alias E = SuperLongStruct.SuperLongInnerStruct.SuperLongEnum;

>
AttackType attack_type = .MELEE;


(..)


switch (attack_type)
{
    case .MELEE:
    break;
}



now your variable name carry more and proper information about what it is,

auto attack_type = AttackType.MELEE;

switch (attack_type) with(AttackType) {
    case MELEE:
    break;
}
November 23, 2022
> >

On Friday, 18 November 2022 at 15:37:31 UTC, Mike Parker wrote:

>

https://github.com/dlang/DIPs/blob/e2ca557ab9d3e60305a37da0d5b58299e0a9de0e/DIPs/DIP1044.md

It looks redundant (“with” and “alias” is enough IMHO).

November 23, 2022

On Wednesday, 23 November 2022 at 11:57:48 UTC, deadalnix wrote:

>

On Tuesday, 22 November 2022 at 21:44:00 UTC, XavierAP wrote:

>

On Friday, 18 November 2022 at 18:54:01 UTC, monkyyy wrote:

>

Ds enum syntax is verbose compared to c; and the code block probaly isnt possible to write

I know I'm not teaching anything that's not known, but languages such as D, modern C++, C# etc requiring enums to be qualified, was absolutely intentional. And IMHO the reasons (against the C syntax) stand.

And wait, there is more: we have anonymous enums:

enum { A, B }

For these who are allergic to named enums.

That doesn't work when interacting with existing c code base here.

November 23, 2022

On Wednesday, 23 November 2022 at 14:21:23 UTC, deadalnix wrote:

>

On Wednesday, 23 November 2022 at 13:41:12 UTC, ryuukk_ wrote:

>
// verbose API, readable
struct SuperLongStruct
{
    struct SuperLongInnerStruct
    {
        enum SuperLongEnum
        {
            VALUE_A, VALUE_B
        }

        SuperLongEnum super_long_flags;
    }
    SuperLongInnerStruct some_data;
}

// oh shoot

SuperLongStruct super_long_struct = {
    some_data: {
        super_long_flags:  SuperLongStruct.SuperLongInnerStruct.SuperLongEnum.VALUE_A | SuperLongStruct.SuperLongInnerStruct.SuperLongEnum.VALUE_A
    }
};

// oh nice!

SuperLongStruct super_long_struct = {
    some_data: {
        super_long_flags: .VALUE_A | .VALUE_A
    }
};

Try alias E = SuperLongStruct.SuperLongInnerStruct.SuperLongEnum;

>
AttackType attack_type = .MELEE;


(..)


switch (attack_type)
{
    case .MELEE:
    break;
}



now your variable name carry more and proper information about what it is,

auto attack_type = AttackType.MELEE;

switch (attack_type) with(AttackType) {
    case MELEE:
    break;
}
struct Action
{
    AttackType attack_type;
}

// no auto here

in your switch, did you read it?

switch attack_type with AttackType

this is not appealing and is the noise and repetition that needs to be suppressed

i was verbose when i declared my variable name, that's enough

and you didn't read my other comment

why should i declare an alias? it leaks the symbol to the scope, it is another symbol that i need to remember when i refactor, and it adds cognitive load when i read my whole module, too much gymnastic and scope bloat