Skip to content

Improve type stability #12

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
16 changes: 8 additions & 8 deletions src/autorun.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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
Expand Down
46 changes: 34 additions & 12 deletions src/core.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

"""
Expand All @@ -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.
Expand All @@ -37,6 +55,7 @@ struct Eff{Effectful, Fs}
end
end
end

Eff(effectful) = Eff(effectful, Continuation())

function Base.show(io::IO, eff::Eff)
Expand All @@ -60,23 +79,26 @@ 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
# -----------------------

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))
TypeClasses.flatmap(f, eff::Eff) = Eff(eff.effectful, Continuation((eff.cont.functions..., f)))
28 changes: 13 additions & 15 deletions src/effecthandler.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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


Expand All @@ -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

Expand All @@ -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)
Expand Down Expand Up @@ -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
Expand Down
12 changes: 6 additions & 6 deletions src/instances.jl
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
using ExtensibleEffects
using .ExtensibleEffects
using DataTypesBasic
using TypeClasses
using Distributed
Expand Down Expand Up @@ -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

"""
Expand All @@ -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
Expand Down