Measuring timing in a free algebra

April 06, 2017

Let's build on the ConsoleIO example from Free Coyoneda in scalaz 7.2 by adding a command to get the current time. We can use this command to measure the evaluation of portions of a program.

Here's what we had before:

import java.util.Date
import scalaz.Free
import scalaz.Free.liftF
import scalaz.effect.IO
import scalaz.~>

sealed trait ConsoleOp[A]
type ConsoleIO[A] = Free[ConsoleOp, A]

case object Readln extends ConsoleOp[String]
val readln: ConsoleIO[String] = liftF(Readln)

case class Writeln(x: String) extends ConsoleOp[Unit]
def writeln(x: String): ConsoleIO[Unit] = liftF(Writeln(x))

case class Person(name: String)

val getPerson: ConsoleIO[Person] =
  for {
    _     <- writeln("What is your name?")
    name  <- readln
  } yield Person(name)

def greetPerson(person: Person): ConsoleIO[Unit] =
  writeln(s"Hello, ${person.name}!")

Let's add a new command, GetTime, that we can use to get the current time during evaluation:

case object GetTime extends ConsoleOp[Date]
val getTime: ConsoleIO[Date] = liftF(GetTime)

val program: ConsoleIO[Unit] =
  for {
    s <- getTime
    p <- getPerson
    e <- getTime
    _ <- writeln(s"That took ${e.getTime - s.getTime} ms.")
    _ <- greetPerson(p)
  } yield ()

val consoleRunner: ConsoleOp ~> IO =
  new (ConsoleOp ~> IO) {
    def apply[A](fa: ConsoleOp[A]): IO[A] =
      fa match {
        case Readln     => IO(readLine)
        case Writeln(x) => IO(println(x))
        case GetTime    => IO(new Date())
      }
  }

program.foldMap(consoleRunner).unsafePerformIO

This code can be run with sbt and codedown:

/***
libraryDependencies += "org.scalaz" %% "scalaz-core" % "7.2.10"
libraryDependencies += "org.scalaz" %% "scalaz-effect" % "7.2.10"
*/
$ curl -sL earldouglas.com/posts/freetime.md | codedown scala > freetime.scala
$ sbt -Dsbt.main.class=sbt.ScriptMain freetime.scala
What is your name?
James
That took 1115 ms.
Hello, James!