你说得对,但是单子是——后面忘了。同时,逐步发觉 Rust Try 的真相。
在学 Haskell 前曾经写过一篇关于 Rust Try
特征的文章, 意识到 Rust 的 Try
特征其实描述了 Haskell Monad
类的一个特例。
根据定义,实现一个 Haskell Monad
需要实现两个方法:
return :: a -> m a
bind :: m a -> (a -> m b) -> m b
我们尝试定义一个用于表示计算的泛型类型 m
,它接受两个类型参数 b
(break
) 和 c
(continue
),其中 c
表示预期结果的类型,b
表示意外退出的类型。据此改写 return
的签名:
m :: * -> * -> *
return :: c -> m b c
这里的 return
把 c
类型的预期结果转换为 m
,因此也可以称作 from_output
。对应地,增加一个函数把意外结果转换为 m
。我们这里不把它定义为 m b c
上的方法,因为一个意外结果 b
转换为 m
的过程对于计算结果的类型应该可以泛化。
from_residual :: b -> m b t
由于 m
表示的计算可能是预期的(c
)或者意外的(b
),我们创建这两种类型的一个简易 sum type ControlFlow
,并引入一个 m b c
的方法 branch
,用于将 m
转换成为这两个类型之一。m
比我们的 ControlFlow
可能要复杂得多。
data ControlFlow b c
= Break b
| Continue c
branch :: m b c -> ControlFlow b c
据此,我们可以导出 bind
。这里的 from_residual
把 b
转换成了新的 m b c'
类型。如果 b
有对应的 from_residual
实现,那么我们的 bind
就可以被定义为 m b c
的导出方法。
bind :: m b c -> (c -> m b c') -> m b c'
bind val compute = case branch val of
Continue output -> compute output
Break residual -> from_residual residual
以上我们描述的 m b c
恰好对应 Rust 的 Try
特征,return
和 branch
刚好就是 Rust Try
的两个方法,而 b
上的 from_residual
刚好就是 FromResidual
特征的方法:
trait Try: FromResidual<Self::B> {
type C; // Output
type B; // Residual
fn from_output(Self::C) -> Self; // return
fn branch(self) -> ControlFlow<B, C>;
}
trait FromResidual<B> {
fn from_residual(B) -> Self;
}
同时,return
加上我们导出的 bind
恰好对应 Haskell Monad
的定义。和 Monad
的区别是,我们定义的 Try
的 kind 是 * -> * -> *
而不是更一般的 * -> *
,需要显示指定一个类型参数 b
。
Try
特征对应的 ?
和 try
块也一定程度上对应了 Haskell 为 Monad
设计的 do
表达式:
try {
let val = computation?;
let result = do_something_to(val)?;
result // Ok-wrapping: 自动调用 return / from_output
}
do
val <- computation
result <- do_something_to val
return result