Option/Maybe Monad相比Functor有哪些具体使用优势
首先明确结论:二者的能力差异和语言层面的类型集成设计没有任何关系,是抽象模型本身的计算能力差,在任何语言里复现这两种抽象都会得到一致的能力边界。
我们先把边界划清楚,避免重复无意义的层级结论:
- 仅实现Functor规范的Option类型,只提供
fmap能力:如果当前实例是有值的Some(a),就把a传入一个返回普通非Option值的函数,再把函数返回值包成Some返回;如果是None就直接透传。 - 实现Monad规范的Option类型,在Functor能力之外额外提供
flatMap(也就是bind)能力:如果当前实例是有值的Some(a),就把a传入一个本身就返回Option值的函数,直接把函数返回的Option结果拍平成单层返回;如果是None就直接短路返回None。
你只要实际写两次业务代码就能立刻感知到不可替代性:所有需要串联「可能返回空/失败的操作」的场景,纯Functor能力完全不够用。
举个最常见的业务链路例子:
- 从HTTP请求里提取用户ID参数,可能因为参数缺失返回
Option[Int] - 用用户ID查数据库拿用户信息,可能因为用户不存在返回
Option[User] - 从用户信息里取绑定的手机号,可能因为用户没填返回
Option[String]
如果你只有Functor的fmap,串联这三个操作会直接得到嵌套的Option[Option[Option[String]]],根本没法直接用,你必须手动写模式匹配/判空逻辑,一层层拆包装,三层操作就会出现三层嵌套的判断分支,链路越长样板代码越多,还很容易因为漏判空抛出空指针异常。
// 纯Functor能力下,你不得不写的嵌套判空样板 val phone: Option[String] = getUserIdFromRequest() match { case Some(id) => queryUserById(id) match { case Some(user) => getPhoneFromUser(user) match { case Some(phone) => Some(phone) case None => None } case None => None } case None => None }
而Option Monad的flatMap本质就是把上面这段重复的嵌套判空逻辑做了统一抽象,你不需要手写任何匹配判断,就能直接串联所有返回Option的操作,自动处理空值短路,最终永远得到单层的Option结果,不会出现嵌套:
// Monad能力下的串联写法,没有任何冗余样板 val phone: Option[String] = getUserIdFromRequest() .flatMap(id => queryUserById(id)) .flatMap(user => getPhoneFromUser(user)) // 大多数函数式语言提供的for/do语法糖,本质就是对上面flatMap链的包装,可读性更强 val phoneFor: Option[String] = for { id <- getUserIdFromRequest() user <- queryUserById(id) phone <- getPhoneFromUser(user) } yield phone
这个能力和你已经熟悉的IO Monad、List Monad的优势逻辑完全一致,只是抽象的控制流场景不同:
- IO Monad解决的是副作用操作串联的嵌套问题,自动串行执行、短路异常,避免出现
IO[IO[A]] - List Monad解决的是多返回值操作串联的嵌套问题,自动做结果扁平化,避免出现
List[List[A]] - Option Monad解决的是可能失败/返回空值的操作串联的嵌套问题,自动做失败分支短路,避免出现
Option[Option[A]]
另外有一个Functor绝对不可能实现的独有能力:支持根据前序步骤的返回值,动态决定后续计算的成功/失败状态。Functor的fmap只能把普通值包回Option容器,永远不会主动产生None,你没法在映射函数里根据业务规则(比如年龄是否在合法区间、用户是否有权限)直接返回失败结果,只要你需要在链路中间产生新的失败分支,就必须用到Monad的flatMap能力。
内容的提问来源于stack exchange,提问作者SmoothTraderKen

