Last active
August 17, 2018 03:05
-
-
Save matfournier/82504b87848331d236a3cba8b290cd9b to your computer and use it in GitHub Desktop.
This file contains hidden or 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
///// this is from https://gist.github.com/jdegoes/1b43f43e2d1e845201de853815ab3cb9 | |
///// App2 ported to cats | |
////// | |
// build.sbt | |
// name := "finaltagless" | |
// version := "0.1" | |
// scalaVersion := "2.12.6" | |
// libraryDependencies += | |
// "org.typelevel" %% "cats-core" % "1.0.0" | |
// scalacOptions ++= Seq( | |
// "-Xfatal-warnings", | |
// "-Ypartial-unification", | |
// "-language:higherKinds" | |
// ) | |
// scalacOptions ++= Seq("-deprecation", "-feature") | |
// libraryDependencies += "org.typelevel" %% "cats-effect" % "1.0.0-RC2" | |
package dumbexamples | |
import cats.effect.IO | |
import cats._ | |
import cats.data._ | |
import cats.implicits._ | |
import scala.io.StdIn | |
import scala.util.Try | |
object FpMaxExample extends App { | |
object stdlib { | |
// DSL | |
trait Console[F[_]] { | |
def putStrLn(line: String): F[Unit] | |
def getStrLn: F[String] | |
} | |
trait Random[F[_]] { | |
def nextInt(upper: Int): F[Int] | |
} | |
// interpreters | |
object Console { | |
def apply[F[_]](implicit F: Console[F]): Console[F] = F | |
implicit object ConsoleIO extends Console[IO] { | |
def putStrLn(line: String): IO[Unit] = | |
IO(println(line)) | |
def getStrLn: IO[String] = | |
IO(StdIn.readLine) | |
} | |
} | |
object Random { | |
def apply[F[_]](implicit F: Random[F]): Random[F] = F | |
implicit val RandomIO: Random[IO] = 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) | |
// API instances -- See | |
// https://alvinalexander.com/scala/fp-book/type-classes-101-introduction | |
// Section 4.2.2 Scalaz for mere mortals | |
// Scala w/ cats Interface Objects in TypeClasses | |
object ConsoleApi { | |
def putStrLn[F[_] : Console](line: String): F[Unit] = Console[F].putStrLn(line) | |
def getStrLn[F[_] : Console]: F[String] = Console[F].getStrLn | |
} | |
} | |
import stdlib._ | |
import stdlib.ConsoleApi._ | |
/////// TESTING | |
case class TestData(input: List[String], output: List[String], nums: List[Int]) { | |
def getOutput: String = output.reverse.mkString("\n") | |
} | |
val testSingleWrongGuess = TestData( | |
input = "mat" :: "3" :: "n" :: Nil, | |
output = Nil, | |
nums = 0 :: Nil) | |
val testSingleRightGuess = TestData( | |
input = "mat" :: "3" :: "n" :: Nil, | |
output = Nil, | |
nums = 2 :: Nil) | |
// somewhat caged from 'Scalaz for mere mortals pg 4.2.2 on testing interpreters | |
// and 4.9.2 Scala With Cats for comprehensions using the state monad | |
// | |
// but how to actually get this to run as an interpreter for main[F[_]] | |
import cats.data.State._ | |
object TestIOImpl { | |
type F[A] = State[TestData, A] | |
implicit object C extends Console[F] { | |
def getStrLn: F[String] = for { | |
str <- get[TestData] | |
_ <- modify[TestData](s => s.copy(input = s.input.drop(1))) | |
} yield str.input.head | |
def putStrLn(line: String): F[Unit] = for { | |
_ <- modify[TestData](s => s.copy(output = line :: s.output)) | |
} yield () | |
} | |
implicit val R: Random[F] = new Random[F] { | |
def nextInt(upper: Int): F[Int] = for { | |
int <- get[TestData] | |
_ <- modify[TestData](s => s.copy(nums = s.nums.drop(1))) | |
} yield int.nums.head | |
} | |
} | |
def checkAnswer[F[_]: Console](name: String, num: Int, guess: Int): F[Unit] = { | |
if (num == guess) putStrLn("You guessed the right number " + name) | |
else putStrLn("You guessed wrong, " + name + "! the number was: " + num) | |
} | |
def checkContinue[F[_]: Monad: Console](name: String): F[Boolean] = | |
for { | |
_ <- putStrLn(s"do you want to continue, " + name + "?") | |
choice <- getStrLn.map(_.toLowerCase) | |
cont <- { | |
if (choice == "y") true.pure[F] | |
else if (choice == "n") false.pure[F] | |
else checkContinue[F](name) | |
} | |
} yield cont | |
def parseInt(s: String): Option[Int] = Try(s.toInt).toOption | |
def parseIntAndGuessBounds(s: String, bounds: Int) = | |
parseInt(s).filter(_ <= bounds) | |
def gameLoop[F[_]:Console:Monad:Random](name: String): F[Unit] = | |
for { | |
num <- nextInt(9).map(_ + 1) | |
_ <- putStrLn("guess between 1 and 10") | |
guess <- getStrLn | |
_ <- parseIntAndGuessBounds(guess, 10).fold( | |
putStrLn("that is not a valid guess!") | |
)((guess: Int) => checkAnswer(name, num, guess)) | |
cont <- checkContinue[F](name) | |
_ <- if (cont) gameLoop[F](name) else ().pure[F] | |
} yield () | |
def main[F[_]: Monad: Console: Random]: F[Unit] = | |
for { | |
_ <- putStrLn("What is your name?") | |
name <- getStrLn | |
_ <- putStrLn("Hello, " + name + ", welcome to the game!") | |
_ <- gameLoop(name) | |
} yield () | |
// main[IO].unsafeRunSync() <-- to play the game | |
// to test the game | |
import TestIOImpl._ | |
// not sure what to do if the tpye[F] = State[TestData, Unit] was in some other file? | |
val playbacktestWrong = main[F].run(testSingleWrongGuess).value._1.getOutput | |
val playbacktestRight = main[F].run(testSingleRightGuess).value._1.getOutput | |
// hack dump playback for console for example purposes | |
println(playbacktestWrong) | |
println("\n\n over \n\n") | |
println(playbacktestRight) | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment