|
1 | 1 | # kotlin-monads |
2 | 2 |
|
3 | | -[](https://jitpack.io/#h0tk3y/kotlin-monads) [](http://kotlinlang.org/) |
| 3 | +[](https://jitpack.io/#h0tk3y/kotlin-monads) [](http://kotlinlang.org/) |
4 | 4 |
|
5 | | -An attempt to implement monads in Kotlin. |
| 5 | +An attempt to implement monads in Kotlin, deeply inspired by Haskell monads, but restricted within the Kotlin type system. |
6 | 6 |
|
7 | | -_Note: this project uses Kotlin 1.1 EAP build. Use the 1.1 EAP IDE plugin to work with it._ |
| 7 | +_Note: this project uses Kotlin 1.1 Beta build. Use the 1.1 Beta IDE plugin to work with it._ |
8 | 8 |
|
9 | 9 | ## The monad type |
10 | 10 |
|
11 | 11 | Monadic types are represented by the `Monad<M, T>` interface, |
12 | | -where `M` **should be the type of the implementation** with its `T` star-projected. Examples: `Maybe<T> : Monad<Maybe<*>, T>`, `State<S, T> : Monad<State<S, *>, T>`. |
| 12 | +where `M` **should be the type of the implementation** with only its `T` star-projected. Examples: `Maybe<T> : Monad<Maybe<*>, T>`, `State<S, T> : Monad<State<S, *>, T>`. |
13 | 13 |
|
14 | | -The purpose is: with `Monad` defined in this way, we |
15 | | -are almost able to say that a function returns the same `Monad` implementation but with a different type parameter: |
| 14 | +With `Monad` defined in this way, we |
| 15 | +are almost able to say in terms of the Kotlin type system that a function returns the same `Monad` implementation but |
| 16 | +with a different type argument `R` instead of `T`: |
16 | 17 |
|
17 | 18 | fun <T, R, M : Monad<M, *>> Monad<M, T>.map(f: (T) -> R) = bind { returns(f(it)) } |
18 | 19 |
|
@@ -49,13 +50,20 @@ See the usage examples in [tests](https://github.com/h0tk3y/kotlin-monads/tree/m |
49 | 50 |
|
50 | 51 | The monad implementation should only provide one function `bind` (Haskell: `>>=`), |
51 | 52 | no separate `return` is there, instead, if you look at the signature of `bind`, you'll see that the function to bind with is `f: Return<This>.(T) -> Monad<This, R>`. |
52 | | - |
53 | | -It means that a monad implementation should provide the `Return<M>` as well and pass it to `f` each time, so that inside `f` its `returns` could be used: |
| 53 | +It means that a `Monad<M, T>` implementation should provide the `Return<M>` as well and pass it to `f` each time, so that inside `f` its `returns` could be used: |
54 | 54 |
|
55 | 55 | just(3) bind { returns(it * it) } |
56 | 56 |
|
57 | | -I found no direct equivalent to `return` in Haskell, which could be used even outside bind functions. Outside the `bind` blocks, you should either |
58 | | -wrap the values into your monads manually or require a `Return<M>`, which can wrap `T` into `Monad<M, T>` for you. |
| 57 | +There seems to be no direct equivalent to Haskell `return`, which could be used outside any context like `bind` blocks. Outside the `bind` blocks, you should either |
| 58 | +wrap the values into your monads manually or require a `Return<M>`, which can wrap `T` into `Monad<M, T>` for you. |
| 59 | + |
| 60 | +Mind the [monad laws](https://wiki.haskell.org/Monad_laws). A correct monad implementation follows these three rules (rephrased in terms of `kotlin-monads`): |
| 61 | + |
| 62 | +1. **Left identity**: `returns(x) bind { f(it) }` should be equivalent to `f(x)` |
| 63 | + |
| 64 | +2. **Right identity**: `m bind { returns(it) }` should be equivalent to `m` |
| 65 | + |
| 66 | +3. **Associativity**: `m bind { f(it) } bind { g(it) }` should be equivalent to `m bind { f(it) bind { g(it) } }` |
59 | 67 |
|
60 | 68 | Also, it's good to make the return type of `bind` narrower, e.g. `bind` of `Maybe<T>` would rather return `Maybe<R>` than `Monad<Maybe<*>, R>`, it allows not to cast |
61 | 69 | the result of a `bind` called on a known monad type. |
@@ -89,21 +97,46 @@ Example implementation: |
89 | 97 |
|
90 | 98 | ## Do notation |
91 | 99 |
|
92 | | -With the power of Kotlin coroutines, we can have a limited variant of do notation: |
| 100 | +With the power of Kotlin coroutines, we can even have an equivalent of the [*Haskell do notation*](https://en.wikibooks.org/wiki/Haskell/do_notation): |
| 101 | + |
| 102 | +Simple example that performs a monadic list nondeterministic expansion: |
93 | 103 |
|
94 | | - val m = doWith(monadListOf(0)) { |
| 104 | + val m = doReturning(MonadListReturn) { |
95 | 105 | val x = bind(monadListOf(1, 2, 3)) |
96 | | - val y = bind(monadListOf(x, x)) |
97 | | - then(monadListOf(y, y + 1)) |
98 | | - } as MonadList |
| 106 | + val y = bind(monadListOf(x, x + 1)) |
| 107 | + monadListOf(y, x * y) |
| 108 | + } |
99 | 109 |
|
100 | 110 | assertEquals(monadListOf(1, 2, 1, 2, 2, 3, 2, 3, 3, 4, 3, 4), m) |
101 | 111 |
|
102 | | - The limitation is that the intermediate results in a single _do_ block are restricted to the same value type `T`. You can, however, use nested _do_ blocks to use different result types. |
| 112 | +Or applied to an existing monad for convenience: |
| 113 | + |
| 114 | + val m = monadListOf(1, 2, 3).bindDo { x -> |
| 115 | + val y = bind(monadListOf(x, x + 1)) |
| 116 | + monadListOf(y, x * y) |
| 117 | + } |
| 118 | + |
| 119 | +This is effectively equivalent to the following code written with only simple `bind`: |
| 120 | + |
| 121 | + val m = monadListOf(1, 2, 3).bind { x -> |
| 122 | + monadListOf(x, x + 1).bind { y -> |
| 123 | + monadList(y, x * y) |
| 124 | + } |
| 125 | + } |
| 126 | + |
| 127 | +Note that, with simple `bind`, each *transformation* requires another inner scope if it uses the variables bound outside, |
| 128 | +which would lead to some kind of callback hell. |
| 129 | +This problem is effectively solved using the Kotlin coroutines: the compiler performs the CPS transformation of a plain |
| 130 | + code block under the hood. However, this coroutines use case is somewhat out of conventions: it might resume the same continuation |
| 131 | + several times and uses quite a dirty hack to do that. |
| 132 | + |
| 133 | +The result type parameter (`R` in `Monad<M, R`) is usually inferred, and the compiler controls the flow inside a *do block*, but still you need to |
| 134 | + downcast the `Monad<M, R>` to your actual monad type (e.g. `Monad<Maybe<*>, R>` to `Maybe<R>`), because the type system doesn't seem to allow this to be done |
| 135 | + automatically (if you know a way, please tell me). |
103 | 136 |
|
104 | | - **Be careful with mutable state** in _do_ blocks, since all continuation calls will share it, resulting into something unexpected: |
| 137 | + **Be careful with mutable state** in _do_ blocks, since all continuation calls will share it, sometimes resulting into counter-intuitive results: |
105 | 138 |
|
106 | | - val m = doWith(monadListOf(0)) { |
| 139 | + val m = doReturning(MonadListReturn) { |
107 | 140 | for (i in 1..10) |
108 | 141 | bind(monadListOf(0, 0)) |
109 | 142 | } as MonadList |
|
0 commit comments