Skip to content

Instantly share code, notes, and snippets.

@mmacphail
Last active August 19, 2019 20:30
Show Gist options
  • Save mmacphail/84ae1b4a31a549f6d52775bf963da630 to your computer and use it in GitHub Desktop.
Save mmacphail/84ae1b4a31a549f6d52775bf963da630 to your computer and use it in GitHub Desktop.
FP to the max implemented with Arrow
package eu.macphail.guesser
import arrow.Kind
import arrow.core.*
import arrow.effects.*
import arrow.extension
import eu.macphail.guesser.io.console.console
import eu.macphail.guesser.io.program.program
import eu.macphail.guesser.io.randomNumberGen.randomNumberGen
import kotlin.random.Random
interface RandomNumberGen<F> {
fun nextInt(upper: Int): Kind<F, Int>
}
@extension
interface IORandomNumberGen : RandomNumberGen<ForIO> {
override fun nextInt(upper: Int): Kind<ForIO, Int> = IO { Random.nextInt(upper) }
}
interface Console<F> {
fun putStrLn(line: String): Kind<F, Unit>
fun getStrLn(): Kind<F, String>
}
@extension
interface IOConsole : Console<ForIO> {
override fun putStrLn(line: String): Kind<ForIO, Unit> = IO { println(line) }
override fun getStrLn(): Kind<ForIO, String> = IO { readLine()!! }
}
interface Program<F> {
fun <A> finish(a: () -> A): Kind<F, A>
fun <A, B> Kind<F, A>.chain(afb: (A) -> Kind<F, B>): Kind<F, B>
fun <A, B> Kind<F, A>.map(ab: (A) -> B): Kind<F, B>
}
@extension
interface IOProgram : Program<ForIO> {
override fun <A> finish(a: () -> A): Kind<ForIO, A> = IO(a)
override fun <A, B> Kind<ForIO, A>.chain(afb: (A) -> Kind<ForIO, B>): Kind<ForIO, B> = this.fix().flatMap(afb)
override fun <A, B> Kind<ForIO, A>.map(ab: (A) -> B): Kind<ForIO, B> = this.fix().map { ab(it) }
}
fun parseInt(s: String): Option<Int> = Try { s.toInt() }.toOption()
fun <F, TC> TC.checkContinue(name: String): Kind<F, Boolean>
where TC : Program<F>, TC : Console<F> =
putStrLn("Do you want to continue, $name?").chain {
getStrLn().map(String::toLowerCase).chain { input ->
when (input) {
"y" -> finish { true }
"n" -> finish { false }
else -> checkContinue(name)
}
}
}
fun <F, TC> TC.gameLoop(name: String): Kind<F, Unit>
where TC : Program<F>, TC : Console<F>, TC : RandomNumberGen<F> =
nextInt(5).map { it + 1 }.chain { num ->
putStrLn("Dear $name, please guess a number from 1 to 5:").chain {
getStrLn().map(::parseInt).chain { guess ->
when (guess) {
is None -> putStrLn("You did not enter a number")
is Some -> if (guess.t == num) putStrLn("You guessed right, $name!")
else putStrLn("You guessed wrong, $name! The number was $num")
}.chain {
checkContinue(name).chain { cont ->
if (cont) gameLoop(name) else finish { Unit }
}
}
}
}
}
fun <F, TC> TC.mainProgram(): Kind<F, Unit>
where TC : Program<F>, TC : Console<F>, TC : RandomNumberGen<F> =
putStrLn("What is your name?").chain {
getStrLn().chain { name ->
putStrLn("Hello $name, welcome to the game!").chain {
gameLoop(name)
}
}
}
class IOConsoleProgram :
Console<ForIO> by IO.console(),
Program<ForIO> by IO.program(),
RandomNumberGen<ForIO> by IO.randomNumberGen()
fun main() {
val cp = IOConsoleProgram()
cp.run { mainProgram() }.fix().unsafeRunAsync { Unit }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment