From 4b4e023aca90290d6cdc2c6aa0a7053acde64174 Mon Sep 17 00:00:00 2001 From: Takafumi Arakaki Date: Fri, 7 Jan 2022 19:51:12 -0500 Subject: [PATCH] Success/failure path elimination --- README.md | 36 ++++++++++++++++++++++++++++-------- docs/Project.toml | 1 + src/Try.jl | 3 +++ src/base.jl | 3 +++ test/Project.toml | 1 + test/TryTests/Project.toml | 1 + 6 files changed, 37 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 6b867c3..00e262a 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,25 @@ mymap(x -> x + 1, (x for x in 1:5 if isodd(x))) 6 ``` +### Success/failure path elimination + +Function using Try.jl for error handling (such as `Try.first`) typically has a +return type of `Union{Ok,Err}`. Thus, the compiler can sometimes prove that +success or failure paths can never be taken: + +```julia +julia> using Try, InteractiveUtils + +julia> @code_typed(Try.first((111, "two", :three)))[2] # always succeeds for non empty tuples +Ok{Int64} + +julia> @code_typed(Try.first(()))[2] # always fails for an empty tuple +Err{BoundsError} + +julia> @code_typed(Try.first(Int[]))[2] # both are possible for an array +Union{Ok{Int64}, Err{BoundsError}} +``` + ## Discussion Julia is a dynamic language with a compiler that can aggressively optimize away @@ -199,14 +218,15 @@ to manually compute the type of the untaken paths. This is tedious and sometimes simply impossible. This is also not idiomatic Julia code which typically delegates output type computation to the compiler. Futhermore, the benefit of type-stabilization is at the cost of loosing the opportunity for the -compiler to eliminate the success and/or failure branches. A similar -optimization can still happen in principle with the concrete `struct` approach -with the combination of (post-inference) inlining, scalar replacement of -aggregate, and dead code elimination. However, since type inference is the main -driving force in the inter-procedural analysis and optimization in the Julia -compiler, `Union` return type is likely to continue to be the most effective way -to communicate the intent of the code with the compiler (e.g., if a function -call always succeeds, always return an `Ok{T}`). +compiler to eliminate the success and/or failure branches (see [Success/failure +path elimination](#success-failure-path-elimination)). A similar optimization +can still happen in principle with the concrete `struct` approach with the +combination of (post-inference) inlining, scalar replacement of aggregate, and +dead code elimination. However, since type inference is the main driving force +in the inter-procedural analysis and optimization in the Julia compiler, `Union` +return type is likely to continue to be the most effective way to communicate +the intent of the code with the compiler (e.g., if a function call always +succeeds, always return an `Ok{T}`). (That said, Try.jl also contains supports for concretely-typed returned value when `Union` is not appropriate. This is for experimenting if such a manual diff --git a/docs/Project.toml b/docs/Project.toml index dfa65cd..f1982f8 100644 --- a/docs/Project.toml +++ b/docs/Project.toml @@ -1,2 +1,3 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" diff --git a/src/Try.jl b/src/Try.jl index 1565c6a..4bb1e5e 100644 --- a/src/Try.jl +++ b/src/Try.jl @@ -112,6 +112,9 @@ end # module Internal @function getindex @function setindex! +@function first +@function last + @function push! @function pushfirst! @function pop! diff --git a/src/base.jl b/src/base.jl index c21c1ce..d52e2eb 100644 --- a/src/base.jl +++ b/src/base.jl @@ -60,6 +60,9 @@ Try.getindex(dict::AbstractDict, k1, k2, ks...) = Try.getindex(dict, (k1, k2, ks Try.setindex!(dict::AbstractDict, v, k1, k2, ks...) = Try.setindex!(dict, v, (k1, k2, ks...)) +Try.first(xs) = Try.getindex(xs, 1) +Try.last(xs) = Try.getindex(xs, lastindex(xs)) + Try.pop!(a::Vector)::Result = isempty(a) ? Causes.empty(a) : Ok(pop!(a)) Try.popfirst!(a::Vector)::Result = isempty(a) ? Causes.empty(a) : Ok(popfirst!(a)) diff --git a/test/Project.toml b/test/Project.toml index 21320b8..bb44141 100644 --- a/test/Project.toml +++ b/test/Project.toml @@ -1,4 +1,5 @@ [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" TestFunctionRunner = "792026f5-ac9a-4a19-adcb-47b0ce2deb5d" diff --git a/test/TryTests/Project.toml b/test/TryTests/Project.toml index f988d9f..220bf53 100644 --- a/test/TryTests/Project.toml +++ b/test/TryTests/Project.toml @@ -5,6 +5,7 @@ version = "0.1.0-DEV" [deps] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" +InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240" Test = "8dfed614-e22c-5e08-85e1-65c5234f0b40" Try = "bf1d0ff0-c4a9-496b-85f0-2b0d71c4f32a"