-
-
Notifications
You must be signed in to change notification settings - Fork 160
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[RFC 0135] Custom asserts #135
Conversation
This pull request has been mentioned on NixOS Discourse. There might be relevant details there: |
I have removed the |
- Implementation clutter | ||
- This could end up as a confusing and underutilized feature that hardly anybody knows about | ||
|
||
# Alternatives |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you should first consider the options we have today without language changes which don't seem to be mentioned in your RFC at all:
lib.assertMsg
which outputs a message viabuiltins.trace
if it isfalse
:assert lib.assertMsg condition "message"; …
- An idiom of using
|| throw
to get an even nicer error message at the expense of it reading a bit strange to the uninitiated readerassert condition || throw "message"; …
. This will also have correct location information etc. while hiding the often ugly and uninformative AST dumpassert
does by default.
rfcs/0135-custom-asserts.md
Outdated
Consider a nixpkgs package expression that wants to validate its arguments. Currently, the best way to | ||
provide a custom error message is to use `assert … -> throw …; …`. | ||
This method has several disadvantages: Since an implication from a falsehood is always true, you are required | ||
to invert the condition. Additionally, since the assertion itself is not triggered by the error, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missed this bit on my first read through. Note that this drawback is not an issue if you do assert condition || throw "…"; …
. No need to invert, quite straightforward.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I removed that.
rfcs/0135-custom-asserts.md
Outdated
This method has several disadvantages: Since an implication from a falsehood is always true, you are required | ||
to invert the condition. Additionally, since the assertion itself is not triggered by the error, | ||
the function of the `assert` keyword is reduced to providing an imperative shorthand for `seq`. This also means that by default, | ||
the error location is not printed, and there is no mention of an assert in the error message. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is not true. throw
also retains the location where it is called and it is displayed correctly to the user.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hm, I thought it was.
} | ||
``` | ||
|
||
Users of this system would be forced to forgo the convenience of imperative-style `assert …; …` in favor of `seq`-like syntax in order to benefit from improved type errors. With this change, no longer! A variant `isType` function could be declared: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can kind of have your cake and eat it, too, with a custom system. Such one has actually been added to nixpkgs by @roberth:
lib.throwIfNot cond1 "msg1"
lib.throwIfNot cond2 "msg2"
stdenv.mkDerivation { /* … */ }
This relies on a neat trick that you can return the identity function from a function, causing anything further in the source file to act as if that lib.throwIfNot
and its first two arguments hadn't been there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh that’s neat! I still think assert …;
is more idiomatic
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
See this prior discussion NixOS/nixpkgs#154292
Nitpick: Idioms evolve from a language and its use, not the other way around. We're designing a language feature, so existing idioms are relevant but of secondary importance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
that’s a nice way to view it! I rephrase: I think my suggestion would make it more natural, easier to understand and more distinctive. It uses the syntax specifically for asserts and only for that, instead of co-opting returning identity sometimes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In my opinion, these other idioms do not demonstrate that this change is not needed, but demonstrate that there is a lack of unified assertion functionality, which has left the conciseness of the dedicated assert
syntax behind.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If we had $
like Haskell, we wouldn't miss the assert ..;
syntax so much because we wouldn't need more parentheses either way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
$
keeps getting brought up, but it is an even more invasive change and would introduce more unfamiliar syntax to a language non FP programmers already struggle with conceptually…
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, IMO assert
is a more user-friendly idiom. And it already exists in the language!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's little a bit unfair: for anybody with no lazy FP experience, syntax will be the least of their problems. No debugger, no printf()
, and brain-meltingly weird stack traces.
But yeah, language stability is precious.
This RFC is open for shepherd nominations! |
This RFC has not acquired enough shepherds. This typically shows lack of interest from the community. In order to progress a full shepherd team is required. Consider trying to raise interest by posting in Discourse, talking in Matrix or reaching out to people that you know. If not enough shepherds can be found in the next month we will close this RFC until we can find enough interested participants. The PR can be reopened at any time if more shepherd nominations are made. |
Since there are not enough shepherds yet, we have moved this RFC to draft state for now. It can be reopened at any time if more shepherd nominations are made. |
Here's an interesting comment by @roberth:
Thinking about this I'm coming to these conclusions:
Considering this, what's the benefit of this RFC then?
This means that it's very much possible to implement almost exactly this using either a builtin function or a library function. And yes indeed, similar to the previously-mentioned # Could also be `builtins.assert`
lib.assert {
success = foo == bar;
message = "failed";
}
null The only difference is that there's no I did think about how formatters might struggle formatting it correctly if there's no |
In addition, I want to point out that there's more than just boolean-style checks one might want to check. Notably there's also |
So here's an alternate proposal:
|
That sounds good, I will close this RFC. If you want you can create an RFC for your idea. |
Just realized a mistake I made above: Library functions like that only work for a subset of expressions of course, e.g.
wouldn't work of course. While I'm not a huge fan of the original idea in the RFC here, how about this other idea I proposed, to have an apply foo;
apply bar;
baz
# Equivalent to
foo (bar baz) This would then be general enough to be also usable for apply lib.guard { ... };
if ... then ... else ... but also much more, see NixOS/nix#1845. |
For these guards, would it be sufficient to use the existing with lib.guard { ... };
if x then y else z |
No, it's nicely lazy.
|
Rendered