-
Notifications
You must be signed in to change notification settings - Fork 16
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
Assertions and irrecoverable runtime errors #158
Conversation
This commit introduces three functions to the standard library: - `runtimeError` for raising fatal errors in impossible cases or in case of gross violation of function preconditions. - `assertImpossible` for indicating impossible branches - regular `assert` function All these function are pure, so they should not be called at runtime of a correct program.
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.
Marked two minor changes i would prefer, but aside from that all seems good. Maybe if we decide to add reflection (like macros evaluating to filename, line number, etc) this module could be revisited.
src/Eval/External.ml
Outdated
@@ -55,8 +55,12 @@ let int64_cmpop op = int64_fun2 (fun x y -> of_bool (op x y)) | |||
|
|||
let str_cmpop op = str_fun (fun s1 -> str_fun (fun s2 -> of_bool (op s1 s2))) | |||
|
|||
let runtime_error msg = | |||
failwith ("Runtime error: " ^ msg) |
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 don't think this should be implemented using failwith
. Triggering this function will result in output:
Fatal error: exception Failure("Runtime error: Assertion failed")
Personally i think it should be Just Runtime error: Assertion failed
, so this code should be changed to eprintf + exit 1
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 would add possibility to add custom error code/number on exit.
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 agree that this error should not be implemented with failwith
. Maybe a custom exception that is handled in the main file would be better? The REPL implementation could catch this exception and recover with a prompt for an another command, instead of aborting the interpreter.
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.
Actually that would probably be best
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 don't know and possible I am wrong but this sounds like an error implementation which we would make in standard library.
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.
Do you mean the issue #130? I think these two are two different things. In this PR we talk about errors that should never occur in correct programs (but unfortunately, most of programs are incorrect), so the effect of such error is pure ([]
). In #130 the error has some effect visible in types. However, after merging this PR, we may rethink if we still want to implement #130.
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.
However, I realized that catching runtime error in REPL is not as trivial as I thought. It is not just adding extra match clasue in Eval.ml, because the exception are catch before the evaluation. I think it is still possible to handle it correctly, because the evaluator is in CPS, but it would be a more complex change, and should be done in a separate issue.
I think we should have default |
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.
One thing to fix, and in my opinion PR is ready to merge
I brought this PR up to date with master and made some small changes based on the discussion we had.
|
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 agree with the changes, and generally, I approve this PR (this PR is created be me, so GitHub forbids me from approving it formally). I have some minor comments (below), that I would like to have discussed, but not necessarily implemented.
else (extern dbl_divInt : Int -> Int -> Int) self n | ||
pub method div (n : Int) = | ||
assert {msg="Division by zero"} (n != 0); | ||
(extern dbl_divInt : Int -> Int -> Int) self n |
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 have a philosophical question: is the type of this extern correct? Or should it be Int -> Int ->[] Int
? The decision will to influence the overall type of this method (because it uses assert
), but in the potential optimizing compiler can we assume that this function always terminates correctly?
The same question applies for some other function modified in this PR.
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.
Well the effect in this case doesn't come from the extern, but from assertion, so in my opinion it works both ways.
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 agree with these two methods, and proposed names are fine. I think, the unwrapErr
function is missing. As a use case, consider that we have some polymorphic ~onError
implicit in the context, so we can write a code optimistically using *Err
functions without explicit error handling, but we still can recover from an error in some handler.
Moreover, other methods and functions for manipulating Option
type, like map
, fold
, iter
, etc. are missing, but the can be added as a separate issue.
|
||
{## For `Some x` returns `x`, otherwise the provided argument is returned. | ||
#} | ||
pub method unwrapOr default = |
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 was wondering, if the default
parameter should be named, because in almost all cases it will be provided. But I think, the proposed interface is good: the name of this method suggests what is the meaning of its first parameter, and writing code like x.unwrapOr {default = 42}
would make some unnecessary noise.
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, I had the same dilemma here and came to the same conclusion.
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.
Alternative (although I don't know how good) would be for unwrap
to take optional argument default.
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.
Functions unwrap
with optional parameter and unwrapOr
have different effects. The latter is more pure (->
instead of ->[]
).
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.
It looks good.
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 agree that more Option
functionality should be added, and can be done in separate PR. And beside that, all changes look good and should be merged.
This commit introduces three functions to the standard library:
runtimeError
for raising fatal errors in impossible cases or in case of gross violation of function preconditions.assertImpossible
for indicating impossible branchesassert
functionAll these function are pure, so they should not be called at runtime of a correct program.
Resolves #157