A few days ago I run into this blog post about implementing a console Hangman game using Scala ZIO. The blog post is based on this talk delivered by John De Goes, for the Scala Kyiv meetup, where he codes a console Hangman game using functional programming.
After I saw the post I was curious how the code would look like written in Koltin with arrow so I decided to try and write it. You can find the result here.
I am still learning functional programming so I asked for feedback from the arrow maintainers on the kotlinlang slack workspace. They were very helpful and I made a few improvements based on their feedback.
Key differences
I would like to point out that the programs in Scala and Kotlin are not 100% equivalent. There are both differences in the language and the functional libraries used.
The IO monad
In Scala ZIO IO[E, A]
describes an effect that may fail with an E
, run forever, or produce a single A
. In the Kotlin version I am using IO<A>
from Arrow. IO<A>
describes an effect that can fail with Throwable
or produce a single A
.
Both type classes produce an A
. The key difference the error. In Scala ZIO you can use any error type or even Nothing
to indicate the program never ends. In Arrow the E
type is always Throwable
so you don’t have to specify it.
Reading and writing from the Console
Scala ZIO comes with built in primitives for interacting with the console. In the Kotlin version I had to implement the readStrLn
and putStrLn
functions myself.
1
2
3
4
5
fun putStrLn(line: String): IO<Unit> = IO { println(line) }
fun getStrLn(): IO<String> = IO {
readLine() ?: throw IOException("Failed to read input!")
}
For comprehensions
For comprehensions are built into the scala language. Unfortunately Kotlin doesn’t have the same feature. But Kotlin has Coroutines so the Arrow team built Comprehensions over coroutines which can be used in a similar way to make the code more readable.
1
2
3
4
5
6
7
8
9
10
//Scala with For comprehensions
val hangman : IO[IOException, Unit] = for {
_ <- putStrLn("Welcome to purely functional hangman")
name <- getName
_ <- putStrLn(s"Welcome $name. Let's begin!")
word <- chooseWord
state = State(name, Set(), word)
_ <- renderState(state)
_ <- gameLoop(state)
} yield()
1
2
3
4
5
6
7
8
9
10
11
//Kotlin with Arrow (Comprehensions over coroutines)
val hangman: IO<Unit> = IO.monad().binding {
putStrLn("Welcome to purely functional hangman").bind()
val name = getName.bind()
putStrLn("Welcome $name. Let's begin!").bind()
val word = chooseWord.bind()
val state = State(name, word = word)
renderState(state).bind()
gameLoop(state).bind()
Unit
}.fix()
Conclusion
Rewriting Functional Hangman from Scala to Kotlin was a nice exercise for learning FP. There are differences between Scala and Kotlin and the IO
type class in Scala ZIO and Arrow but the core principles are the same, we write programs by solving small problems and then combine the solutions. We use lazy evaluation and move the side effects to the edge of the system.
Thanks to John who wrote this functional hangman, Abhishek for writing the article and Paco, Raul and Leandro, from the Arrow team, for the feedback about my code.