-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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: APIs for recoverable failures #45080
Comments
Can we get the optimizer to optimize try-catch better instead? Then we wouldn't need the
|
I think this is a good initiative. It's at the same time clear to me that recoverable errors in Julia Base would be amazing, and also not clear to me at all which API is nicest. Having Base |
The obvious obstacles in the optimization current exception handling system are:
Another approach (perhaps more "straightforward" in some sense) is to introduce "typed But, more importantly, as linked from the OP, I think there is an aspect in which this error value convention makes sense beyond optimization: When to
It is important to realize that |
Sure, but the |
Actually, I think it'd be better to clarify a more concrete point first. If Alternatively, a more principled approach is to call the So, independent of whether or not |
So would |
I think discussing abstract type DerivedFromTry <: Function end
function try end
function (f::DerivedFromTry)(args...; kwargs...)
result = try(f, args...; kwargs...)
if result isa Ok
return unwrap(result)
else
throw(unwrap_err(result))
end
end to codify that Then the API designer can choose to derive struct Get <: DerivedFromTry end
const get = Get()
# maybe `function get::DerivedFromTry end` can be a short hand for this
function try(::typeof(get), dict::AbstractDict, key)
if haskey(dict, key)
return Ok(dict[key])
else
return Err(KeyError(key))
end
end However, there are cases where it's not useful to call Overall, I'm neutral on whether or not using APIs like Meanwhile, I think it'd be great if other people can share their thoughts on Footnotes
|
-1 for
|
There have been discussions on API for recoverable failures #41966 #34821 #44397 albeit case-by-case basis. By "recoverable," it means that the caller program expects certain failure modes (which is documented as public API of the callee) and thus the author of the caller program is willing to handle "success" and "failure" paths efficiently. We need a basic protocol for such APIs since the compiler is not very eager on optimizing exception-based error handling 1 (and, as a result, e.g., it is hard to use in constrained run-time environments such as GPU devices). I suggest looking at these issues as a whole and trying to come up with a principled solution. This was also discussed in #43773 but no concrete solution was proposed. This issue discusses a two-part RFC that is aiming at landing a concrete solution.
The first part of the RFC is to create simple wrapper types like
Some
but conveys "success" and "failure" semantics in some sense. Namely, I suggest addingA lot of interesting mechanisms can be built around them that may even require changing the
struct
itself (e.g., Try.jl). To maximize what's possible in 1.x time-frame, I suggest only exporting abstract types fromBase
and putting concretestruct
s inBase.Experimental
.The second part of the RFC is to add some
Union{Ok,Err}
-valued functions to address issues like #41966, #34821, and #44397. Some possible APIs include:tryget(dict, key)
Ok(key′ => value)
2Err(KeyError(key))
trysetwith!(f, dict, key)
Ok(key′ => f())
2Ok(key′ => dict[key])
2tryset!(dict, key, value)
Ok(key′ => value′)
2Ok(key′ => dict[key])
2tryput!(xs, x)
Ok(x)
Err(ClosedError(xs))
etc.trytake!(xs)
Ok(x)
Err(ClosedError(xs))
etc.tryfetch(xs)
Ok(x)
Err(ClosedError(xs))
etc.trypop!(xs)
Ok(x)
Err(EmptyError(xs))
etc.trypopfirst!(xs)
Ok(x)
Err(EmptyError(xs))
etc.trypush!(xs, x)
Ok(x′)
(Ok(xs)
?) 2Err(ClosedError(xs))
etc.trypushfirst!(xs, x)
Ok(x′)
(Ok(xs)
?) 2Err(ClosedError(xs))
etc.trygetonly(xs)
Ok(xs)
Err(EmptyError(xs))
(Concrete implementations of
tryget(dict, key)
,trysetwith!(f, dict, key)
, andtryset!(dict, key, value)
3 can be found in PreludeDicts.jl. Some of other functions are implemented in TryExperimental.jl.)I suggest promoting
try
prefix for this "calling convention." This is similar to the!
suffix convention that hints mutation in some sense.Of course, we already have a couple of functions with
try
prefix in Base (but fortunately not in stdlib) APIs:We can leave them as-is but it may also be reasonable to introduce and promote aliases (as we obviously cannot remove them during 1.x time frame). For example,
trylock
could be calledracylock
orlock_nowait
. ConcurrentUtils.jl usesrace_
prefix for racy and non-deterministic APIs in general including trylock. That said, since trylock is a pretty standard terminology, I understand that people may want to keep it.tryparse
could be renamed tomaybeparse
. However, rather than simple rename, it may be better to clean up the API so that it is possible to support, e.g.,maybeparse(Union{Int,Nothing}, "nothing")::Union{Nothing,Some{<:Union{Int,Nothing}}} == Some(nothing)
#43746.The "exit condition" of the RFC is not implementing the above APIs in the table. Rather, I think it'd be a good idea to start with one of #41966, #34821, or #44397. However, it seems to be a better idea to try arriving at a naming convention before tackling individual problems. That said, I think it is also a reasonable end result to decide not using any naming scheme for this type of API.
In summary, I think it'd be great if we can decide (at least):
Ok
andErr
types inBase
or notFootnotes
For more discussion on the co-existence of two error handling mechanisms, I included a section in Try.jl README: When to
throw
? When toreturn
? ↩′
indicates that the returned value include the object that is actually stored in the container. For example,tryset!(dict, key, value)
may returnOk(key′ => value′)
withkey !== key′
andvalue !== value′
ifkey
andvalue
areconvert
ed. ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7Note that
tryset!(dict, key, value)
/trysetwith!(f, dict, key)
are the "failable" counter parts ofget!(dict, key, value)
/get!(f, dict, key)
respectively. I swappedget
withset
since (1) it clarifies the implication on the failure path and (2) adding thewith
suffix as inmergewith!
is more natural. ↩The text was updated successfully, but these errors were encountered: