Jump to page: 1 25  
Page
Thread overview
March 04
One of the reddit comments posted this link:

https://elm-lang.org/blog/compiler-errors-for-humans

Wow, those error messages are MUCH better than D has. Want my opinion on the best thing D should change for productivity? Completely overhaul the error messages.

The formatting in the link is nice, but what really wins is how it gives relevant details. "Missing age field" and "Html and String mismatch in array" sum up what it said - and I wish we had that in D too.

How many template constraint messages would be nice if it just said "missing popFront" instead of 40 lines of overload failed spam?

oh yeah that would be nice.
March 04
On Monday, 4 March 2019 at 14:50:11 UTC, Adam D. Ruppe wrote:
> One of the reddit comments posted this link:
>
> https://elm-lang.org/blog/compiler-errors-for-humans
>
> Wow, those error messages are MUCH better than D has. Want my opinion on the best thing D should change for productivity? Completely overhaul the error messages.
>
> The formatting in the link is nice, but what really wins is how it gives relevant details. "Missing age field" and "Html and String mismatch in array" sum up what it said - and I wish we had that in D too.
>
> How many template constraint messages would be nice if it just said "missing popFront" instead of 40 lines of overload failed spam?
>
> oh yeah that would be nice.

Among one of them:

https://issues.dlang.org/buglist.cgi?component=dmd&keywords=diagnostic%2C%20&keywords_type=allwords&list_id=225094&product=D&query_format=advanced&resolution=---
March 04
On Mon, Mar 04, 2019 at 02:50:11PM +0000, Adam D. Ruppe via Digitalmars-d wrote: [...]
> How many template constraint messages would be nice if it just said "missing popFront" instead of 40 lines of overload failed spam?
[...]

This is why I'm starting to think sig constraints are not the genius idea they first appeared to be. The basic problem is that when you write a sig constraint, you're basically saying "I only accept template arguments if they don't cause an error, otherwise it's not my problem and somebody else can pick up the tab", whereas the essence of user-friendly error-reporting is "I accept everything that looks like it ought to work, and if something breaks, I'll tell you why it didn't work".

With the former, you're essentially absolving yourself from the responsibility of dealing with bad input by offloading it to other overloads in the overload set. If nobody else in the overload set picks up the tab, the compiler is left to clean up the mess, which it can't possibly do a good job of because it wasn't hired to do this. I.e., it doesn't understand the logical context of the error well enough to be able to pinpoint and describe the problem in a user-friendly way.

To have user-friendly error reporting, the code that was intended to pick up those bad arguments *need* to pick them up, and then report any errors that were found -- which can then be made friendly because now we're in the intended code context, and thereby have enough information to emit a sensible message that makes sense in the intended context.

This is why years ago I said both here and in the Phobos PR queue that sig constraints need to be written in such a way that anything that *might* possibly be intended for that function ought to be accepted, and static ifs should be used inside the function body to dispatch to the code that handles the corresponding template arguments, with an assert(0) at the end that tells you why the arguments weren't matched. A human needs to write this, because auto-generated messages by the compiler will never come up to the standard of user-friendliness.

This isn't the full solution, but it's at least a first stab at fixing this problem. Ultimately, the philosophy behind sig constraints is fundamentally incompatible with user-friendly error reporting, and possibly a different approach needs to be adopted.


T

-- 
Study gravitation, it's a field with a lot of potential.
March 04
On Monday, 4 March 2019 at 15:47:12 UTC, H. S. Teoh wrote:
> This is why I'm starting to think sig constraints are not the genius idea they first appeared to be. The basic problem is that when you write a sig constraint, you're basically saying "I only accept template arguments if they don't cause an error, otherwise it's not my problem and somebody else can pick up the tab", whereas the essence of user-friendly error-reporting is "I accept everything that looks like it ought to work, and if something breaks, I'll tell you why it didn't work".

It's even worse. There might be another sig constraint that is perfectly happy. It might even be in another library. Your function has no way to know that. Ergo, error messages are hard.

With Rust a type declares to conform to a trait explicitly. Error messages are easy.

With D it seems we are reinventing traits, poorly. And all because we wanted to avoid naming them?
March 04
On Mon, Mar 04, 2019 at 04:49:25PM +0000, Sebastiaan Koppe via Digitalmars-d wrote:
> On Monday, 4 March 2019 at 15:47:12 UTC, H. S. Teoh wrote:
> > This is why I'm starting to think sig constraints are not the genius idea they first appeared to be. The basic problem is that when you write a sig constraint, you're basically saying "I only accept template arguments if they don't cause an error, otherwise it's not my problem and somebody else can pick up the tab", whereas the essence of user-friendly error-reporting is "I accept everything that looks like it ought to work, and if something breaks, I'll tell you why it didn't work".
> 
> It's even worse. There might be another sig constraint that is perfectly happy. It might even be in another library. Your function has no way to know that. Ergo, error messages are hard.

Yep.  This can be good or bad: allowing this flexibility *can* mean that the other library can extend your function without you needing to be involved.

Of course, this flexibility comes at a pretty steep price.


> With Rust a type declares to conform to a trait explicitly. Error messages are easy.
> 
> With D it seems we are reinventing traits, poorly. And all because we wanted to avoid naming them?

No, I think the original reasoning was that allowing arbitrary boolean clauses in sig constraints is more expressive than allowing only a subset of expressions (e.g., implementsTrait!T).  It's necessarily not wrong per se, but the extra expressive power does come at a price. And we've been paying for it over and over.

It's just like the tradeoff between stack machines and Turing machines. Turing machines are powerful enough to express anything you might want to compute, but it comes at the price that certain seemingly-simple things become undecidable. If you're willing to give up a certain degree of expressiveness, you can eliminate (some of) the undecidable problems.


T

-- 
Today's society is one of specialization: as you grow, you learn more and more about less and less. Eventually, you know everything about nothing.
March 05
On 05/03/2019 5:49 AM, Sebastiaan Koppe wrote:
> With Rust a type declares to conform to a trait explicitly. Error messages are easy.
> 
> With D it seems we are reinventing traits, poorly. And all because we wanted to avoid naming them?

Rust traits and Swift's Protocols are what I think of as inverse signatures.

The concept itself dates back to ML, where the signature has the capacity to adapt to the implementation to some degree. We can do this in D probably better than any other language and not require a horrible linking "type".

But the DIP is going to be massive and it will mean Phobos will get an almost complete redesign. Its not something I'm bringing to the table until I'm very sure that I have it completely nailed down and not just the basic semantics. It has potential for being D's killer feature. But it could also never make it out of community review. Its worth waiting for, now that these issues have matured quite a bit.
March 04
On Mon, Mar 04, 2019 at 02:50:11PM +0000, Adam D. Ruppe via Digitalmars-d wrote:
> One of the reddit comments posted this link:
> 
> https://elm-lang.org/blog/compiler-errors-for-humans
[...]

P.S. One point I disagree with in that article, though, is color.  I find colors very tiresome on the eyes and distracting from focusing on the code. Worse yet, they are often conflicting with the default background colors I set on my terminal.  So if we were to do colors (e.g. in recent dmd releases) I'd insist that there be an option to turn it off.

I'm aware that my opinion is in the vast minority, though.


T

-- 
What are you when you run out of Monet? Baroque.
March 04
On Monday, 4 March 2019 at 15:47:12 UTC, H. S. Teoh wrote:
> [snip]
>
> This is why years ago I said both here and in the Phobos PR queue that sig constraints need to be written in such a way that anything that *might* possibly be intended for that function ought to be accepted, and static ifs should be used inside the function body to dispatch to the code that handles the corresponding template arguments, with an > assert(0) at the end that tells you why the arguments weren't matched. [snip]

What would be an example of a signature constraint that you think would be worth keeping? I.e., why not do everything with static ifs?
March 04
On Monday, 4 March 2019 at 16:49:25 UTC, Sebastiaan Koppe wrote:
> On Monday, 4 March 2019 at 15:47:12 UTC, H. S. Teoh wrote:
>> This is why I'm starting to think sig constraints are not the genius idea they first appeared to be. The basic problem is that when you write a sig constraint, you're basically saying "I only accept template arguments if they don't cause an error, otherwise it's not my problem and somebody else can pick up the tab", whereas the essence of user-friendly error-reporting is "I accept everything that looks like it ought to work, and if something breaks, I'll tell you why it didn't work".
>
> It's even worse. There might be another sig constraint that is perfectly happy. It might even be in another library. Your function has no way to know that. Ergo, error messages are hard.
>
> With Rust a type declares to conform to a trait explicitly. Error messages are easy.
>
> With D it seems we are reinventing traits, poorly. And all because we wanted to avoid naming them?

I think that's a little drastic. Template constraints are far more powerful than Rust traits or Swift protocols.

I agree, though, that their design makes it much harder to give good error messages.
March 04
On Mon, Mar 04, 2019 at 06:18:14PM +0000, jmh530 via Digitalmars-d wrote:
> On Monday, 4 March 2019 at 15:47:12 UTC, H. S. Teoh wrote:
> > [snip]
> > 
> > This is why years ago I said both here and in the Phobos PR queue that sig constraints need to be written in such a way that anything that *might* possibly be intended for that function ought to be accepted, and static ifs should be used inside the function body to dispatch to the code that handles the corresponding template arguments, with an > assert(0) at the end that tells you why the arguments weren't matched.  [snip]
> 
> What would be an example of a signature constraint that you think would be worth keeping? I.e., why not do everything with static ifs?

Haha, good question.  My current stance (which may change -- I'm not fully convinced either way yet) is a matter of documentation.  Static ifs inside the function body are invisible to the user, since they are implementation details, so they don't tell the user what is expected by the function.  A sig constraint is part of the function signature, and therefore a good place to document intent.

Note that intent may not be the same thing as implementation.  For example, if myFunc logically expects an input range, but the current implementation can only handle bidirectional ranges, then I'd say put isInputRange!R in the sig constraints, and static assert(0) inside the function body if R is not also a bidirectional range.  IOW, the intent is that myFunc should accept all input ranges, but at the moment our algorithm can only handle a subset of that.  Instead of cluttering the sig constraint with unreadable clauses like "if this is an input range, and it also has .length, and it also has feature X and trait Y, and it also howls at the moon at night", just keep it simple and to-the-point: "myFunc expects an input range". The rest of the dirty implementation details can be documented in the ddoc comment and checked with static ifs / static asserts inside.

Similarly, if two different algorithms are used depending on whether R is an input range or a forward range, that should be done via static if -- the user doesn't care whether your algorithm treats forward ranges differently from input ranges; your function should present a unified, logically-whole API rather than two similar-looking overloads with inscrutable sig constraints.  And if the function logically ought to also accept bidirectional ranges but your current algorithm for whatever reason breaks in that case, the sig constraint should still declare that it accepts all input ranges, but you'd have a static assert in the function body that explains to the user exactly why bidi ranges aren't currently supported.

Of course, this doesn't quite solve the problem of bad error messages when you passed something that you thought was an input range, but fails the isInputRange constraint.  But with simplified sig constraints of this sort, it's easier to tell what went wrong:

	couldn't match call to myFunc, candidates are:
	auto myFunc(R) if (isInputRange!R)

is a lot easier to read, and figure out what went wrong, than:

	couldn't match call to myFunc, candidates are:
	auto myFunc(R) if (isInputRange!R && !isForwardRange)
	auto myFunc(R) if (isForwardRange!R && hasLength!R && howlsAtMoon!R)
	auto myFunc(R) if (!howlsAtMoon!R && (isInputRange!R || is(ElementType!R : dchar)) && !isSomeString!R)

where you can't even tell at a glance which overload was the intended one.

Once we've eliminated needless overloads from the equation, the problem of emitting better error messages is simplified to emitting a sane error message for one failed constraint of one overload, rather than N failed constraints for M overloads.  For that, perhaps Andrei's idea of adding string clauses to sig constraints might be one way of tackling this.

But in any case, there needs to be a way for a template like isInputRange to emit a specific error like "R does not implement .empty", rather than just silently failing (because errors are gagged -- D's version of SFINAE) and offloading to whatever other overloads there may be.  Then when the compiler lists the candidate functions that failed to match, it could also display this error message beside each one to indicate what went wrong, where.


T

-- 
"I suspect the best way to deal with procrastination is to put off the procrastination itself until later. I've been meaning to try this, but haven't gotten around to it yet. " -- swr
« First   ‹ Prev
1 2 3 4 5