-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy patheffecthandler.jl
More file actions
171 lines (133 loc) · 5.59 KB
/
effecthandler.jl
File metadata and controls
171 lines (133 loc) · 5.59 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
"""
runhandlers(handlers, eff)
runhandlers((Vector, Option), eff)::Vector{Option{...}}
run all handlers such that the first handler will define the most outer container
"""
runhandlers(single_handler, eff::Eff) = runlast_ifpossible(runhandler(single_handler, eff))
runhandlers(all_handlers::Vector, eff::Eff) = runhandlers(tuple(all_handlers...), eff)
runhandlers(all_handlers::Tuple, eff::Eff) = runlast_ifpossible(_runhandlers(all_handlers, eff))
_runhandlers(all_handlers::Tuple{}, eff::Eff) = eff
function _runhandlers(all_handlers::Tuple, eff::Eff)
subresult = _runhandlers(Base.tail(all_handlers), eff)
runhandler(first(all_handlers), subresult)
end
runhandlers(any, not_eff) = error("can only apply runhandlers onto an `Eff`, got a `$(typeof(not_eff))`")
"""
runlast(eff::Eff)
extract final value from Eff with all effects (but Identity) already run
"""
function runlast(final::Eff)
@assert isempty(final.cont) "expected eff without continuation, but found cont=$(final.cont)"
@assert final.effectful isa NoEffect "not all effects have been handled, found $(final.value)"
final.effectful.value
end
"""
runlast_ifpossible(eff::Eff)
like `ExtensibleEffects.runlast`, however if the Eff is not yet completely handled, it just returns it.
"""
function runlast_ifpossible(final::Eff)
ifemptyelse(
final.cont,
final.effectful isa NoEffect ? final.effectful.value : final,
final
)
end
"""
runhandler(handler, eff::Eff)
runhandler(handler, eff::Eff, context)
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)
eff_applies(handler, eff.effectful) || return runhandler_not_applies(handler, eff)
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)),
Continuation(x -> runhandler(handler, eff.cont(x)))
)
_eff_flatmap(handler, interpreted_continuation, eff.effectful)
end
"""
runhandler_not_applies(handler, eff)
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 = 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)
end
"""
@runhandlers handlers eff
For convenience we provide `runhandlers` function also as a macro.
With this you can easier run left-over handlers from an `@syntax_eff` autorun.
Example
-------
```
@runhandlers WithCall(args, kwargs) @syntax_eff begin
a = Callable(x -> 2x)
@pure a
end
```
"""
macro runhandlers(handlers, eff)
esc(:(ExtensibleEffects.runhandlers($handlers, $eff)))
end
# effecthandler interface
# =======================
# to support an effect, a type needs to implement three functions
# 1. eff_flatmap
# 2. eff_pure
# 3. eff_applies
# eff_flatmap
# -----------
function _eff_flatmap(handler, interpreted_continuation::Continuation, value)
# provide convenience wrapper if someone forgets to return an Eff
result = eff_flatmap(handler, interpreted_continuation, value)
noeffect(result)
end
"""
ExtensibleEffects.eff_flatmap(handler, interpreted_continuation, value)
ExtensibleEffects.eff_flatmap(interpreted_continuation, value)
Overwrite this for your custom effect handler to handle your effect.
This function is only called if `eff_applies(handler, value)==true`.
While for custom effects it is handy to dispatch on the handler itself, in simple cases
`handler == typeof(value)` and hence, we allow to ommit it.
Parameters
----------
The arg `interpreted_continuation` is guaranteed to return an Eff of the handled type.
E.g. if you might handle the type `Vector`, you are guaranteed that `interpreted_continuation(x)::ExtensibleEffects.Eff{Vector}`
Return
------
If you do not return an `ExtensibleEffects.Eff`, the result will be wrapped into `noeffect` automatically,
i.e. assuming the effect is handled afterwards.
"""
eff_flatmap(handler, interpreted_continuation, value) = eff_flatmap(interpreted_continuation, value)
# eff_pure
# --------
function _eff_pure(handler::H, value) where {H}
result = eff_pure(handler, value)
noeffect(result)
end
"""
ExtensibleEffects.eff_pure(handler, value)
Overwrite this for your custom effect handler, return either `ExtensibleEffects.Eff` type, or a plain value.
Plain values will be wrapped with `noeffect` automatically.
"""
function eff_pure end
# eff_applies
# -----------
"""
ExtensibleEffects.eff_applies(handler::YourHandlerType, value::ToBeHandledEffectType) = true
Overwrite this function like above to indicate that a concrete effect is handled by a handler.
In most cases you will have `YourHandlerType = Type{ToBeHandledEffectType}`, like for `Vector` or similar.
Sometimes you need extra information without which you cannot run a specific effect. Then you need to link
the specific handler containing the required information. E.g. `Callable` needs `args` and `kwargs` to be run,
which are captured in the handler type `CallableHandler(args, kwargs)`.
Hence above you would choose YourHandlerType = `CallableHandler`, and ToBeHandledEffectType = `Callable`.
"""
eff_applies(handler, value) = false