From 214ad69dcf78d241a74ccf04e62122387dbd8c72 Mon Sep 17 00:00:00 2001 From: Chad Scherrer Date: Fri, 3 Feb 2023 10:04:26 -0800 Subject: [PATCH 1/3] improve type stability --- src/autorun.jl | 16 +++++++-------- src/core.jl | 46 ++++++++++++++++++++++++++++++++------------ src/effecthandler.jl | 28 +++++++++++++-------------- src/instances.jl | 10 +++++----- 4 files changed, 60 insertions(+), 40 deletions(-) diff --git a/src/autorun.jl b/src/autorun.jl index 9507c1f..83a0c9e 100644 --- a/src/autorun.jl +++ b/src/autorun.jl @@ -44,11 +44,12 @@ function _autorun(handlers, eff::Eff) # The same we want to happen if we found a handler which cannot be handled automatically. # We just skip it and leave it for later handling. - interpreted_continuation = if isempty(eff.cont) - Continuation() - else + + interpreted_continuation = ifemptyelse(eff.cont, + Continuation(), Continuation(x -> _autorun(handlers, eff.cont(x))) - end + ) + # unwrap NoAutoRun if found value = eff.effectful isa NoAutoRun ? eff.effectful.value : eff.effectful Eff(value, interpreted_continuation) @@ -57,11 +58,10 @@ function _autorun(handlers, eff::Eff) # recursing into runhandler on the interpreted_continuation, we want to handle all yet unseen nested handlers. # We do this by first calling `_autorun` before calling `runhandler` within the interpreted_continuation. handlers_new = (handler, handlers...) - interpreted_continuation = if isempty(eff.cont) - Continuation(x -> _eff_pure(handler, x)) - else + interpreted_continuation = ifemptyelse(eff.cont, + Continuation(x -> _eff_pure(handler, x)), Continuation(x -> runhandler(handler, _autorun(handlers_new, eff.cont(x)))) - end + ) _eff_flatmap(handler, interpreted_continuation, eff.effectful) end end diff --git a/src/core.jl b/src/core.jl index 982d9f6..675e215 100644 --- a/src/core.jl +++ b/src/core.jl @@ -12,7 +12,20 @@ only for internal purposes, captures the still unevaluated part of an Eff """ struct Continuation{Fs} functions::Fs - Continuation(functions...) = new{typeof(functions)}(functions) + # Continuation(functions...) = new{typeof(functions)}(functions) + Continuation() = new{Tuple{}}(()) + Continuation(fs::T) where {T<:Tuple} = new{T}(fs) + Continuation(f::F) where {F} = new{Tuple{Core.Typeof(f)}}((f,)) +end + +function ifemptyelse(c::Continuation{Tuple{}}, p, q) + @assert isempty(c) + p +end + +function ifemptyelse(c::Continuation, p, q) + @assert !isempty(c) + q end """ @@ -23,8 +36,13 @@ struct Eff{Effectful, Fs} effectful::Effectful cont::Continuation{Fs} + function Eff(effectful::T, cont::Continuation{Tuple{}}) where {T} + # also run this if isempty(cont) to stop infinite recursion + # (which happens otherwise because empty cont results returns noeffect for convenience) + new{T, Tuple{}}(effectful, cont) + end function Eff(effectful::T, cont::Continuation{Fs}) where {T, Fs} - if !isempty(cont) && effectful isa NoEffect + if effectful isa NoEffect # Evaluate NoEffect directly to make sure, we don't have a chain of NoEffect function accumulating # e.g. with ContextManager this could lead to things not being evaluated, while the syntax suggest # everything is evaluated, and hence the ContextManager may finalize resource despite they are still used. @@ -37,6 +55,7 @@ struct Eff{Effectful, Fs} end end end + Eff(effectful) = Eff(effectful, Continuation()) function Base.show(io::IO, eff::Eff) @@ -60,18 +79,21 @@ effect(eff::Eff) = eff # if we find a Eff effect, we just directly use it # Functionalities for Continuation # -------------------------------- -function (c::Continuation)(value) - if isempty(c) +function (c::Continuation{Tuple{}})(value) noeffect(value) - else - first_func = c.functions[1] - rest = c.functions[2:end] +end + +function (c::Continuation)(value) + first_func = first(c.functions) + rest = Base.tail(c.functions) eff = first_func(value) - Eff(eff.effectful, Continuation(eff.cont.functions..., rest...)) - end + Eff(eff.effectful, Continuation((eff.cont.functions..., rest...))) end -Base.isempty(c::Continuation) = Base.isempty(c.functions) -Base.map(f, c::Continuation) = Continuation(c.functions..., noeffect ∘ f) + +Base.isempty(c::Continuation{Tuple{}}) = true +Base.isempty(::Continuation) = false + +Base.map(f, c::Continuation) = Continuation((c.functions..., noeffect ∘ f)) # Functionalities for Eff @@ -79,4 +101,4 @@ Base.map(f, c::Continuation) = Continuation(c.functions..., noeffect ∘ f) TypeClasses.pure(::Type{<:Eff}, a) = noeffect(a) TypeClasses.map(f, eff::Eff) = TypeClasses.flatmap(noeffect ∘ f, eff) -TypeClasses.flatmap(f, eff::Eff) = Eff(eff.effectful, Continuation(eff.cont.functions..., f)) \ No newline at end of file +TypeClasses.flatmap(f, eff::Eff) = Eff(eff.effectful, Continuation((eff.cont.functions..., f))) \ No newline at end of file diff --git a/src/effecthandler.jl b/src/effecthandler.jl index 54021c3..f70607a 100644 --- a/src/effecthandler.jl +++ b/src/effecthandler.jl @@ -32,11 +32,11 @@ end like `ExtensibleEffects.runlast`, however if the Eff is not yet completely handled, it just returns it. """ function runlast_ifpossible(final::Eff) - if isempty(final.cont) && final.effectful isa NoEffect - final.effectful.value - else + ifemptyelse( + final.cont, + final.effectful isa NoEffect ? final.effectful.value : final, final - end + ) end @@ -49,16 +49,16 @@ key method to run an effect on some effecthandler Eff note that we represent effectrunners as plain types in order to associate standard effect runners with types like Vector, Option, ... """ -function runhandler(handler, eff::Eff) +function runhandler(handler, eff::Eff) eff_applies(handler, eff.effectful) || return runhandler_not_applies(handler, eff) - interpreted_continuation = if isempty(eff.cont) + interpreted_continuation = ifemptyelse(eff.cont, # you may think we could simplify this, however for eff `eff_flatmap(handler, x -> eff_pure(handler, x), eff) != eff` # because there is the handler which may have extra information - Continuation(x -> _eff_pure(handler, x)) - else + Continuation(x -> _eff_pure(handler, x)), Continuation(x -> runhandler(handler, eff.cont(x))) - end + ) + _eff_flatmap(handler, interpreted_continuation, eff.effectful) end @@ -68,11 +68,9 @@ end if your handler does not apply, use this as the fallback to handle the unknown effect. """ function runhandler_not_applies(handler, eff::Eff) - interpreted_continuation = if isempty(eff.cont) - Continuation(x -> _eff_pure(handler, x)) - else - Continuation(x -> runhandler(handler, eff.cont(x))) - end + interpreted_continuation = ifemptyelse(eff.cont, + Continuation(x -> _eff_pure(handler, x)), + Continuation(x -> runhandler(handler, eff.cont(x)))) # if we don't know how to handle the current eff, we return it with the new continuation # this ensures the handler is applied recursively Eff(eff.effectful, interpreted_continuation) @@ -142,7 +140,7 @@ eff_flatmap(handler, interpreted_continuation, value) = eff_flatmap(interpreted_ # eff_pure # -------- -function _eff_pure(handler, value) +function _eff_pure(handler::H, value) where {H} result = eff_pure(handler, value) noeffect(result) end diff --git a/src/instances.jl b/src/instances.jl index 3d93d99..2c9d231 100644 --- a/src/instances.jl +++ b/src/instances.jl @@ -308,11 +308,10 @@ function ExtensibleEffects.runhandler(handler::StateHandler, eff::Eff) value, nextstate = eff.effectful(handler.state) nexthandler = StateHandler(nextstate) - if isempty(eff.cont) - _eff_pure(nexthandler, value) - else + ifemptyelse(eff.cont, + _eff_pure(nexthandler, value), runhandler(nexthandler, eff.cont(value)) - end + ) end """ @@ -328,11 +327,12 @@ macro runstate(expr) # Note, that we have to use `runhandlers` explicitly, such that other outer handlers using `@insert_into_runhandlers` # can interact well with this outer handler. esc(quote - let eff = $expr + let eff = $expr isa(eff, ExtensibleEffects.Eff) || error(""" `@runstate` only works for `Eff` type, got `$(typeof(eff))`. Try to use `@runstate` as you first outer handler, which is directly applied to the `Eff`. """) + @assert eff isa ExtensibleEffects.Eff ExtensibleEffects.State() do state ExtensibleEffects.runhandlers(ExtensibleEffects.StateHandler(state), eff) end From b95ded8cb93891311eb14e2d2d4e09378ecdb8cf Mon Sep 17 00:00:00 2001 From: Chad Scherrer Date: Sat, 6 May 2023 07:21:53 -0700 Subject: [PATCH 2/3] Update Project.toml --- Project.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Project.toml b/Project.toml index 65d2ca2..3df4c8c 100644 --- a/Project.toml +++ b/Project.toml @@ -16,10 +16,10 @@ TypeClasses = "addcc920-e0cf-11e8-30b7-0fb08706b574" Compat = "2.1, 3" DataTypesBasic = "1.0, 2" ExprParsers = "1" -julia = "1.6" Monadic = "1" Reexport = "1" TypeClasses = "1.1" +julia = "1.6" [extras] Documenter = "e30172f5-a6a5-5a46-863b-614d45cd2de4" From 8c05a6740b5e7da4de8d9d79276ba8eabd061bd0 Mon Sep 17 00:00:00 2001 From: Chad Scherrer Date: Sat, 6 May 2023 07:22:03 -0700 Subject: [PATCH 3/3] using ExtensibleEffects => using .ExtensibleEffects --- src/instances.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/instances.jl b/src/instances.jl index 2c9d231..aaec928 100644 --- a/src/instances.jl +++ b/src/instances.jl @@ -1,4 +1,4 @@ -using ExtensibleEffects +using .ExtensibleEffects using DataTypesBasic using TypeClasses using Distributed