Tagless Final via Cats

sbt configuration

/***
libraryDependencies += "org.typelevel" %% "cats-core" % "2.3.0"
*/

Usage

trait Algebra[F[_]] {
  def readEnv(name: String): F[String]
  def readLn: F[String]
  def write(output: String): F[Unit]
}
class Program[F[_]: cats.Monad](x: Algebra[F]) {

  // `flatMap` extension method
  import cats.implicits.toFlatMapOps

  // `map` extension method
  import cats.implicits.toFunctorOps

  def enProgram(): F[Unit] =
    for {
      _    <- x.write("What's your name? ")
      name <- x.readLn
      _    <- x.write(s"Hello, ${name}!\n")
    } yield ()

  def esProgram(): F[Unit] =
    for {
      _    <- x.write("¿Cómo te llamas? ")
      name <- x.readLn
      _    <- x.write(s"¡Hola, ${name}!\n")
    } yield ()

  def program: F[Unit] =
    for {
      lang <- x.readEnv("LANG")
      _    <- if (lang.startsWith("es")) {
                esProgram
              } else {
                enProgram
              }
    } yield ()
}
object Interpreter extends Algebra[cats.Id] {
  override def readEnv(name: String): cats.Id[String] = sys.env(name)
  override def readLn: cats.Id[String] = scala.io.StdIn.readLine()
  override def write(output: String): cats.Id[Unit] = print(output)
}

new Program(Interpreter).program

Demo

This file is literate Scala, and can be run using Codedown:

$ curl https://earldouglas.com/posts/effect-systems/cats.md |
  codedown scala > script.scala
$ LANG=es sbt -Dsbt.main.class=sbt.ScriptMain script.scala
¿Cómo te llamas? James
¡Hola, James!