Skip to content

Commit

Permalink
Success/failure path elimination
Browse files Browse the repository at this point in the history
  • Loading branch information
tkf committed Jan 8, 2022
1 parent c497dc7 commit 4b4e023
Show file tree
Hide file tree
Showing 6 changed files with 37 additions and 8 deletions.
36 changes: 28 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions docs/Project.toml
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
[deps]
Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4"
InteractiveUtils = "b77e0a4c-d291-57a0-90e8-8db25a27a240"
3 changes: 3 additions & 0 deletions src/Try.jl
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ end # module Internal
@function getindex
@function setindex!

@function first
@function last

@function push!
@function pushfirst!
@function pop!
Expand Down
3 changes: 3 additions & 0 deletions src/base.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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))

Expand Down
1 change: 1 addition & 0 deletions test/Project.toml
Original file line number Diff line number Diff line change
@@ -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"
1 change: 1 addition & 0 deletions test/TryTests/Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down

0 comments on commit 4b4e023

Please sign in to comment.