Maybe 函数
“maybe” 函数的本质
- 许多读者承认自己经常会写一些“maybe”函数:它们有时什么也不做,或者返回 null,并认为这篇批评很有帮助。
- 也有人认为文章把两件事混为一谈:
- 行为被隐藏 / 与其类型不一致的函数(例如,在未登录时悄悄不执行任何操作)。
- 那些必须有时失败的函数(例如,解析、用户未登录),因此不可能总是返回一个值。
- 几位评论者认为,真正的问题是撒谎的函数签名和隐藏的全局状态检查,而不是“可选性”本身。
替代方案:异常、null 与显式选项
- 有人主张用异常来表达“为什么不行”的信息,而不是用 null,但也有人批评异常在类型签名中不可见,且调用方很容易遗漏。
- 关于什么才算“异常”存在争论:“用户未登录”是正常流程还是错误?这取决于函数契约,意见并不一致。
- 许多人更喜欢显式的可选/可空返回类型(
T | null、Option<T>、TryParse风格),这样调用者就被迫处理缺失值。 - 对于集合,返回空列表而不是 null 被广泛赞同。Null Object 模式偶尔有用,但如果它掩盖了真正的失败,就会很危险。
类型系统、单子与语言支持
- 多条评论将这个话题与“Maybe vs null”、“parse, don’t validate”以及管道/铁路导向编程联系起来:把失败建模为一等类型,并把处理向上推到调用栈。
- 许多人指出,文章里的“Maybe”其实只是一个函子(map),并不是完整的单子(缺少 flatMap/bind)。
- 讨论还提到单子/Option 会变得“有传染性”:一旦引入,很多函数都必须处理它们。带有语法糖的语言(Rust 的
?、Haskell 的 do-notation、可选链)会让这件事更愉快;而在 JS/Java/Go 中则可能很笨拙。
设计模式与状态结构
- 有很强的倾向把登录/有效性检查移到边界上:
- 使用显式的 session/LoggedInUser 参数,而不是全局状态。
- 确保假定前提条件的函数接收能够编码这些前提的参数,避免在内部出现“maybe”行为。
- 一些人认为 maybe 函数的泛滥,是调用图设计不佳和全局状态分散的信号。
分歧与实用主义
- 也有人拒绝对 maybe 函数采取绝对禁止的立场,认为像
maybeShowReminderDialog这样的模式在实践中是有用且符合 DRY 原则的。 - 另一些人则强调,对于程序员错误应当快速失败(崩溃或在编译期被拒绝),而不是过度防御性地处理 maybe。