-
Notifications
You must be signed in to change notification settings - Fork 265
Add "attempt" keyword for Go-like error handling #4051
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
base: development
Are you sure you want to change the base?
Conversation
|
I really like this solution! |
|
This seems like a good approach. Hopefully at some point, we can also build in some sort of error types so that we can check for particular errors by their types as opposed to their messages. |
|
That sounds like a good idea! |
|
what I don't understand is, the output of |
|
the location of the error is different than in the usual error message. is that intentional? (it may not be obvious, but in the example the first error point to |
|
This location doesn't seem right either: |
That's a good point. I was basically just copying the Go syntax that Mahrud pointed out in the other PR. Also, in an earlier draft, the second element was a string, not an |
|
As for the file positions, as I recall, errors originally all start out with |
Strictly speaking, in Go the return value is always of the expected type, so (inv, err) := attempt inverse n
if err =!= null then error("oops: ", err#"message")than a situation where the return value has different types in each branch: ret := attempt inverse n
if not instance(ret, Error) then inv := ret else error("oops: ", ret#"message")Also, what if you're writing an error-handling related function that needs to return an error? You need to separate the legitimate output Error object from the error that may have occurred in the process :) |
|
I stand by my original point. |
antonleykin
left a comment
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.
The two ideas are good.
The second (proposed by @seangrate) to have an Error type.
The original idea to "capture message of error" (due to @d-torrance) via attempt (returning it, not storing, proposed by @mahrud).
Returning one thing---either "result" or Error---would be more natural, since our language is not statically typed (e.g., the type of the "result" is unknown anyway).
M2/Macaulay2/d/evaluate.d
Outdated
| else ( | ||
| when r | ||
| is err:Error do seq(nullE, toExpr(err)) | ||
| else seq(r, nullE))); -- shouldn't happen |
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 shouldn't happen, unless the code fabricates it's own Error. I don't think we should let attempt output user-fabricated stuff.
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.
Makes sense. I've replaced this with an error.
| attempt c | ||
| Description | ||
| Text | ||
| The code @VAR "c"@ is evaluated and a sequence containing two elements |
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.
Emphasize that this is a keywork in the language, not a method. One could mentally parse (attempt 1)/0.
Perhaps give more examples: what would attempt 1/2; 1/0 give?
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.
Documentation expanded
8be50f4 to
85964c6
Compare
|
@antonleykin - I think I've addressed your comments. |
|
I would encourage you to reevaluate this decision. Here are a few dangers of this paradigm:
f := M -> attempt apply(codim M, i -> i^2);
# f(ZZ^100) -- gives #{} which is 0
# f((ZZ[x])^100) -- gives #Error{...} which is 2If you're going through the trouble of adding a new keyword, I'd like it to be robust and future proof, especially for MPI or RPC type applications. |
this I don't understand at all. There seems to be a basic confusion here. These
well, if you're going to write |
Why shouldn't error objects be produced at the top-level? Is something preventing it in this proposal? There is a huge upside, as @seangrate suggested above, in allowing custom subtypes inheriting from Error so that error checking looks more like We currently don't have an Error type, but once it exists I guarantee this PR isn't going to be the end of it. Soon we'll have extensions like Python error types and Flint error types and use it with hooks and threads and loads more that we can't imagine now. (Side note: @d-torrance if
Mistakes like this will sneak in, either by people treating More generally, I'm just surprised that we're going with a syntax like this: c := attempt codim M;
if instance(c, Error) then
if c#"message" == "codim: expected an affine ring (consider Generic=>true to work over QQ"
then c = planZZ(M)
else error "unexpected module" |
|
I'm convinced by @mahrud's arguments and prefer the Go-style two-element output. |
because that simply wouldn't work in the current setting. what would happen if you called your function that outputs an error your way without There absolutely should be subtypes, but this is irrelevant to the point I'm making. |
|
I went back to the Go-style output and also added some basic support for subclassing of i1 : MyError = new SelfInitializingType of Error;
i2 : MyError "foo"
o2 = MyError{"message" => foo}
o2 : MyError
i3 : error o2
stdio:3:5:(3): error: foo
i4 : attempt error o2
o4 = (, MyError{"message" => foo })
"position" => ../m2/debugging.m2:21:22-21:75
o4 : Sequence |
|
Looks like the error
is pointing to this line: error Error := err -> olderror new class err from processArgs err#"message"No obligation to fix this in this PR, but obviously the location should point somewhere more appropriate. There are probably multiple ways to fix it (e.g. maybe moving |
|
I understand less and less the logic. Now one can create |
|
The point of object oriented error handling is that instead of interrupting the computation, an error object is created to help the subsequent code resolve it and proceed without interruption. But there may be errors objects which can't be handled, so there should be a way to force an interrupt so that the user can intervene. This is where (c, err) := attempt codim M;
if instance(err, Error) then
if instance(err, RingError)
then c = planZZ M
else error("unexpected error: " | err#"message" | " at " | err#"position")into this: (c, err) := attempt codim M;
if instance(err, Error) then
if instance(err, RingError)
then c = planZZ M
else error(err) -- the original message and position are returned |
Class is stored in "value" field so we can subclass
Returns a pair (value, err) If code evaluates successfully, then err is null. Otherwise, it's an Error object with information about the error that occurred.
|
I made some changes that may address some of @pzinn's concerns.
Rather than getting the message and location by looking up keys in a hash table, we can use |
|
I think we are getting caught in the weeds and should tread carefully as the discussion diverged
I would like the language modifications to be minimal unless we are constructing a new language (Macaulay3 or GoMacaulay). I believe we should encourage users to, as someone wise said, Let's try to think about minimal changes of language could be done to accomplish 1. returns either a string that is printed or an |
|
I think it would be straightforward to use I don't think we should return just a string, as otherwise @antonleykin - Is the "extensive rethinking of how errors are done in Macaulay2" the idea of subclassing |
@mikestillman and I discussed this earlier today. In .m2 files, the keywords
This is a deficiency, but also seem to be a hypothetical edge case.
This would make returning a pair unnecessary. Returning a pair is, of course, not a big deal, but I think we'd be just paying tribute to the edge case (e.g., hypothetical users who would use nested
My point: we can export |
This is an alternative to #4047 that may address @mahrud's concerns. Rather than touching
try, we add a new keywordattemptthat returns a pair(val, err)(much like Go), wherevalis the value of the code anderris null if no error occurred, and otherwisevalis null anderris an instance of a newErrortype with information about the error that occurred .