Console
Installation
"tf.tofu" %% "tofu" % tofu-version
or as a standalone dependency
"tf.tofu" %% "tofu-core" % tofu-version
What if you need to keep things simple?
Let's say you're writing something simple and you need some of that good ol' console IO. Of course you can use plain println
but we are nice and pure here. It is way better to use tofu.Console
in such case.
Console
The Console[F]
does three things:
- lets you read from standard input in
F
- lets you write to standard output in
F
- lets you write to error output in
F
Let's demonstrate it with simple example.
Suppose we are writing a subset of unix cat
program that can echo its input to output.
import cats.effect.{ExitCode, IO, IOApp}
import cats.FlatMap
import tofu.common.Console
import tofu.syntax.monadic._ //for flatMap
object catStraight extends IOApp {
override def run(args: List[String]): IO[ExitCode] =
catProgramStep[IO].foreverM
def catProgramStep[F[_] : FlatMap : Console]: F[Unit] = for {
input <- Console[F].readStrLn
_ <- Console[F].putStrLn(input) // or putStr if you don't like newlines
} yield ()
}
Where does the instance of Console[IO]
comes from?
The answer is that for any type F
that has Sync[F]
instance of console comes for free by using standard scala console IO.
Syntax
It's all cool but writing Console[F]
isn't cool. There is 'tofu.syntax.console' for a fancy functions to work with it.
Let's make our cat program a little nicer by adding one import and removing duplicates.
import tofu.syntax.console._ //this one gets you all the goodies
object catWithSyntax extends IOApp {
override def run(args: List[String]): IO[ExitCode] =
catProgramStep[IO].foreverM
def catProgramStep[F[_]: FlatMap: Console]: F[Unit] = for {
input <- readStrLn
_ <- if (input != "dog")
putStrLn(input)
else
putErrLn("Do not scare the cat!")
} yield ()
}
So when ran it works like that:
cat
>cat
kitten
>kitten
dog
>Do not scare the cat! //written in red because of 'putErrLn'
Show
There are integrations with cats.Show
typeclass.
Let's say we have some case class and a custom Show
instance for it:
import cats.Show
case class Person(name: String)
implicit val personShow: Show[Person] = Show.show[Person](p => s"this person has name ${p.name}")
You can use two methods to put a person to console
val cat: Person = Person("Cat")
putToStringLn[IO](cat).unsafeRunSync() //uses .toString
putShowLn[IO, Person](cat).unsafeRunSync() //uses Show from scope
Puts
Also, it is possible to print a interpolated string in a nice way using puts"..."
:
def putCat[F[_]: Console]: F[Unit] = puts"$cat, not a Person"
putCat[IO].unsafeRunSync()
As you can see it uses Show
inside so you'd want to have instances for values inside puts
-string.
The error message when you do not have Show
instance in scope look like that:
[error] ...: type mismatch
[error] found : tofu.cat.Person
[error] required: cats.Show.Shown
[error] def putCat[F[_]: Console] = puts"$cat that is not a Person"
[error] ^