Skip to content

Instantly share code, notes, and snippets.

@sshark
Forked from jdegoes/fpmax.scala
Created August 22, 2018 17:14

Revisions

  1. @jdegoes jdegoes created this gist Jul 13, 2018.
    528 changes: 528 additions & 0 deletions fpmax.scala
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,528 @@
    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)
    }