Skip to content

Commit 8a04604

Browse files
committed
Create README.md
1 parent 2f0eb82 commit 8a04604

File tree

1 file changed

+102
-0
lines changed

1 file changed

+102
-0
lines changed

README.md

Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# kotlin-monads
2+
3+
An attempt to implement monads in Kotlin.
4+
5+
_Note: this project uses Kotlin 1.1 EAP build. Use the 1.1 EAP IDE plugin to work with it._
6+
7+
## The monad type
8+
9+
Monadic types are represented by the `Monad<M, T>` interface,
10+
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>`.
11+
12+
The purpose is: with `Monad` defined in this way, we
13+
are almost able to say that a function returns the same `Monad` implementation but with a different type parameter:
14+
15+
fun <T, R, M : Monad<M, *>> Monad<M, T>.map(f: (T) -> R) = bind { returns(f(it)) }
16+
17+
val m = just(3).map { it * 2 } as Maybe
18+
19+
We still need the downcast `as Maybe`, but at least it's checked.
20+
21+
## Usage
22+
23+
See the usage examples inside [`src/test/kotlin`](https://github.com/h0tk3y/kotlin-monads/tree/master/src/test/kotlin).
24+
25+
## How to implement a monad
26+
27+
`Monad<M, T>` is defined as follows:
28+
29+
interface Return<M> {
30+
fun <T> returns(t: T): Monad<M, T>
31+
}
32+
33+
interface Monad<This, out T> {
34+
infix fun <R> bind(f: Return<This>.(T) -> Monad<This, R>): Monad<This, R>
35+
}
36+
37+
The monad implementation should only provide one function `bind` (Haskell: `>>=`),
38+
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>`.
39+
40+
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:
41+
42+
just(3) bind { returns(it * it) }
43+
44+
I found no direct equivalent to `return` in Haskell, which could be used even outside bind functions. Outside the `bind` blocks, you should either
45+
wrap the values into your monads manually or require a `Return<M>`, which can wrap `T` into `Monad<M, T>` for you.
46+
47+
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
48+
the result of a `bind` called on a known monad type.
49+
50+
val m = monadListOf(1, 2, 3) bind { monadListOf("$it", "$it") } // already `MonadList<String>`, no need to cast
51+
52+
Example implementation:
53+
54+
sealed class Maybe<out T> : Monad<Maybe<*>, T> {
55+
class Just<T>(val value: T) : Maybe<T>()
56+
class None : Maybe<Nothing>()
57+
58+
override fun <R> bind(f: Binder<Maybe<*>, T, R>): Maybe<R> = when (this) {
59+
is Just -> f(MaybeReturn, value) as Maybe
60+
is None -> None()
61+
}
62+
}
63+
64+
object MaybeReturn : Return<Maybe<*>> {
65+
override fun <T> returns(t: T) = Maybe.Just(t)
66+
}
67+
68+
## Monads implementations bundled
69+
70+
* `Maybe<T>`
71+
* `Either<F, T>`
72+
* `MonadList<T>`
73+
* `Reader<E, T>`
74+
* `Writer<T>` (no monoid for now, just `String`)
75+
* `State<S, T>`
76+
77+
## Do notation
78+
79+
With the power of Kotlin coroutines, we can have a limited variant of do notation:
80+
81+
val m = doWith(monadListOf(0)) {
82+
val x = bind(monadListOf(1, 2, 3))
83+
val y = bind(monadListOf(x, x))
84+
then(monadListOf(y, y + 1))
85+
} as MonadList
86+
87+
assertEquals(monadListOf(1, 2, 1, 2, 2, 3, 2, 3, 3, 4, 3, 4), m)
88+
89+
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.
90+
91+
**Be careful with mutable state** in _do_ blocks, since all continuation calls will share it, resulting into something unexpected:
92+
93+
val m = doWith(monadListOf(0)) {
94+
for (i in 1..10)
95+
bind(monadListOf(0, 0))
96+
} as MonadList
97+
98+
One would expect 1024 items here, but the result only contains 11! That's because `i` is mutable and is shared between all the calls that `bind` makes.
99+
100+
101+
102+

0 commit comments

Comments
 (0)