语法转换技术的一点应用

一串函数,每个地方都需要处理可能的出错?我们需要异常机制。

考虑如下几个声明

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 来解决这个问题

在 ok_monad 的帮助下。我们可以这样写

(* 例3 *)
let getAgeByName name=
  let%m id= getIdByName name in
  let%m info= getInfoById id in
  getAgeByInfo info

好了,结果的代码不用考虑中间的出错信息啦。只要其中的任何一步出错(返回 None), 函数就会直接返回 None, 不再继续后续的调用。

CPS

结合 bind 函数,例2 的方法即是 CPS(continuation-passing style). 以此,将控制流明确地暴露出来。于是 bind 里面就可做控制的选择了。这里的选择非常简单,也就是,根据结果选择是否继续这个流的运行,或是返回出错。

上面的例子,返回结果是用了 'a option 类型,返回 None 为出错,返回 Some 'a 的话, 'a 类型即是结果。如果换用 extensible data type 来表达的话。我们其实是实现了异常机制。

用户级线程

既然 CPS 可以将控制流明确的暴露出来并加以控制,如上文所述可方便实现异常机制。那么我们也可以以此实现用户级线程,做到百万甚至千万的并发。实现原理可见 fiber

CPS transformation

如 例2 所示,虽然 CPS 的代码可以暴露出控制流以实现很多奇妙的事情,但它和普通的代码长得不一样,可读性会差很多。
例3 的代码并没用 CPS 方式写,但也暴露出了控制流给 bind, 这就是语法转换技术的一个应用了。 let 后面的 %m 标记告诉编译器调用语法处理器。于是在语法处理器里就可以进行自动的普通代码转换成 CPS 代码了。

ok_monad

ok_monad 简介 extension point, 以实现 CPST 做为一个小例子,想了想,可能别人也要用,就开了个项目