diff --git a/modules/docs/arrow-docs/docs/docs/patterns/typeclasspolymorphism/README.md b/modules/docs/arrow-docs/docs/docs/patterns/typeclasspolymorphism/README.md index 741c5f3468f..ddb7c2ac3a1 100644 --- a/modules/docs/arrow-docs/docs/docs/patterns/typeclasspolymorphism/README.md +++ b/modules/docs/arrow-docs/docs/docs/patterns/typeclasspolymorphism/README.md @@ -709,3 +709,7 @@ Some of the mentioned concepts like purity are described in the following blogpo * [Kotlin Functional Programming: Does it make sense?](https://medium.com/@JorgeCastilloPr/kotlin-functional-programming-does-it-make-sense-36ad07e6bacf) by [Jorge Castillo](https://www.twitter.com/JorgeCastilloPR)) * [Kotlin purity and Function Memoization](https://medium.com/@JorgeCastilloPr/kotlin-purity-and-function-memoization-b12ab35d70a5) by [Jorge Castillo](https://www.twitter.com/JorgeCastilloPR)) + +Also, consider watching [FP to the max](https://youtu.be/sxudIMiOo68) by [John De Goes](https://twitter.com/jdegoes) and +the related `FpToTheMax.kt` example located in the `arrow-examples` module. This technique may seem to be a huge overhead +on such a small example, but it is meant to be used at scale. \ No newline at end of file diff --git a/modules/docs/arrow-examples/build.gradle b/modules/docs/arrow-examples/build.gradle index 93f4d3657cb..b41110a5b1c 100644 --- a/modules/docs/arrow-examples/build.gradle +++ b/modules/docs/arrow-examples/build.gradle @@ -1,3 +1,27 @@ + +apply plugin: 'idea' + +idea { + module { + sourceDirs += files( + 'build/generated/source/kapt/main', + 'build/generated/source/kapt/debug', + 'build/generated/source/kapt/release', + 'build/generated/source/kaptKotlin/main', + 'build/generated/source/kaptKotlin/debug', + 'build/generated/source/kaptKotlin/release', + 'build/tmp/kapt/main/kotlinGenerated') + generatedSourceDirs += files( + 'build/generated/source/kapt/main', + 'build/generated/source/kapt/debug', + 'build/generated/source/kapt/release', + 'build/generated/source/kaptKotlin/main', + 'build/generated/source/kaptKotlin/debug', + 'build/generated/source/kaptKotlin/release', + 'build/tmp/kapt/main/kotlinGenerated') + } +} + dependencies { compile project(':arrow-data') compile project(':arrow-effects') diff --git a/modules/docs/arrow-examples/src/test/kotlin/arrow/FpToTheMax.kt b/modules/docs/arrow-examples/src/test/kotlin/arrow/FpToTheMax.kt new file mode 100644 index 00000000000..e65cf609e1b --- /dev/null +++ b/modules/docs/arrow-examples/src/test/kotlin/arrow/FpToTheMax.kt @@ -0,0 +1,89 @@ +package arrow + +import arrow.core.Option +import arrow.core.Try +import arrow.effects.ForIO +import arrow.effects.IO +import arrow.effects.console +import arrow.effects.fRandom +import arrow.effects.fix +import arrow.effects.monad +import arrow.typeclasses.Monad +import arrow.typeclasses.binding +import java.util.Random + +/** + * This sample is a simple translation in Kotlin (using arrow, of course) of this talk: https://youtu.be/sxudIMiOo68 + */ + + +object ORandom : Random() + +interface Console { + fun putStrLn(s: String): Kind + fun getStrLn(): Kind +} + +@instance(IO::class) +interface IOConsoleInstance : Console { + + override fun putStrLn(s: String): Kind = IO { println(s) } + + override fun getStrLn(): Kind = IO { readLine().orEmpty() } + +} + +interface FRandom { + fun nextInt(upper: Int): Kind +} + +@instance(IO::class) +interface FRandomInstance: FRandom { + override fun nextInt(upper: Int): Kind = IO { ORandom.nextInt(upper) } +} + +class MonadAndConsoleRandom(M: Monad, C: Console, R: FRandom): Monad by M, Console by C, FRandom by R { + +} + +object FpToTheMax { + + fun parseInt(s: String): Option = Try { s.toInt() }.toOption() + + + fun checkContinue(MC: MonadAndConsoleRandom, name: String): Kind = MC.binding { + MC.putStrLn("Do you want to continue, $name?").bind() + val input = MC.getStrLn().map { it.toLowerCase() }.bind() + when (input) { + "y" -> MC.just(true) + "n" -> MC.just(false) + else -> checkContinue(MC, name) + }.bind() + } + + fun gameLoop(MC: MonadAndConsoleRandom, name: String): Kind = MC.binding { + val num = MC.nextInt(5).map { it + 1 }.bind() + MC.putStrLn("Dear $name, please guess a number from 1 to 5:").bind() + val input = MC.getStrLn().bind() + parseInt(input).fold({ MC.putStrLn("You did not enter a number")}){ guess -> + if (guess == num) MC.putStrLn("You guessed right, $name!") + else MC.putStrLn("You guessed wrong, $name! The number was: $num") + }.bind() + val cont = checkContinue(MC, name).bind() + (if (cont) gameLoop(MC, name) else MC.just(Unit)).bind() + } + + fun fMain(MC: MonadAndConsoleRandom): Kind = MC.binding { + MC.putStrLn("What is your name?").bind() + val name = MC.getStrLn().bind() + MC.putStrLn("Hello $name, welcome to the game").bind() + gameLoop(MC, name).bind() + } + + @JvmStatic + fun main(args: Array) { + val r = fMain(MonadAndConsoleRandom(IO.monad(), IO.console(), IO.fRandom())) + r.fix().unsafeRunSync() + } + +}