Skip to content

Instantly share code, notes, and snippets.

@matfournier
Last active August 17, 2018 03:05
Show Gist options
  • Save matfournier/82504b87848331d236a3cba8b290cd9b to your computer and use it in GitHub Desktop.
Save matfournier/82504b87848331d236a3cba8b290cd9b to your computer and use it in GitHub Desktop.
///// 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