Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Simple Scala example of a pure functional program that does I/O
/*
* Referentially transparent program to print the size of files.
*
* Techniques inspired by:
* "Dead-Simple Dependency Injection"
* Rúnar Óli Bjarnason
* Northeast Scala Symposium, 2012
*
* To run: "scala filesizerer.scala"
* When prompted, enter a file name.
* If the file exists, its size will be printed,
* otherwise prints "Size: Unknown"
*/
/**
* An IO action to be performed.
* Every action has a "link" to the next step,
* forming a chain of actions.
* IOAction is a functor.
*/
sealed abstract class IOAction[B] {
protected type A
protected val link: A => B
protected def dup[C](f: A => C): IOAction[C]
final def map[C](f: B => C): IOAction[C] = dup(f compose link)
}
final case class ReadConsole[B](protected val link: String => B) extends IOAction[B] {
protected type A = String
protected def dup[C](f: String => C) = ReadConsole(f)
}
final case class WriteConsole[B](protected val link: Unit => B, msg: String) extends IOAction[B] {
protected type A = Unit
protected def dup[C](f: Unit => C) = WriteConsole(f, msg)
}
final case class FileSize[B](protected val link: Option[Long] => B, file: String) extends IOAction[B] {
protected type A = Option[Long]
protected def dup[C](f: Option[Long] => C) = FileSize(f, file)
}
/**
* A monad for limited I/O. The dependency on IOAction can be abstracted over,
* which gives you a "free" monad for any functor,
* but I left that out to keep the demo simple.
*/
sealed abstract class SimpleIO[A] {
def flatMap[B](f: A => SimpleIO[B]): SimpleIO[B]
final def map[B](f: A => B): SimpleIO[B] = flatMap((IODone.apply[B] _) compose f)
/**
* A convenience version of flatMap that ignores the result of the
* preceeding action (which is commonly Unit anyway).
* Like a functional version of Java's semicolon.
* `>>` in Haskell.
*/
final def andThen[B](io: SimpleIO[B]) = flatMap(Function.const(io))
}
/**
* The end of a chain of actions holding the resulting value of the chain.
*/
final case class IODone[A](a: A) extends SimpleIO[A] {
def flatMap[B](f: A => SimpleIO[B]) = f(a)
}
/**
* Represents more actions to be performed.
*/
final case class IOMore[A](next: IOAction[SimpleIO[A]]) extends SimpleIO[A] {
def flatMap[B](f: A => SimpleIO[B]) = IOMore(next.map(_ flatMap f))
}
val readConsole = IOMore[String](ReadConsole(IODone.apply _))
def writeConsole(msg: String) = IOMore[Unit](WriteConsole(IODone.apply _, msg))
def fileSize(file: String) = IOMore[Option[Long]](FileSize(IODone.apply _, file))
val terminate = IODone(())
/**
* The part of our program that repeats, looping using recursion.
*/
val loop: SimpleIO[Unit] = for {
_ <- writeConsole("Enter a file name or q to quit:")
s <- readConsole
_ <- if ("q" equalsIgnoreCase s) {
terminate
}
else for {
size <- fileSize(s)
_ <- writeConsole("Size: " +
(size map ("%,12d".format(_)) getOrElse ("%12s".format("Unknown"))))
_ <- loop
} yield ()
} yield ()
/**
* Our referentially transparent program.
*/
val program: SimpleIO[Unit] =
writeConsole("Welcome to file sizerer") andThen loop
/*
All the above code is referentially transparent.
Only the code below has side-effects.
*/
/**
* A "runner" to execute any `SimpleIO` program.
* We could write other implementations, such as a mock
* runner for unit tests, or a GUI, or using HTTP instead
* of the file system. You get the idea.
*/
@annotation.tailrec
def run[A](p: SimpleIO[A]): A = {
p match {
case IODone(a) => a
case IOMore(ReadConsole(link)) => run(link(readLine()))
case IOMore(WriteConsole(link, msg)) => {
println(msg)
run(link(()))
}
case IOMore(FileSize(link, name)) => {
val f = new java.io.File(name)
val length = f.length
run(link(if (length == 0) None else Some(length)))
}
}
}
run(program)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.