|
| 1 | +```@meta |
| 2 | +CurrentModule = ExtensibleEffects |
| 3 | +DocTestSetup = quote |
| 4 | + using ExtensibleEffects |
| 5 | +end |
| 6 | +``` |
| 7 | + |
| 8 | +# Introduction |
| 9 | + |
| 10 | +Welcome to `ExtensibleEffects.jl`. This package provides an implementation of Extensible Effects. We follow the approach presented in the paper [Freer Monads, More Extensible Effects](http://okmij.org/ftp/Haskell/extensible/more.pdf) which already has an [Haskell implementation](https://hackage.haskell.org/package/freer-effects) as well as a [Scala implementation](https://github.com/atnos-org/eff). |
| 11 | + |
| 12 | +This Julia implementation is massively simplified, and hence can also serve as a good introduction to get to know the details behind Extensible Effects. |
| 13 | + |
| 14 | +Many effects are provided, ranging from `Option`, which can be handled very simple, to the very limit of what can be supported by ExtensibleEffects - the `State` effect. Still, all the implementations are short and easy to follow, so look into the `instances.jl` file to see how to write your own Effect handlings. |
| 15 | + |
| 16 | +## Installation |
| 17 | + |
| 18 | +```julia |
| 19 | +using Pkg |
| 20 | +pkg"add ExtensibleEffects" |
| 21 | +``` |
| 22 | + |
| 23 | +Use it like |
| 24 | + |
| 25 | +```julia |
| 26 | +using ExtensibleEffects |
| 27 | +``` |
| 28 | + |
| 29 | +## Usage |
| 30 | + |
| 31 | +The power of ExtensibleEffects.jl is to combine multiple different contexts into one *composable* super context abstraction. Its key macro is |
| 32 | +```julia |
| 33 | +@syntax_eff begin |
| 34 | + a = an_effect |
| 35 | + b = another_effect |
| 36 | + @pure a, b |
| 37 | +end |
| 38 | +``` |
| 39 | +which provides a syntax similar to `TypeClasses.@syntax_flatmap` for working seamlessly with effects. See its documentation [`@syntax_eff`](@ref) for more details. |
| 40 | + |
| 41 | +There is another version of the key macro called [`@syntax_eff_noautorun`](@ref), which as the name indicates disables the autorun feature of `@syntax_eff`. You may need this in case you don't want to execute your effects immediately, but staying in the meta-monad `ExtensibleEffects.Eff` for further composition with other effectful algorithms. [Effects](@ref) and [How does it actually work?](@ref) also provide some examples of using `@syntax_eff_noautorun` for explanation purposes. |
| 42 | + |
| 43 | +You can also specifically disable the autorun feature for individual effects only by using the [`noautorun`](@ref) function like |
| 44 | +```julia |
| 45 | +@syntax_eff noautorun(Vector) begin |
| 46 | + ... |
| 47 | +end |
| 48 | +``` |
| 49 | +which in this case would not handle the `Vector` effect. |
| 50 | + |
| 51 | +---------------------- |
| 52 | + |
| 53 | +In addition to `@syntax_eff`, `@syntax_eff_noautorun` and `@syntax_eff noautorun(effect1, effect2, ...)` the package reexports all **data types** from [`DataTypesBasic.jl`](https://github.com/JuliaFunctional/DataTypesBasic.jl) and [`TypeClasses.jl`](https://github.com/JuliaFunctional/TypeClasses.jl) |
| 54 | + |
| 55 | +For the more complicated effects `Writer`, `Callable`, `ContextManager` and `State` extra handlers and further helper macros are provided. Take a look at [Example](@ref) and [Effects](@ref) for further details. |
| 56 | + |
| 57 | + |
| 58 | +## Example |
| 59 | + |
| 60 | +To start small, you can use `ExtensibleEffects.@syntax_eff` instead of `TypeClasses.@syntax_flatmap`. |
| 61 | + |
| 62 | +```jldoctest |
| 63 | +julia> @syntax_eff begin |
| 64 | + a = [1, 2] |
| 65 | + b = ["one", "two"] |
| 66 | + @pure a, b |
| 67 | + end |
| 68 | +4-element Vector{Tuple{Int64, String}}: |
| 69 | + (1, "one") |
| 70 | + (1, "two") |
| 71 | + (2, "one") |
| 72 | + (2, "two") |
| 73 | +
|
| 74 | +julia> option_example(n) = @syntax_eff begin |
| 75 | + a = Option(n) |
| 76 | + b = @Try isodd(a) ? error("fail") : a+1 |
| 77 | + @pure a, b |
| 78 | + end |
| 79 | +option_example (generic function with 1 method) |
| 80 | +
|
| 81 | +julia> option_example(nothing) |
| 82 | +Const(nothing) |
| 83 | +
|
| 84 | +julia> option_example(41) |
| 85 | +Const(Thrown(ErrorException("fail"))) |
| 86 | +
|
| 87 | +julia> option_example(42) |
| 88 | +Identity((42, 43)) |
| 89 | +``` |
| 90 | + |
| 91 | +Some monads of TypeClasses need a bit more work to translate them into effects. They need little extra wrappers, but nothing fancy, just use their respective `@run...` macro. |
| 92 | + |
| 93 | +Let's directly jump to super complicated interactions of many effects at once. Please experiment with this little example. Take effects out, reorder them, etc. |
| 94 | + |
| 95 | +```jldoctest |
| 96 | +julia> # simple ContextManager for example purposes |
| 97 | + create_context(x) = @ContextManager continuation -> begin |
| 98 | + println("before $x") |
| 99 | + result = continuation(x) |
| 100 | + println("after $x") |
| 101 | + result |
| 102 | + end |
| 103 | +create_context (generic function with 1 method) |
| 104 | +
|
| 105 | +julia> contextmanager_callable_state = @runcontextmanager @runcallable @runstate @syntax_eff begin |
| 106 | + co = create_context(4) |
| 107 | + ve = collect(1:co) |
| 108 | + st = State(s -> (ve+s, 2s)) |
| 109 | + op = isodd(st) ? Option(100) : Option() |
| 110 | + ca = Callable(x -> "x = $x, st = $st, op = $op") |
| 111 | + @pure [co, ve, st, op, ca] |
| 112 | + end; |
| 113 | +
|
| 114 | +julia> # running the contextmanager |
| 115 | + result, nextstate = run(contextmanager_callable_state) do value |
| 116 | + @show value |
| 117 | + end |> |
| 118 | + # calling the callable |
| 119 | + callable_state -> callable_state("hello") |> |
| 120 | + # providing initial state for the state |
| 121 | + state -> run(state, 11); |
| 122 | +before 4 |
| 123 | +value = (Option{Vector{Any}}[Const(nothing), Const(nothing), Identity(Any[4, 3, 47, 100, "x = hello, st = 47, op = 100"]), Const(nothing)], 176) |
| 124 | +after 4 |
| 125 | +
|
| 126 | +julia> result |
| 127 | +4-element Vector{Option{Vector{Any}}}: |
| 128 | + Const(nothing) |
| 129 | + Const(nothing) |
| 130 | + Identity(Any[4, 3, 47, 100, "x = hello, st = 47, op = 100"]) |
| 131 | + Const(nothing) |
| 132 | +
|
| 133 | +julia> nextstate |
| 134 | +176 |
| 135 | +``` |
| 136 | + |
| 137 | +Welcome to fully composable effects. [Effects](@ref) and [How does it actually work?](@ref) can provide you more details. |
| 138 | + |
| 139 | + |
| 140 | +## Core Interface `eff_applies`, `eff_pure`, `eff_flatmap` |
| 141 | + |
| 142 | +All effects and effect handlers need to overwrite the three core functions. We specify them by using `Vector` as an example: |
| 143 | + |
| 144 | + |
| 145 | +| core function | default | description | |
| 146 | +| ------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| 147 | +| `eff_applies(handler::Type{<:Vector}, effectful::Vector) = true` | there is no default | specify on which values the handler applies (the handler Vector applies to Vector of course) | |
| 148 | +| `eff_pure(handler::Type{<:Vector}, value) = [value]` | defaulting to `TypeClasses.pure` (enough for Vector) | wrap a plain value into the Monad of the handler, here Vector. | |
| 149 | +| `eff_flatmap(continuation, effectful::Vector)` | defaults to using `map`, `flatmap`, and `flip_types` from `TypeClasses` (this is enough for `Vector`) | apply a continuation to the current effect (here again Vector as an example). The key difference to plain`TypeClasses.flatmap` is that `continuation` does not return a plain `Vector`, but a `Eff{Vector}`. Applying this `continuation` with a plain `map` would lead `Vector{Eff{Vector}}`. However, `eff_flatmap` needs to return an `Eff{Vector}` instead. | |
| 150 | + |
| 151 | + |
| 152 | +## Future Work |
| 153 | + |
| 154 | +Julia's type-inference seems to have quite some trouble inferring through the core algorithms of ExtensibleEffects. Hence in case type-inference and speed is crucial to your effectful/monadic code, we recommend to use [`TypeClasses.jl`](https://github.com/JuliaFunctional/TypeClasses.jl) as of now. The monads of TypeClasses.jl do not compose that well as the effects in ExtensibleEffects.jl, but type-inference is much simpler. |
0 commit comments