ase (nw, a) => f(a)(nw) }) ... /** Construct an IO action from a world-transition function. */ def io[A](f: Tower[IvoryTower] => Trampoline[(Tower[IvoryTower], A)]): IO[A] =
new IO[A] { private[effect] def apply(rw: Tower[IvoryTower]) = Free(() => f(rw)) }
可以看得出io[A](...)就是IO的构建器(constructor)。IO[A]类型是Free[Function0,A] = Free(() => f(rw)),最终就是一个Trampoline,这个可以从下面的apply看得出来:
object IO extends IOInstances { def apply[A](a: => A): IO[A] = io(rw => return_(rw -> a)) ...
上面的Tower[IvoryTower]是状态切换函数的输入类型,不参与实际运算(不在任何运算中调用如: rw -> a),起一种状态标签作用(state tag)。主要提供给编译器(compiler)做类型安全用。实际上这个状态切换函数就是一个延迟运算值 => A。io把这个值挂到Free的suspend状态:
/** Suspend the given computation in a single step. */ def return_[S[_], A](value: => A)(implicit S: Applicative[S]): Free[S, A] = liftF[S, A](S.point(value))
再看看IO的运算方式:
sealed abstract class IO[A] { private[effect] def apply(rw: Tower[IvoryTower]): Trampoline[(Tower[IvoryTower], A)] import IO._ /** * Runs I/O and performs side-effects. An unsafe operation. * Do not call until the end of the universe. */ def unsafePerformIO(): A = apply(ivoryTower).run._2
先用apply建Trampoline,再运行Free.run(Trampoline[A]=Free[Function0,A])。注意,我们并没有采用这个Tower[IvoryTower]。再者,函数unsafePerformIO是通过private函数apply先构建了Trampoline后再进行运算的。换言之IO Monad的用户是无法自定义算法(interpreter)的。我们前面曾经把Free描述成可以自定义F[A]编程语言的数据结构,那么IO[A]就是一种固定的FP编程语言,它只有unsafePerformIO一种算法(interpreter)。
IO Monad可以使我们更方便地在IO这个壳子里进行我们熟悉的行令编程(imperative programming),因为我们只需要把行令程序直接放进IO里就行了。看看下面这些例子:
1 val hello = print("hello ").point[IO] //> hello : scalaz.effect.IO[Unit] = scalaz.effect.IO$$anon$6@145eaa29
2 val world = IO (print("world,")) //> world : scalaz.effect.IO[Unit] = scalaz.effect.IO$$anon$6@57c758ac
3 val howareyou = io {rw => return_(rw -> println("how are you!"))} 4 //> howareyou : scalaz.effect.IO[Unit] = scalaz.effect.IO$$anon$6@a9cd3b1
5 val greeting = hello |+| world |+| howareyou //> greeting : scalaz.effect.IO[Unit] = scalaz.effect.IO$$anon$6@481a996b
6 greeting.unsafePerformIO //> hello world,how are you!
这个例子示范了用三种方式把副作用语句print升格成IO。不要被IO[A]的IO字面误导了,IO[A]的这个A不一定是副作用命令,任何行令编程使用的语句都可以放人IO[_],包括变量申明、赋值、文件读写等。所以我们说IO Monad就是在FP模式中进行行令编程的通用方式。可以想象我们可能会在IO这个壳子内进行我们熟悉的程序编写。那么IO Monad到底能不能符合在FP环境内的行令编程要求呢?我们可以用几个例子来对流程控制(flow control),跟踪记录(logging)即异常处理(exception handling)等方面进行测试示范:
1 import scalaz._ 2 import Scalaz._ 3 import effect._ 4 import IO._ 5 import Free._ 6 import scala.language.higherKinds 7 import scala.language.implicitConversions 8
9 object IOPrg { 10 def div(dvdn: Int, dvsor: Int): IO[Int] =
11 IO(dvdn / dvsor) 12 val ioprg: IO[Int] = for { 13 _ <- putLn("enter dividend:") 14 dvdn <- readLn 15 _ <- putLn("enter divisor:") 16 dvsor <- readLn 17 quot <- div(dvdn.toInt, dvsor.toInt) 18 _ <- putLn(s"the result:$quot") 19 } yield quot 20 } 21
22 object IOMonadDemo extends App { 23 import IOPrg._ 24 ioprg.unsafePerformIO() 25 }
"enter dividend:"
10
"enter divisor:"
5
"the result:2"
ioprg是一段包含了带副作用语句的程序。所有副作用延迟到unsafePerformIO()才正真产生。现在我们先示范流程控制,试着提早跳出这个for-loop。这不就是option在for-comprehension的作用吗。我们需要在IO[A]这种Monad之上增加Option的作用,可以用Monad Transform