-
Notifications
You must be signed in to change notification settings - Fork 269
Add "trap" 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 |
Also bump to version 0.2 and start a changelog
Now takes a single input (the object to print) and leaves the formatting of the message to the caller. Also renamed to RobustPrintNetMethod so we can also have RobustPrintSringMethod (which we'll use for arbitrary errors)
|
I presume this is just a rebase? |
|
Yup -- fixing the conflicct |
|
@antonleykin @pzinn @mikestillman any objections to merging this for now, with the expectation that there may be future changes as Paul indicated? (And, of course, the possibility of reverting one or both new features before the release if we discover major problems) |
|
fine with me |
|
I am looking at the final PR, and will review it too, and I will have comments (already do, but going back over the long discussion to make sure I don't re-ask too many questions. I think I like this version now, but I want to make sure it doesn't slow things down, and that the doc matches the actual behavior (e.g. doc mentions |
Also swap its values, i.e.: * true = yes, we caught an error * false = no, we didn't, either because it wasn't an error or it was some error we couldn't catch (syntax, interrupting)
Raise them instead
|
Oops -- |
|
@d-torrance Thanks! Perhaps we should also add to the "error handling" node "stopIfError" (a mutable variable), and a sentence about It seems like other things that I thought were a problem have somehow been fixed since I tried this yesterday? Not sure... (I needed to do |
|
Sounds good! I've updated "error handling" as requested. The only difference between |
Mention "trap" in the description, and also add a link to stopIfError.
|
It seems like we have 5 different forms for try blocks. Perhaps we should deprecate a couple versions, or give guidance about which should be used? Which ones are currently used? (That question is for myself, I'll take a look!) I do like the trap function/keyword though! |
|
I found the problem: The extended markdown package (well, one of 3 different packages I just disabled) was grabbing shift-enter... Disabling them, and restarting makes it work. |
|
There are six forms: try x
try x else z
try x then y
try x then y else z
try x except err do z
try x then y except err do zThey're all documented in this PR. I don't see a problem with having this many -- it doesn't hold a candle to all the different forms of |
|
I agree with Doug about different forms of try.
There's a markdown package?! |
|
@mikestillman is this good to merge? |
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 .