Created
July 13, 2018 03:18
-
-
Save jdegoes/1b43f43e2d1e845201de853815ab3cb9 to your computer and use it in GitHub Desktop.
FP to the Max — Code Examples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package fpmax | |
import scala.util.Try | |
import scala.io.StdIn.readLine | |
object App0 { | |
def main: Unit = { | |
println("What is your name?") | |
val name = readLine() | |
println("Hello, " + name + ", welcome to the game!") | |
var exec = true | |
while (exec) { | |
val num = scala.util.Random.nextInt(5) + 1 | |
println("Dear " + name + ", please guess a number from 1 to 5:") | |
val guess = readLine().toInt | |
if (guess == num) println("You guessed right, " + name + "!") | |
else println("You guessed wrong, " + name + "! The number was: " + num) | |
println("Do you want to continue, " + name + "?") | |
readLine() match { | |
case "y" => exec = true | |
case "n" => exec = false | |
} | |
} | |
} | |
} | |
object App1 { | |
def parseInt(s: String): Option[Int] = Try(s.toInt).toOption | |
trait Program[F[_]] { | |
def finish[A](a: => A): F[A] | |
def chain[A, B](fa: F[A], afb: A => F[B]): F[B] | |
def map[A, B](fa: F[A], ab: A => B): F[B] | |
} | |
object Program { | |
def apply[F[_]](implicit F: Program[F]): Program[F] = F | |
} | |
implicit class ProgramSyntax[F[_], A](fa: F[A]) { | |
def map[B](f: A => B)(implicit F: Program[F]): F[B] = F.map(fa, f) | |
def flatMap[B](afb: A => F[B])(implicit F: Program[F]): F[B] = F.chain(fa, afb) | |
} | |
def finish[F[_], A](a: => A)(implicit F: Program[F]): F[A] = F.finish(a) | |
trait Console[F[_]] { | |
def putStrLn(line: String): F[Unit] | |
def getStrLn: F[String] | |
} | |
object Console { | |
def apply[F[_]](implicit F: Console[F]): Console[F] = F | |
} | |
def putStrLn[F[_]: Console](line: String): F[Unit] = Console[F].putStrLn(line) | |
def getStrLn[F[_]: Console]: F[String] = Console[F].getStrLn | |
trait Random[F[_]] { | |
def nextInt(upper: Int): F[Int] | |
} | |
object Random { | |
def apply[F[_]](implicit F: Random[F]): Random[F] = F | |
} | |
def nextInt[F[_]](upper: Int)(implicit F: Random[F]): F[Int] = Random[F].nextInt(upper) | |
case class IO[A](unsafeRun: () => A) { self => | |
def map[B](f: A => B): IO[B] = IO(() => f(self.unsafeRun())) | |
def flatMap[B](f: A => IO[B]): IO[B] = IO(() => f(self.unsafeRun()).unsafeRun()) | |
} | |
object IO { | |
def point[A](a: => A): IO[A] = IO(() => a) | |
implicit val ProgramIO = new Program[IO] { | |
def finish[A](a: => A): IO[A] = IO.point(a) | |
def chain[A, B](fa: IO[A], afb: A => IO[B]): IO[B] = fa.flatMap(afb) | |
def map[A, B](fa: IO[A], ab: A => B): IO[B] = fa.map(ab) | |
} | |
implicit val ConsoleIO = new Console[IO] { | |
def putStrLn(line: String): IO[Unit] = IO(() => println(line)) | |
def getStrLn: IO[String] = IO(() => readLine()) | |
} | |
implicit val RandomIO = new Random[IO] { | |
def nextInt(upper: Int): IO[Int] = IO(() => scala.util.Random.nextInt(upper)) | |
} | |
} | |
case class TestData(input: List[String], output: List[String], nums: List[Int]) { | |
def putStrLn(line: String): (TestData, Unit) = | |
(copy(output = line :: output), ()) | |
def getStrLn: (TestData, String) = | |
(copy(input = input.drop(1)), input.head) | |
def nextInt(upper: Int): (TestData, Int) = | |
(copy(nums = nums.drop(1)), nums.head) | |
def showResults = output.reverse.mkString("\n") | |
} | |
case class TestIO[A](run: TestData => (TestData, A)) { self => | |
def map[B](ab: A => B): TestIO[B] = | |
TestIO(t => self.run(t) match { case (t, a) => (t, ab(a)) }) | |
def flatMap[B](afb: A => TestIO[B]): TestIO[B] = | |
TestIO(t => self.run(t) match { case (t, a) => afb(a).run(t) }) | |
def eval(t: TestData): TestData = run(t)._1 | |
} | |
object TestIO { | |
def point[A](a: => A): TestIO[A] = TestIO(t => (t, a)) | |
implicit val ProgramTestIO = new Program[TestIO] { | |
def finish[A](a: => A): TestIO[A] = TestIO.point(a) | |
def chain[A, B](fa: TestIO[A], afb: A => TestIO[B]): TestIO[B] = fa.flatMap(afb) | |
def map[A, B](fa: TestIO[A], ab: A => B): TestIO[B] = fa.map(ab) | |
} | |
implicit val ConsoleTestIO = new Console[TestIO] { | |
def putStrLn(line: String): TestIO[Unit] = TestIO(t => t.putStrLn(line)) | |
def getStrLn: TestIO[String] = TestIO(t => t.getStrLn) | |
} | |
implicit val RandomTestIO = new Random[TestIO] { | |
def nextInt(upper: Int): TestIO[Int] = TestIO(t => t.nextInt(upper)) | |
} | |
} | |
def checkContinue[F[_]: Program: Console](name: String): F[Boolean] = | |
for { | |
_ <- putStrLn("Do you want to continue, " + name + "?") | |
input <- getStrLn.map(_.toLowerCase) | |
cont <- input match { | |
case "y" => finish(true) | |
case "n" => finish(false) | |
case _ => checkContinue(name) | |
} | |
} yield cont | |
def printResults[F[_]: Console](input: String, num: Int, name: String): F[Unit] = | |
parseInt(input).fold( | |
putStrLn("You did not enter a number") | |
)(guess => | |
if (guess == num) putStrLn("You guessed right, " + name + "!") | |
else putStrLn("You guessed wrong, " + name + "! The number was: " + num) | |
) | |
def gameLoop[F[_]: Program: Random: Console](name: String): F[Unit] = | |
for { | |
num <- nextInt(5).map(_ + 1) | |
_ <- putStrLn("Dear " + name + ", please guess a number from 1 to 5:") | |
input <- getStrLn | |
_ <- printResults(input, num, name) | |
cont <- checkContinue(name) | |
_ <- if (cont) gameLoop(name) else finish(()) | |
} yield () | |
def main[F[_]: Program: Random: Console]: F[Unit] = | |
for { | |
_ <- putStrLn("What is your name?") | |
name <- getStrLn | |
_ <- putStrLn("Hello, " + name + ", welcome to the game!") | |
_ <- gameLoop(name) | |
} yield () | |
def mainIO: IO[Unit] = main[IO] | |
def mainTestIO: TestIO[Unit] = main[TestIO] | |
val TestExample = | |
TestData( | |
input = "John" :: "1" :: "n" :: Nil, | |
output = Nil, | |
nums = 0 :: Nil | |
) | |
def runTest = mainTestIO.eval(TestExample).showResults | |
/* | |
What is your name? | |
Hello, John, welcome to the game! | |
Dear John, please guess a number from 1 to 5: | |
You guessed right, John! | |
Do you want to continue, John? | |
*/ | |
} | |
object App2 { | |
object stdlib { | |
trait Program[F[_]] { | |
def finish[A](a: A): F[A] | |
def chain[A, B](fa: F[A], afb: A => F[B]): F[B] | |
def map[A, B](fa: F[A], ab: A => B): F[B] | |
} | |
object Program { | |
def apply[F[_]](implicit F: Program[F]): Program[F] = F | |
} | |
implicit class ProgramSyntax[F[_], A](fa: F[A]) { | |
def map[B](ab: A => B)(implicit F: Program[F]): F[B] = F.map(fa, ab) | |
def flatMap[B](afb: A => F[B])(implicit F: Program[F]): F[B] = F.chain(fa, afb) | |
} | |
def finish[F[_], A](a: A)(implicit F: Program[F]): F[A] = F.finish(a) | |
final case class IO[A](unsafeRun: () => A) { self => | |
final def map[B](f: A => B): IO[B] = IO(() => f(self.unsafeRun())) | |
final def flatMap[B](f: A => IO[B]): IO[B] = | |
IO(() => f(self.unsafeRun()).unsafeRun()) | |
} | |
object IO { | |
def point[A](a: => A): IO[A] = IO(() => a) | |
implicit val ProgramIO = new Program[IO] { | |
def finish[A](a: A): IO[A] = IO.point(a) | |
def chain[A, B](fa: IO[A], afb: A => IO[B]): IO[B] = fa.flatMap(afb) | |
def map[A, B](fa: IO[A], ab: A => B): IO[B] = fa.map(ab) | |
} | |
} | |
trait Console[F[_]] { | |
def putStrLn(line: String): F[Unit] | |
def getStrLn: F[String] | |
} | |
object Console { | |
def apply[F[_]](implicit F: Console[F]): Console[F] = F | |
implicit val ConsoleIO = new Console[IO] { | |
def putStrLn(line: String): IO[Unit] = IO(() => println(line)) | |
def getStrLn: IO[String] = IO(() => readLine()) | |
} | |
} | |
def putStrLn[F[_]: Console](line: String): F[Unit] = Console[F].putStrLn(line) | |
def getStrLn[F[_]: Console]: F[String] = Console[F].getStrLn | |
trait Random[F[_]] { | |
def nextInt(upper: Int): F[Int] | |
} | |
object Random { | |
def apply[F[_]](implicit F: Random[F]): Random[F] = F | |
implicit val RandomIO = new Random[IO] { | |
def nextInt(upper: Int): IO[Int] = IO(() => scala.util.Random.nextInt(upper)) | |
} | |
} | |
def nextInt[F[_]: Random](upper: Int): F[Int] = Random[F].nextInt(upper) | |
} | |
import stdlib._ | |
case class TestData(input: List[String], output: List[String], nums: List[Int]) { | |
def showResults = output.reverse.mkString("\n") | |
def nextInt: (TestData, Int) = (copy(nums = nums.drop(1)), nums.head) | |
def putStrLn(line: String): (TestData, Unit) = (copy(output = line :: output), ()) | |
def getStrLn: (TestData, String) = (copy(input = input.drop(1)), input.head) | |
} | |
case class TestIO[A](run: TestData => (TestData, A)) { self => | |
def map[B](f: A => B): TestIO[B] = | |
TestIO(t => self.run(t) match { case (t, a) => (t, f(a)) }) | |
def flatMap[B](f: A => TestIO[B]): TestIO[B] = | |
TestIO(t => self.run(t) match { case (t, a) => f(a).run(t) }) | |
def eval(t: TestData): TestData = self.run(t)._1 | |
} | |
object TestIO { | |
def point[A](a: => A): TestIO[A] = TestIO(t => (t, a)) | |
implicit val RandomTestIO = new Random[TestIO] { | |
def nextInt(upper: Int): TestIO[Int] = | |
TestIO(t => t.nextInt) | |
} | |
implicit val ProgramTestIO = new Program[TestIO] { | |
def finish[A](a: A): TestIO[A] = TestIO.point(a) | |
def chain[A, B](fa: TestIO[A], afb: A => TestIO[B]): TestIO[B] = fa.flatMap(afb) | |
def map[A, B](fa: TestIO[A], ab: A => B): TestIO[B] = fa.map(ab) | |
} | |
implicit val ConsoleTestIO = new Console[TestIO] { | |
def putStrLn(line: String): TestIO[Unit] = | |
TestIO(t => t.putStrLn(line)) | |
def getStrLn: TestIO[String] = | |
TestIO(t => t.getStrLn) | |
} | |
} | |
def parseInt(s: String): Option[Int] = Try(s.toInt).toOption | |
def checkAnswer[F[_]: Console](name: String, num: Int, guess: Int): F[Unit] = | |
if (num == guess) putStrLn("You guessed right, " + name + "!") | |
else putStrLn("You guessed wrong, " + name + "! The number was: " + num) | |
def checkContinue[F[_]: Program: Console](name: String): F[Boolean] = | |
for { | |
_ <- putStrLn("Do you want to continue, " + name + "?") | |
choice <- getStrLn.map(_.toLowerCase) | |
cont <- if (choice == "y") finish(true) | |
else if (choice == "n") finish(false) | |
else checkContinue(name) | |
} yield cont | |
def gameLoop[F[_]: Program: Console: Random](name: String): F[Unit] = | |
for { | |
num <- nextInt(5).map(_ + 1) | |
_ <- putStrLn("Dear " + name + ", please guess a number from 1 to 5:") | |
guess <- getStrLn | |
_ <- parseInt(guess).fold( | |
putStrLn("That is not a valid selection, " + name + "!") | |
)((guess: Int) => checkAnswer(name, num, guess)) | |
cont <- checkContinue(name) | |
_ <- if (cont) gameLoop(name) else finish(()) | |
} yield () | |
def main[F[_]: Program: Console: Random]: F[Unit] = | |
for { | |
_ <- putStrLn("What is your name?") | |
name <- getStrLn | |
_ <- putStrLn("Hello, " + name + ", welcome to the game!") | |
_ <- gameLoop(name) | |
} yield () | |
def mainIO: IO[Unit] = main[IO] | |
def mainTestIO: TestIO[Unit] = main[TestIO] | |
val TestExample = TestData( | |
input = "john" :: "1" :: "n" :: Nil, | |
output = Nil, | |
nums = 0 :: Nil) | |
} | |
object App3 { | |
object stdlib { | |
trait Program[F[_]] { | |
def finish[A](a: A): F[A] | |
def chain[A, B](fa: F[A], afb: A => F[B]): F[B] | |
def map[A, B](fa: F[A], ab: A => B): F[B] | |
} | |
object Program { | |
def apply[F[_]](implicit F: Program[F]): Program[F] = F | |
} | |
implicit class ProgramSyntax[F[_], A](fa: F[A]) { | |
def map[B](ab: A => B)(implicit F: Program[F]): F[B] = F.map(fa, ab) | |
def flatMap[B](afb: A => F[B])(implicit F: Program[F]): F[B] = F.chain(fa, afb) | |
} | |
def finish[F[_], A](a: A)(implicit F: Program[F]): F[A] = F.finish(a) | |
final case class IO[A](unsafeRun: () => A) { self => | |
final def map[B](f: A => B): IO[B] = IO(() => f(self.unsafeRun())) | |
final def flatMap[B](f: A => IO[B]): IO[B] = | |
IO(() => f(self.unsafeRun()).unsafeRun()) | |
} | |
object IO { | |
def point[A](a: => A): IO[A] = IO(() => a) | |
implicit val ProgramIO = new Program[IO] { | |
def finish[A](a: A): IO[A] = IO.point(a) | |
def chain[A, B](fa: IO[A], afb: A => IO[B]): IO[B] = fa.flatMap(afb) | |
def map[A, B](fa: IO[A], ab: A => B): IO[B] = fa.map(ab) | |
} | |
} | |
sealed trait ConsoleOut { | |
def en: String | |
} | |
object ConsoleOut { | |
case class YouGuessedRight(name: String) extends ConsoleOut { | |
def en = "You guessed right, " + name + "!" | |
} | |
case class YouGuessedWrong(name: String, num: Int) extends ConsoleOut { | |
def en = "You guessed wrong, " + name + "! The number was: " + num | |
} | |
case class DoYouWantToContinue(name: String) extends ConsoleOut { | |
def en = "Do you want to continue, " + name + "?" | |
} | |
case class PleaseGuess(name: String) extends ConsoleOut { | |
def en = "Dear " + name + ", please guess a number from 1 to 5:" | |
} | |
case class ThatIsNotValid(name: String) extends ConsoleOut { | |
def en = "That is not a valid selection, " + name + "!" | |
} | |
case object WhatIsYourName extends ConsoleOut { | |
def en = "What is your name?" | |
} | |
case class WelcomeToGame(name: String) extends ConsoleOut { | |
def en = "Hello, " + name + ", welcome to the game!" | |
} | |
} | |
trait Console[F[_]] { | |
def putStrLn(line: ConsoleOut): F[Unit] | |
def getStrLn: F[String] | |
} | |
object Console { | |
def apply[F[_]](implicit F: Console[F]): Console[F] = F | |
implicit val ConsoleIO = new Console[IO] { | |
def putStrLn(line: ConsoleOut): IO[Unit] = IO(() => println(line.en)) | |
def getStrLn: IO[String] = IO(() => readLine()) | |
} | |
} | |
def putStrLn[F[_]: Console](line: ConsoleOut): F[Unit] = Console[F].putStrLn(line) | |
def getStrLn[F[_]: Console]: F[String] = Console[F].getStrLn | |
trait Random[F[_]] { | |
def nextInt(upper: Int): F[Int] | |
} | |
object Random { | |
def apply[F[_]](implicit F: Random[F]): Random[F] = F | |
implicit val RandomIO = new Random[IO] { | |
def nextInt(upper: Int): IO[Int] = IO(() => scala.util.Random.nextInt(upper)) | |
} | |
} | |
def nextInt[F[_]: Random](upper: Int): F[Int] = Random[F].nextInt(upper) | |
} | |
import stdlib._ | |
case class TestData(input: List[String], output: List[ConsoleOut], nums: List[Int]) { | |
def showResults = output.reverse.map(_.en).mkString("\n") | |
def nextInt: (TestData, Int) = (copy(nums = nums.drop(1)), nums.head) | |
def putStrLn(line: ConsoleOut): (TestData, Unit) = (copy(output = line :: output), ()) | |
def getStrLn: (TestData, String) = (copy(input = input.drop(1)), input.head) | |
} | |
case class TestIO[A](run: TestData => (TestData, A)) { self => | |
def map[B](f: A => B): TestIO[B] = | |
TestIO(t => self.run(t) match { case (t, a) => (t, f(a)) }) | |
def flatMap[B](f: A => TestIO[B]): TestIO[B] = | |
TestIO(t => self.run(t) match { case (t, a) => f(a).run(t) }) | |
def eval(t: TestData): TestData = self.run(t)._1 | |
} | |
object TestIO { | |
def point[A](a: => A): TestIO[A] = TestIO(t => (t, a)) | |
implicit val RandomTestIO = new Random[TestIO] { | |
def nextInt(upper: Int): TestIO[Int] = | |
TestIO(t => t.nextInt) | |
} | |
implicit val ProgramTestIO = new Program[TestIO] { | |
def finish[A](a: A): TestIO[A] = TestIO.point(a) | |
def chain[A, B](fa: TestIO[A], afb: A => TestIO[B]): TestIO[B] = fa.flatMap(afb) | |
def map[A, B](fa: TestIO[A], ab: A => B): TestIO[B] = fa.map(ab) | |
} | |
implicit val ConsoleTestIO = new Console[TestIO] { | |
def putStrLn(line: ConsoleOut): TestIO[Unit] = | |
TestIO(t => t.putStrLn(line)) | |
def getStrLn: TestIO[String] = | |
TestIO(t => t.getStrLn) | |
} | |
} | |
def parseInt(s: String): Option[Int] = Try(s.toInt).toOption | |
def checkAnswer[F[_]: Console](name: String, num: Int, guess: Int): F[Unit] = | |
if (num == guess) putStrLn(ConsoleOut.YouGuessedRight(name)) | |
else putStrLn(ConsoleOut.YouGuessedWrong(name, num)) | |
def checkContinue[F[_]: Program: Console](name: String): F[Boolean] = | |
for { | |
_ <- putStrLn(ConsoleOut.DoYouWantToContinue(name)) | |
choice <- getStrLn.map(_.toLowerCase) | |
cont <- if (choice == "y") finish(true) | |
else if (choice == "n") finish(false) | |
else checkContinue(name) | |
} yield cont | |
def gameLoop[F[_]: Program: Console: Random](name: String): F[Unit] = | |
for { | |
num <- nextInt(5).map(_ + 1) | |
_ <- putStrLn(ConsoleOut.PleaseGuess(name)) | |
guess <- getStrLn | |
_ <- parseInt(guess).fold( | |
putStrLn(ConsoleOut.ThatIsNotValid(name)) | |
)((guess: Int) => checkAnswer(name, num, guess)) | |
cont <- checkContinue(name) | |
_ <- if (cont) gameLoop(name) else finish(()) | |
} yield () | |
def main[F[_]: Program: Console: Random]: F[Unit] = | |
for { | |
_ <- putStrLn(ConsoleOut.WhatIsYourName) | |
name <- getStrLn | |
_ <- putStrLn(ConsoleOut.WelcomeToGame(name)) | |
_ <- gameLoop(name) | |
} yield () | |
def mainIO: IO[Unit] = main[IO] | |
def mainTestIO: TestIO[Unit] = main[TestIO] | |
val TestExample = TestData( | |
input = "john" :: "1" :: "n" :: Nil, | |
output = Nil, | |
nums = 0 :: Nil) | |
} |
@halyph good try, but to get actual results you need to evaluate an IO - via
IO.unsafeRun()
method. It's so-called an "end of the world" when we're interpreting our pure IO values into real-world side effects.
to expand on this, the correct syntax would be to put this into the two apps you wish to check:
def main(args: Array[String]): Unit = {
mainIO.unsafeRun()
}
Similarly, you can print the test results to the console like so:
def main(args: Array[String]): Unit = {
println(runTest)
}
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Original video: https://www.youtube.com/watch?v=sxudIMiOo68
Here is a Kotlin implementation: https://github.com/enhan/fptothemax/tree/master/src/main/kotlin/eu/enhan/fptothemax