Instead of reading the second half of the paper, we can get the gist by reading Towards an Effect System in Scala, Part 2: IO Monad by Rúnar (@runarorama):
While ST gives us guarantees that mutable memory is never shared, it says nothing about reading/writing files, throwing exceptions, opening network sockets, database connections, etc.
Here’s the typeclass contract for ST
again:
sealed trait ST[S, A] {
private[effect] def apply(s: World[S]): (World[S], A)
}
And the following is the typeclass contract of IO
:
sealed trait IO[+A] {
private[effect] def apply(rw: World[RealWorld]): Trampoline[(World[RealWorld], A)]
}
If we ignore the Trampoline
part, IO
is like ST
with state fixed to RealWorld
. Similar to ST
, we can create IO
monads using the functions under IO
object. Here’s Hello world.
scala> import scalaz._, Scalaz._, effect._, IO._
import scalaz._
import Scalaz._
import effect._
import IO._
scala> val action1 = for {
_ <- putStrLn("Hello, world!")
} yield ()
action1: scalaz.effect.IO[Unit] = scalaz.effect.IOFunctions$$anon$4@149f6f65
scala> action1.unsafePerformIO
Hello, world!
Here are the IO actions under IO
:
/** Reads a character from standard input. */
def getChar: IO[Char] = ...
/** Writes a character to standard output. */
def putChar(c: Char): IO[Unit] = ...
/** Writes a string to standard output. */
def putStr(s: String): IO[Unit] = ...
/** Writes a string to standard output, followed by a newline.*/
def putStrLn(s: String): IO[Unit] = ...
/** Reads a line of standard input. */
def readLn: IO[String] = ...
/** Write the given value to standard output. */
def putOut[A](a: A): IO[Unit] = ...
// Mutable variables in the IO monad
def newIORef[A](a: => A): IO[IORef[A]] = ...
/**Throw the given error in the IO monad. */
def throwIO[A](e: Throwable): IO[A] = ...
/** An IO action that does nothing. */
val ioUnit: IO[Unit] = ...
}
We can also make our own action using the apply
method under IO
object as follows:
scala> val action2 = IO {
val source = scala.io.Source.fromFile("./README.md")
source.getLines.toStream
}
action2: scalaz.effect.IO[scala.collection.immutable.Stream[String]] = scalaz.effect.IOFunctions$$anon$4@bab4387
scala> action2.unsafePerformIO.toList
res57: List[String] = List(# Scalaz, "", Scalaz is a Scala library for functional programming., "", It provides purely functional data structures to complement those from the Scala standard library., ...
TESS2:
Composing these into programs is done monadically. So we can use
for
-comprehensions. Here’s a program that reads a line of input and prints it out again:
def program: IO[Unit] = for {
line <- readLn
_ <- putStrLn(line)
} yield ()
IO[Unit]
is an instance ofMonoid
, so we can re-use the monoid addition function|+|
.
Let’s try this out:
scala> (program |+| program).unsafePerformIO
123
123