Skip to content

Commit a2dbc9a

Browse files
further changes
1 parent 20a3cbe commit a2dbc9a

9 files changed

Lines changed: 261 additions & 38 deletions

File tree

src/ExtensibleEffects.jl

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,35 @@ using TypeClasses
1717
@reexport using DataTypesBasic
1818
export @pure # users need @pure for the monadic syntax @syntax_eff
1919

20+
include("utils.jl")
2021
include("core.jl")
2122
include("effecthandler.jl")
2223
include("outereffecthandler.jl")
2324
include("autorun.jl")
2425
include("syntax.jl")
2526
include("instances.jl")
2627

28+
29+
# Type Inference Workarounds
30+
# --------------------------
31+
32+
# TODO unfortunately this does not work yet for everything
33+
# Cthulhu still tells that inference failed because of recursion
34+
35+
# define recursion_relation so that inference works better
36+
# we probably have problems with true infinite cases now, but that should be still
37+
# better in total
38+
39+
# It is a workaround this issue https://github.com/JuliaLang/julia/issues/45759
40+
41+
recursion_relation_always_true(@nospecialize(_...)) = true
42+
43+
for f in (TypeClasses.flatmap, eff_flatmap, map, Eff)
44+
for m in methods(f)
45+
if Base.moduleroot(m.module) === ExtensibleEffects
46+
m.recursion_relation = recursion_relation_always_true
47+
end
48+
end
49+
end
50+
2751
end # module

src/autorun.jl

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,10 @@ function _autorun(handlers, eff::Eff)
6767
end
6868

6969
"""
70-
eff_autohandler(value) = Base.typename(typeof(value)).wrapper
70+
eff_autohandler(value) = type of value
7171
7272
Overwrite this if the default autohandler extraction does not work for your case.
7373
E.g. for `value::Vector` the default would return `Array`, hence we need to overwrite it individually.
7474
"""
75-
eff_autohandler(value) = Base.typename(typeof(value)).wrapper
75+
eff_autohandler(value) = TypeVal{Base.typename(typeof(value)).wrapper}()
76+
# we wrap the type into a TypeVal as Type values have limited support for specialization

src/core.jl

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -12,25 +12,26 @@ only for internal purposes, captures the still unevaluated part of an Eff
1212
"""
1313
struct Continuation{Fs}
1414
functions::Fs
15-
Continuation(functions...) = new{typeof(functions)}(functions)
15+
# specialization on vararg `...` is triggered by explicit ::Vararg type annotation
16+
Continuation(functions::Vararg{Any,N}) where {N} = new{typeof(functions)}(functions)
1617
end
1718

1819
"""
1920
central data structure which can capture Effects in a way that they can interact, while each
2021
is handled independently on its own
2122
"""
22-
struct Eff{Effectful, Fs}
23+
struct Eff{Effectful,Fs}
2324
effectful::Effectful
2425
cont::Continuation{Fs}
2526
end
2627

27-
function Eff(effectful::E, cont::Continuation{Tuple{}}) where E<:NoEffect
28+
function Eff(effectful::E, cont::Continuation{Tuple{}}) where {E<:NoEffect}
2829
# also run this if isempty(cont) to stop infinite recursion
2930
# (which happens otherwise because empty cont results returns noeffect for convenience)
30-
Eff{E, Tuple{}}(effectful, cont)
31+
Eff{E,Tuple{}}(effectful, cont)
3132
end
3233

33-
function Eff(effectful::NoEffect, cont::Continuation{Fs}) where Fs
34+
function Eff(effectful::NoEffect, cont::Continuation{Fs}) where {Fs}
3435
# Evaluate NoEffect directly to make sure, we don't have a chain of NoEffect function accumulating
3536
# e.g. with ContextManager this could lead to things not being evaluated, while the syntax suggest
3637
# everything is evaluated, and hence the ContextManager may finalize resource despite they are still used.
@@ -79,5 +80,13 @@ Base.map(f, c::Continuation) = Continuation(c.functions..., noeffect ∘ f)
7980
# -----------------------
8081

8182
TypeClasses.pure(::Type{<:Eff}, a) = noeffect(a)
82-
TypeClasses.map(f, eff::Eff) = TypeClasses.flatmap(noeffect f, eff)
83-
TypeClasses.flatmap(f, eff::Eff) = Eff(eff.effectful, Continuation(eff.cont.functions..., f))
83+
84+
# we use @generated functions to force inlining of the definition
85+
# which prevents bad type inference on recursive calls
86+
function TypeClasses.map(f::F, eff::Eff) where {F}
87+
Eff(eff.effectful, Continuation(eff.cont.functions..., noeffect f))
88+
end
89+
90+
function TypeClasses.flatmap(f::F, eff::Eff) where {F}
91+
Eff(eff.effectful, Continuation(eff.cont.functions..., f))
92+
end

src/effecthandler.jl

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using SimpleMatch: @match
2+
13
"""
24
runhandlers(handlers, eff)
35
runhandlers((Vector, Option), eff)::Vector{Option{...}}
@@ -32,13 +34,15 @@ end
3234
like `ExtensibleEffects.runlast`, however if the Eff is not yet completely handled, it just returns it.
3335
"""
3436
function runlast_ifpossible(final::Eff)
35-
if isempty(final.cont) && final.effectful isa NoEffect
36-
final.effectful.value
37-
else
38-
final
37+
@match(final.cont, final.effectful) do f
38+
f(::Continuation{Tuple{}}, effectful::NoEffect) = effectful.value
39+
f(_, _) = final
3940
end
4041
end
4142

43+
# we reinterpret Types as TypeVal typevariables in order to automatically
44+
# specialize functions on these types
45+
runhandler(::Type{Handler}, eff::Eff) where Handler = runhandler(TypeVal{Handler}(), eff)
4246

4347
"""
4448
runhandler(handler, eff::Eff)
@@ -49,11 +53,11 @@ key method to run an effect on some effecthandler Eff
4953
note that we represent effectrunners as plain types in order to associate
5054
standard effect runners with types like Vector, Option, ...
5155
"""
52-
function runhandler(handler, eff::Eff)
56+
function runhandler(handler, eff::Eff) # this weird type parameter is needed because of https://discourse.julialang.org/t/need-help-on-understanding-type-inference-performance/80600
5357
eff_applies(handler, eff.effectful) || return runhandler_not_applies(handler, eff)
5458

5559
interpreted_continuation = if isempty(eff.cont)
56-
# you may think we could simplify this, however for eff `eff_flatmap(handler, x -> eff_pure(handler, x), eff) != eff`
60+
# you may think we could simplify this, however for eff `eff_flatmap(handler, x -> eff_pure(handler, x), eff) != eff`
5761
# because there is the handler which may have extra information
5862
Continuation(x -> _eff_pure(handler, x))
5963
else
@@ -62,10 +66,11 @@ function runhandler(handler, eff::Eff)
6266
_eff_flatmap(handler, interpreted_continuation, eff.effectful)
6367
end
6468

69+
6570
"""
6671
runhandler_not_applies(handler, eff)
6772
68-
if your handler does not apply, use this as the fallback to handle the unknown effect.
73+
if your handler does not apply, use this as the fallback to handle the unknown effect.
6974
"""
7075
function runhandler_not_applies(handler, eff::Eff)
7176
interpreted_continuation = if isempty(eff.cont)
@@ -78,6 +83,7 @@ function runhandler_not_applies(handler, eff::Eff)
7883
Eff(eff.effectful, interpreted_continuation)
7984
end
8085

86+
8187
"""
8288
@runhandlers handlers eff
8389
@@ -138,6 +144,9 @@ i.e. assuming the effect is handled afterwards.
138144
"""
139145
eff_flatmap(handler, interpreted_continuation, value) = eff_flatmap(interpreted_continuation, value)
140146

147+
# extra support for Type handlers
148+
eff_flatmap(::TypeVal{Handler}, interpreted_continuation, value) where Handler = eff_flatmap(Handler, interpreted_continuation, value)
149+
141150

142151
# eff_pure
143152
# --------
@@ -155,6 +164,9 @@ Plain values will be wrapped with `noeffect` automatically.
155164
"""
156165
function eff_pure end
157166

167+
# extra support for Type handlers
168+
eff_pure(::TypeVal{Handler}, value) where Handler = eff_pure(Handler, value)
169+
158170

159171
# eff_applies
160172
# -----------
@@ -171,3 +183,6 @@ which are captured in the handler type `CallableHandler(args, kwargs)`.
171183
Hence above you would choose YourHandlerType = `CallableHandler`, and ToBeHandledEffectType = `Callable`.
172184
"""
173185
eff_applies(handler, value) = false
186+
187+
# extra support for Type handlers
188+
eff_applies(::TypeVal{Handler}, value) where Handler = eff_applies(Handler, value)

src/instances.jl

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,30 @@ function ExtensibleEffects.eff_flatmap(continuation, a)
1717
eff_of_a
1818
end
1919

20+
# Try to improve performance by merging map and flip_types
21+
function eff_flatmap_iteration(continuation::F, iter::T) where {F, T}
22+
first = iterate(iter)
23+
if first === nothing
24+
# only in this case we actually need `pure(eltype(T))` and `neutral(T)`
25+
# for non-empty sequences everything works for Types without both
26+
# TODO include continuation
27+
pure(T, neutral(eltype(T)))
28+
else
29+
_b, state = first
30+
b = continuation(_b)
31+
# we need to abstract out details so that combine can actually work
32+
# note that because of its definition, pure(ABC, x) == pure(A, x)
33+
# start = feltype_unionall_implementationdetails(fmap(traitsof, a -> pure(traitsof, T, a), x)) # we can only combine on S
34+
start = map(c -> pure(T, c), b) # we can only combine on ABC
35+
Base.foldl(Iterators.rest(iter, state); init = start) do acc, _b
36+
b = continuation(_b)
37+
mapn(acc, b) do acc′, c # working in applicative context B
38+
acc′ pure(T, c) # combining on T
39+
end
40+
end
41+
end
42+
end
43+
2044
# NoEffect
2145
# --------
2246
ExtensibleEffects.eff_applies(handler::Type{<:NoEffect}, effectful::NoEffect) = true
@@ -38,7 +62,7 @@ ExtensibleEffects.eff_flatmap(continuation, effectful::Identity) = continuation(
3862
# -----
3963
ExtensibleEffects.eff_applies(handler::Type{<:Const}, effectful::Const) = true
4064
# usually Const does not have a pure, however within Eff, it is totally fine,
41-
# as continuations on Const never get evaluated anyways,
65+
# as continuations on Const never get evaluated anyways,
4266
# (and eff_pure is only called at the very end, when literal values are reached)
4367
ExtensibleEffects.eff_pure(handler::Type{<:Const}, value) = value
4468
ExtensibleEffects.eff_flatmap(continuation, effectful::Const) = effectful
@@ -305,7 +329,7 @@ ExtensibleEffects.eff_pure(handler::StateHandler, value) = (value, handler.state
305329
# We need to define our own runhandler instead. It is a bit more complex, but still straightforward and compact.
306330
function ExtensibleEffects.runhandler(handler::StateHandler, eff::Eff)
307331
eff_applies(handler, eff.effectful) || return runhandler_not_applies(handler, eff)
308-
332+
309333
value, nextstate = eff.effectful(handler.state)
310334
nexthandler = StateHandler(nextstate)
311335
if isempty(eff.cont)

src/utils.jl

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
struct TypeVal{T}
2+
TypeVal{T}() where T = new{T}()
3+
end
4+
5+
# needed because of https://discourse.julialang.org/t/need-help-on-understanding-type-inference-performance/80600/5
6+
macro specializetype(var, expr)
7+
var = esc(var)
8+
expr = esc(expr)
9+
quote
10+
let
11+
f(::Type{$var}) where $var = $expr
12+
f($var) = $expr
13+
f($var)
14+
end
15+
end
16+
end

test/benchmarks.jl

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
using ExtensibleEffects
2-
using ExtensibleEffects: runhandler, Continuation, Eff, _eff_pure, @specializetype, myeff_flatmap, myeff_flatmap2
2+
using ExtensibleEffects: runhandler, Continuation, Eff, _eff_pure, @specializetype, myeff_flatmap #, myeff_flatmap2
33
using TypeClasses
44
using BenchmarkTools
55
using JET
@@ -19,11 +19,11 @@ runhandlers(Vector, program1)
1919

2020
function test(handler, eff)
2121
continuation = if isempty(eff.cont)
22-
# you may think we could simplify this, however for eff `eff_flatmap(handler, x -> eff_pure(handler, x), eff) != eff`
22+
# you may think we could simplify this, however for eff `eff_flatmap(handler, x -> eff_pure(handler, x), eff) != eff`
2323
# because there is the handler which may have extra information
24-
@specializetype handler Continuation(x -> ExtensibleEffects._eff_pure(handler, x))
24+
Continuation(x -> ExtensibleEffects._eff_pure(handler, x))
2525
else
26-
@specializetype handler Continuation(x -> ExtensibleEffects.runhandler(handler, eff.cont(x)))
26+
Continuation(x -> ExtensibleEffects.runhandler(handler, eff.cont(x)))
2727
end
2828

2929
a = eff.effectful
@@ -58,7 +58,7 @@ eff_mark_eltype(eltype, eff) = EffWithEltype2{eltype, typeof(eff)}(eff)
5858
eff_mark_eltype(::Type{eltype}) where eltype = eff -> eff_mark_eltype(eltype, eff)
5959

6060
Base.IteratorEltype(::EffWithEltype2) = Iterators.HasEltype()
61-
Base.eltype(::EffWithEltype2{ElType}) where ElType = ElType
61+
Base.eltype(::EffWithEltype2{ElType}) where ElType = ElType
6262

6363
@code_warntype eff_mark_eltype(Vector, eff)
6464
@code_warntype map(eff_mark_eltype(Vector), a_of_eff_of_a)
@@ -121,7 +121,7 @@ function _foldl_3(op::OP, init, itr) where {OP}
121121
y = iterate(itr)
122122
y === nothing && return init
123123
v = op(init, y[1])
124-
124+
125125
y = iterate(itr, y[2])
126126
y === nothing && return v
127127
v = op(v, y[1])
@@ -194,7 +194,7 @@ function test3(::Type{T}, a1, a2) where T
194194
#= /home/ssahm/.julia/dev/TypeClasses/src/TypeClasses/FunctorApplicativeMonad.jl:167 =#
195195
a2 -> func(a1, a2)
196196
end
197-
197+
198198
ap1 = map(curried_func, a1)
199199
ap2 = a2
200200

@@ -217,15 +217,15 @@ function test3(::Type{T}, a1, a2) where T
217217
TypeClasses.flatmap(f_flatmap, ap1)
218218

219219
myeff_flatmap(f_flatmap, ap1)
220-
220+
221221
# myflatmap2(f_flatmap, ap1)
222-
222+
223223

224224
# myflatmap(f, eff::Eff) = Eff(eff.effectful, Continuation(eff.cont.functions..., f))
225225
# myflatmap(f_flatmap, ap1)
226-
227-
228-
226+
227+
228+
229229
# eff = ap1
230230
# f = f_flatmap
231231
# Eff(eff.effectful, Continuation(eff.cont.functions..., f))
@@ -263,7 +263,7 @@ test2(ap1, ap2) = TypeClasses.flatmap(ap1) do f
263263
f(a)
264264
end
265265
end
266-
@code_warntype test2(ap1, ap2)
266+
@code_warntype test2(ap1, ap2)
267267

268268
create_fflatmap(ap2) = f -> TypeClasses.map(ap2) do a
269269
f(a)
@@ -363,7 +363,7 @@ mapn_code(2)
363363

364364

365365

366-
singleton_array(T::Type{<:AbstractArray}, a) = convert(T, [a])
366+
singleton_array(T::Type{<:AbstractArray}, a) = convert(T, [a])
367367

368368
struct ChainedFunctions{Fs}
369369
functions::Fs

0 commit comments

Comments
 (0)