type info
val getIdByName: string -> int option
val getInfoById: int -> info option
val getAgeByInfo: info -> int option
那么,一个
val getAgeByName: string-> int option
的函数的写法就是
(* 例1 *)
let getAgeByName name=
match getIdByName name with
| None-> None
| Some id->
match getInfoById id with
| None-> None
| Some info->
match getAgeByInfo with
| None-> None
| Some age-> Some age
我们引入一个 bind 函数来缓解这个问题
let bind value func=
match value with
| None-> None
| Some value-> func value
let (>>=)= bind
于是就能以如下方式写 getAgeByName 函数了
(* 例2 *)
let getAgeByName name=
getIdByName name >>= fun id->
getInfoById id >>= fun info->
getAgeByInfo info
每行后面还要拖个小尾巴,而且代码也不直观。
在 ok_monad 的帮助下。我们可以这样写
(* 例3 *)
let getAgeByName name=
let%m id= getIdByName name in
let%m info= getInfoById id in
getAgeByInfo info
好了,结果的代码不用考虑中间的出错信息啦。只要其中的任何一步出错(返回 None), 函数就会直接返回 None, 不再继续后续的调用。
结合 bind 函数,例2 的方法即是 CPS(continuation-passing style). 以此,将控制流明确地暴露出来。于是 bind 里面就可做控制的选择了。这里的选择非常简单,也就是,根据结果选择是否继续这个流的运行,或是返回出错。
上面的例子,返回结果是用了 'a option 类型,返回 None 为出错,返回 Some 'a 的话, 'a 类型即是结果。如果换用 extensible data type 来表达的话。我们其实是实现了异常机制。
既然 CPS 可以将控制流明确的暴露出来并加以控制,如上文所述可方便实现异常机制。那么我们也可以以此实现用户级线程,做到百万甚至千万的并发。实现原理可见 fiber
如 例2 所示,虽然 CPS 的代码可以暴露出控制流以实现很多奇妙的事情,但它和普通的代码长得不一样,可读性会差很多。
例3 的代码并没用 CPS 方式写,但也暴露出了控制流给 bind, 这就是语法转换技术的一个应用了。 let 后面的 %m 标记告诉编译器调用语法处理器。于是在语法处理器里就可以进行自动的普通代码转换成 CPS 代码了。
ok_monad 简介 extension point, 以实现 CPST 做为一个小例子,想了想,可能别人也要用,就开了个项目