May 08

On Tuesday, 7 May 2024 at 17:44:25 UTC, Paul Backus wrote:

>

On Tuesday, 7 May 2024 at 16:34:52 UTC, Steven Schveighoffer wrote:

>

I don't think this is going to be the case. Consider that without explicitly forwarding, the difficulty is not in preserving refness, but non-refness. If care is not taken (e.g. via using forward), then a non-ref parameter suddenly turns into a ref parameter (implicitly). This is not the case for enum parameters -- enumness is preserved whether it's not an enum or is an enum.

If a single function in a call stack forgets to pass a given parameter by ref, it will be copied. If your code relies on the parameter not being copied (e.g., if it has a disabled copy constructor), this failure to preserve ref results in a bug.

If a single function in a call stack forgets to pass a given parameter by enum, it will be evaluated at runtime. If your code relies on the parameter being evaluable at compile time (e.g., if it uses it as a template argument or in a static if condition), this failure to preserve enum results in a bug.

The two are exactly analogous.

If an entire ref chain is broken by one copy, then the semantics are different. That is, all of a sudden, you aren't affecting the original parameter.

If an entire enum chain is broken by one runtime evaluation, the code does not compile. This is either a loud error, or a silent acceptance that does not affect correctness. Of course, this assumes that the auto-enum in question does not do something drastically different based on enumness.

By example:

void foo(auto ref int x) {
   bar(x);
}

void bar(int x) {
   baz(x); // oops, this does not affect the caller's x
}

void baz(auto ref int x) {
   ++x;
}

// and with enums:
void foo(auto enum int x) {
   bar(x);
}

void bar(int x) {
   baz(x);
}

void baz(auto enum int x) {
   writeln(x); // same whether bar is enum or not, no semantic difference
}

Yes, if baz accepts by enum only, you will get a compiler error. Contrast that with ref, where you get no indication that something is wrong, and it is semantically wrong.

> >

Yes, generic code that wishes to forward enum-ness to another thing must use auto enum to accomplish this. However, comparing to the the current mechanism, things are pretty inferior. Switching from an enum parameter to a runtime parameter involves changing the place where parameters are put. You currently have to remember to create a nested template with an outer template that takes a tuple for this to work correctly (something which I doubt many generic functions do).

Put another way, if the cost of having the convenience of enum parameters is that we must also allow auto enum, I don't think this is a large enough drawback.

The argument here is essentially that it is worth it to make the library author's job more difficult in order to give the library users a better experience.

No, I think it makes both easier.

Consider the case of writef. We have two versions of writef, one which takes a string format via a runtime parameter, and one which takes a string format via compile time parameter.

They have to be called in different ways (burden on the user). You have to write two different functions, and overload them based on template constraints (burden on the developer).

    void writef(alias fmt, A...)(A args)
    if (isSomeString!(typeof(fmt)))
    { ... }

    void writef(Char, A...)(in Char[] fmt, A args)
    { ... }

// vs

    void writef(Char, A...)(auto enum Char[] fmt, A args)
    // note the lack of template constraints needed
    { ... }

It also has the benefit of being easier to explain and easier to document.

>

In general, I agree. But there's a point at which this argument falls apart. If you make the library author's job so difficult that they can no longer write correct code, it is library users who will ultimately suffer.

I don't think this case has been made, even with auto enum.

>

Personally, I think the ref storage class already crosses this line. The vast majority of generic D code does not handle non-copyable types correctly, and the design of ref is 100% to blame for this. Since enum uses the exact same design as ref, I think it's pretty reasonable to assume that it will cause the same kinds of problems.

I can't see how ref is responsible for this. Non-copyable types are difficult to work with in general. Can you explain further?

>

So, when I evaluate this DIP, here's the tradeoff I'm looking at:

  • Pro: more convenient library APIs (e.g., format).
  • Con: more bugs in generic library code.

I have different opinions of these categories.

-Steve

May 16

On Tuesday, 7 May 2024 at 15:20:24 UTC, Paul Backus wrote:

>

On Tuesday, 7 May 2024 at 11:23:10 UTC, Quirin Schroll wrote:

>

You’re right in that forward better be mentioned in the DIP. Writing an answer to this post, I found out that not only could forward easily be adapted to recognize enum parameters and handle them properly by leaving them alone, forward actually would handle them correctly today.

Good to know; thanks for looking into this.

However, the main issue here is not forward--it's that in order to write correct generic code with this feature in place, D programmers will forever be obliged to defensively write auto enum auto ref on any parameter they intend to forward (and will have to go back and add auto enum to many of their existing auto ref parameters).

Well, there’s always going to be legacy code that doesn’t interact well with the latest features. It’s unfortunate, but true for every new feature that extends APIs.
What are we gonna do? Never ever extend APIs?

>

Most D programmers already forget to add auto ref to such parameters (see for example the vast swathes of Phobos which fail to compile when used with non-copyable types). Adding an additional thing for them to forget is, like I said, setting them up for failure.

I cannot help but find this argument quite weak. It only concerns library authors. If you write production code and find that a function needs to change and forward ref, you can just change it to do so. Same with enum. It’s the same with attributes.

On the other hand, if a library author doesn’t bother to annotate functions as @safe, the library isn’t usable for SafeD.

>

(Also, auto enum auto ref is quite ugly and "attribute spam"-y, and some programmers will no doubt leave it out of their code intentionally in order to make the code look nicer--as they already do with existing attributes and parameter storage classes.)

The only thing I could imagine doing about this is shortening it to auto enum ref.

May 16

On Thursday, 16 May 2024 at 13:59:35 UTC, Quirin Schroll wrote:

>

Well, there’s always going to be legacy code that doesn’t interact well with the latest features. It’s unfortunate, but true for every new feature that extends APIs.
What are we gonna do? Never ever extend APIs?

What we do is, we weigh the costs and benefits of each proposed change, and decide accordingly. I agree that there are cases where introducing incompatibilities with "legacy code" is justified; I just don't think this is one of them.

>

I cannot help but find this argument quite weak. It only concerns library authors. If you write production code and find that a function needs to change and forward ref, you can just change it to do so. Same with enum. It’s the same with attributes.

Given how often people complain that D needs more libraries, I think it's in our best interest to make library authors' lives easier, not harder.

July 31

On Thursday, 25 April 2024 at 17:56:59 UTC, Quirin Schroll wrote:

>

Abstract
On function templates, allow enum to be used as a function parameter storage class and a member function attribute. Arguments binding to enum parameters must be compile-time constants, as if template value parameters. With auto enum, “compile-time-ness” is determined from argument (cf. auto ref) and queried via a trait.

I have been dying to have this feature in my hands for since I first read about it nearly 2 years ago. The fact that this DIP keeps getting left in limbo is very disappointing.
I’m a library author, I write a lot of compile-time code, and in private I have written my own std.traits-style library before. This DIP solves one of my most longstanding frustrations with writing functions that have a compile-time optimised variant. The possibility of enhancing IES is also really exciting. I haven’t commented on this DIP here until now because I actually didn’t notice this thread.

My only real gripe with the DIP is that isEnum should only work for enum parameters. Having it check for enum membership will surely create bugs in generic code somewhere.

July 31

On Wednesday, 31 July 2024 at 08:15:31 UTC, IchorDev wrote:

>

My only real gripe with the DIP is that isEnum should only work for enum parameters. Having it check for enum membership will surely create bugs in generic code somewhere.

I truly wonder how. In a function template, you can’t define an enum locally that has the same name as a parameter. And if there’s an eponymous enum constant outside the function’s scope, the name of the argument refers to the argument (unless you use with).

It says that it checks for enum membership, but the most relevant special case would be for simple enum constants, which are technically a shorthand for defining a member of an unnamed enum, so those are implicitly included.

Considering __traits(isRef) works to detect ref variables, too (in the current implementation), it would be weird if __traits(isEnum) wouldn’t detect enum other than enum parameters. Is it really a big issue that __traits(isEnum, arg) may return true if you misspell arg so that it accidentally becomes some enum? It’s not like it would be an error to use isEnum on it, it’d just return false instead, which IMO is just worse.

August 02

On Wednesday, 31 July 2024 at 22:55:36 UTC, Quirin Schroll wrote:

>

I truly wonder how. In a function template, you can’t define an enum locally that has the same name as a parameter. And if there’s an eponymous enum constant outside the function’s scope, the name of the argument refers to the argument (unless you use with). It says that it checks for enum membership, but the most relevant special case would be for simple enum constants, which are technically a shorthand for defining a member of an unnamed enum, so those are implicitly included.

It could still happen in string mixins, and generally we want a trait to mean one specific thing.

>

It’s not like it would be an error to use isEnum on it, it’d just return false instead, which IMO is just worse.

Silently doing the wrong thing instead of giving an error message IS worse—this is D, not JavaScript!

It sounds like you intend the DIP to mean isEnum checks for manifest constant declarations? This is really not clear in the text, it sounds like you want it to check whether is(typeof(symbol) == enum). You could probably clarify this a little better.

So, does isEnum yield true for template value parameters? If not, I think it’s too specific. Why not generalise it to all manifest constants and call it isManifestConstant (or just isManifest) instead? This could benefit meta-programming and potential future language features.

August 02

On Friday, 2 August 2024 at 03:49:22 UTC, IchorDev wrote:

>

On Wednesday, 31 July 2024 at 22:55:36 UTC, Quirin Schroll wrote:

>

I truly wonder how. In a function template, you can’t define an enum locally that has the same name as a parameter. And if there’s an eponymous enum constant outside the function’s scope, the name of the argument refers to the argument (unless you use with). It says that it checks for enum membership, but the most relevant special case would be for simple enum constants, which are technically a shorthand for defining a member of an unnamed enum, so those are implicitly included.

It could still happen in string mixins, and generally we want a trait to mean one specific thing.

>

It’s not like it would be an error to use isEnum on it, it’d just return false instead, which IMO is just worse.

Silently doing the wrong thing instead of giving an error message IS worse—this is D, not JavaScript!

It sounds like you intend the DIP to mean isEnum checks for manifest constant declarations? This is really not clear in the text, it sounds like you want it to check whether is(typeof(symbol) == enum). You could probably clarify this a little better.

So, does isEnum yield true for template value parameters? If not, I think it’s too specific. Why not generalise it to all manifest constants and call it isManifestConstant (or just isManifest) instead? This could benefit meta-programming and potential future language features.

I'll try to reword the DIP.

Checking if a type or an expression's type is an enemy type is quite easy as of now. Checking if some expression is a compile-time constant is not as trivial, but it's not exactly hard either. Checking if a symbol is a manifest constant, as far as I know, isn't possible. Are template value parameters manifest constants? Good question. They're compile-time constants, but not every compile-time constant is a manifest constant (e.g. static immutable values aren't), so maybe the answer is no. Literals aren't manifest constants either. There are many forms of compile-time constants.

Yes, this isn't JavaScript, but again, isRef doesn't check if it's a ref parameter, it checks if a symbol is a reference, and with the upcoming v2.111, that will include local variables, possibly more. isEnum checks if something is declared using enum, in particular after inference from arguments.

The only thing that isRef has to contend with is that it doesn't detect ref returning functions and one could argue it should. But that's beside the point as Enum Parameters doesn't propose enum returns, whatever that would even mean.

1 2 3
Next ›   Last »