I've just been in contact with a new user, who was understandably using throw("my message")
rather than throwing an ErrorException
via error()
.
Do we need the conceptual overhead of having both error
and throw
? Why not conceptually have the following definitions:
throw(msg::AbstractString) = throw(ErrorException(msg))
throw(e::Exception) = builtin_throw(e)
A somewhat related question - what do we gain by allowing people to throw things which aren't subtypes of Exception
?
I've wondered if throw("...")
would be better, also.
A somewhat related question - what do we gain by allowing people to throw things which aren't subtypes of Exception?
In Common Lisp, it's accepted to use throw
to return values from a deeply nested context, and it's sometimes much more convenient than passing the computation result through many functions.
I remember seeing this proposed before, and after some digging I found the comment by @kmsquire https://github.com/JuliaLang/julia/issues/13515#issuecomment-147742619
throw
throws an Exception
object. throw(::String)
doesn't make sense conceptually; it would have to be throw(ErrorException(::String))
. But if you don't have an appropriate error type to use, just saying error
is much cleaner.
You can, in fact, currently throw a string without wrapping it in an ErrorException
,
julia> typeof(try ; throw("asdf") ; catch ex ; ex end)
String
The observation here is that this probably doesn't make a whole heap of sense, so we have the opportunity to turn two verbs into one by just removing error. It seems like a good pun to me.
I don't think we should try to guess what the user want. We are in general moving away from error
so I'll be fine to deprecate that. Allowing throw
ing a string and not actually throwing it is encouraging bad coding style.
We are in general moving away from error so I'll be fine to deprecate that.
Why?
It offers no information of what actually happens.
Usually the error can be replaced with ArgumentError
(which roughly corresponds to pythong's ValueError
) if it doesn't fit in any other category.
We don't have a good type hierarchy for exceptions yet but we still should not encourage throwing the most general error possible.
I'd be content to simply deprecate error
, though I still prefer the option of overloading throw
. This is no more "guessing what the user wants" than all the other uses of multiple dispatch in julia.
The argument that we should encourage users to throw more specific exception types holds more weight.
This is no more "guessing what the user wants" than all the other uses of multiple dispatch in julia.
No. Multiple dispatches should not be used that way. Multiple dispatch is generally used to select the best implementation and not completely changing the behavior.
Multiple dispatch is generally used to select the best implementation and not completely changing the behavior.
What do we gain by allowing users to throw things which are not subtypes of Exception
? If the answer is "nothing", we can also deprecate that, and there would be no conceptual ambiguity here.
I agree that this is a change of behavior, and the deprecation would need to be handled correctly in stages.
there would be no conceptual ambiguity here.
There is. Instead of giving an error you want it to do something that shouldn't be encouraged.
So to recap, my own interpretation of the proposal is this:
error
throw
always ends up throwing something which is a subtype of Exception
(or more precisely, that catch
always receives an Exception
).Exception
to throw
(perhaps just a String
), it automatically gets wrapped in ErrorException
, for convenience.In the last point, I see this as more-or-less "sugar" (it's not literally syntax sugar; we could use dispatch to arrange this) so that in the cases you want to be lazy, you can. Now, I do understand that it's much better practice to use the corresponding Exception
type - but you have to admit that there are times that you are writing a hacky script and want to do it quickly but you want some useful error message at some point, and writing throw(ErrorException("..."))
is too much overhead in my opinion. And I've found we don't have good exceptions for a lot of situations.
The consequences are that this "lispy" style of breaking through multiple stack frames by throwing whatever data type you want might not work as now. However, to make that safe, I think it would be better practice to make your own Exception
subtype to explicitly mark and carry that information.
but you want some useful error message at some point, and writing
throw(ErrorException("..."))
is too much overhead in my opinion.
If people just want this, then we should keep error
and clearly document that it shouldn't be used to show user errors. FWIW, for writing something quick, @assert
is usually good enough. I usually only switch away from it if I want the code to be more permanent in which case I'll pick the right error type. (which would be ArgumentError
most of the time since I'm just checking input ranges).
And I only said I'm fine with deprecating error
because it should generally not be used, not because it should be done with a worse API. For the confusion between error("")
and throw("")
's current behavior, only allowing throw
to throw Exception
is the right thing to do. There's also nothing special about string since error
also does implicit string convertion.
The lisp usage of exception as control flow should not be as much of a concern in julia since we don't usually have as deep nesting AFAICT.
If people just want this, then we should keep error and clearly document that it shouldn't be used to show user errors.
+1. Base and serious packages should have properly typed exceptions, but for personal code, it would be annoying to specify which category each error fits into, because 99% of the time I have no intent of ever catching them, and the error message is all the information I want.
And I only said I'm fine with deprecating error because it should generally not be used, not because it should be done with a worse API
Whether this is worse depends on your point of view. Throwing generic errors is more convenient and concise (good things), but less precise (arguably a bad thing). It's fine to discount convenience and concision as not worth the tradeoff here, but they are legitimate points in the design space, and julia is stylistically a very concise language.
Furthermore, when exceptions are used to signal something very exceptional (programmer error) rather than to direct control flow, the detailed exception type is much less important: you probably shouldn't be directing flow control based on the type anyway. I feel this is a distinct departure from popular languages such as java, python, and C++, where flow control based on exception type is supported as part of the language, and is perfectly routine.
The lisp usage of exception as control flow should not be as much of a concern in julia
Yes, I hope we never start using exceptions for control flow :+1:
Furthermore, when exceptions are used to signal something very exceptional (programmer error) rather than to direct control flow, the detailed exception type is much less important:
Why not use @assert
for that? It looks like the existence of error
encourages abusing exceptions.
Why not use
@assert
for that
This is quite a good point, I'd forgotten that it allowed arbitrary descriptive text.
So what do people think of the following plan:
error()
in favor of @assert
and throwing explicitly typed exceptions.throw
- only allow subtypes of Exception
.I didn't know @assert
could have a custom message - that makes it more useful! Not sure that it replaces every use of error
, but probably the majority.
@andyferris sounds like something that should be more prominent in the manual, then, if someone wants to open a PR...
For me, @assert
and error()
have somewhat different objectives:
@assert
says: Unless I messed up my code, this should hold; if it doesn't, bail out with an error descriptive enough to figure out what the problem was instead of continuing, producing a more hard-to-backtrack error later on.error()
says: I know this might occasionally go wrong (due to bad input, state, moon phase, whatever), but I'm too lazy to figure out the most appropriate Exception
to throw
here.I see the merit in discouraging the use of error()
(although it might be absolutely ok for your hacky script), but I don't think replacing it with @assert
without further thought (because the deprecation told me so) is a good idea.
So, we're almost back to the start of this discussion.
throw
allows beginners to throw somewhat nonsensical things (not subtypes of exception) by mistake.error()
verb just for throwing a blessed exception type, especially one that's somewhat discouraged.Does anyone have ideas about how to solve these, without loosing the concision of error()
? For now, I'm all out, I think.
I suppose a survey of the way error()
is used in packages would be useful at this stage.
I use error
when I feel that this error is unlikely enough to happen for me to not take the time to write something like throw(ArugmentError("..."))
. or when I don't know what actual exception fits best.
I think there are two related but separate issues here:
error("...")
vs. throw("...")
. Could be resolved by going with the OPs proposal or by limiting throw
to take Exception
s.error
(to be used for logging). Could be resolved by going with the OPs proposal.Concerning 2, I wonder how common a "log error and throw exception" pattern vs. a "throw an exception and log an error where it is caught" pattern would be. If the former is more common, it might even make sense to go the other way round and allow error
to take an Exception
. (I have no opinion here, just mentioning this as an option to consider.)
[...] go the other way round and allow
error
to take anException
Thats basically what MATLAB does, i.e. error()
accepts either a message or an exception+message
Freeing up error (to be used for logging)
@martinholters Hah! You've discovered my other (not so secret) motive :-) In that case I want to use @error
, but it seems problematic to have both @error
and error()
mean different things.
I wonder how common a "log error and throw exception" pattern vs. a "throw an exception and log an error where it is caught" pattern would be
I've worked in code bases (in C++) using both of these patterns, and I far prefer the second one. The catch site is pretty much forced to log the error in either case because it doesn't know whether the thrower already did so. This results in double logging of errors all over the place which is somewhat annoying and confusing.
I don't think
throw(Exception("msg"))
is possible because Exception
is ever used as base exception (I'd have named it BaseException
like in Python... but that's an other story).
Maybe
throw(Error("msg"))
with
Error(msg) = ErrorException(msg)
will be nice but I don't think there is a general consensus for this...
Maybe we could first deprecate use of error
as function and suggest changing code from
error(msg)
to
throw(ErrorException(msg))
Most helpful comment
For me,
@assert
anderror()
have somewhat different objectives:@assert
says: Unless I messed up my code, this should hold; if it doesn't, bail out with an error descriptive enough to figure out what the problem was instead of continuing, producing a more hard-to-backtrack error later on.error()
says: I know this might occasionally go wrong (due to bad input, state, moon phase, whatever), but I'm too lazy to figure out the most appropriateException
tothrow
here.I see the merit in discouraging the use of
error()
(although it might be absolutely ok for your hacky script), but I don't think replacing it with@assert
without further thought (because the deprecation told me so) is a good idea.