在Edx Haskell课程中,Erik Meijer反复指出,使用Maybe
类型进行失败的计算不是应该做的事情;相反,应该为此使用空列表。
我的理解是,Maybe
类型是一件好事,我们应该使用它。但是,列表似乎可以对Maybe
可以建模的所有内容进行建模,甚至更多......那么为什么我们需要Maybe
类型呢?
但是,似乎列表可以对所有内容进行建模,例如
"和更多"是使用Maybe
的绝佳理由。作为列表的使用者,您需要能够处理零个、一个或多个值。作为Maybe
的使用者,您只需要能够处理零个或一个值。因此,在多个值没有意义的情况下,最好使用Maybe
,这样您就会静态地知道您不会得到无意义的值。
列表可以对任意数量的结果进行建模。另一方面,Maybe
只对一个结果进行建模,或者根本没有结果。
考虑以下功能:
f1 :: A -> [B]
f2 :: B -> [C]
f3 :: C -> [D]
f4 :: D -> [E]
目前还不清楚有多少元素f1
、f2
、f3
或f4
返回。那么,如果你对它们进行排序会发生什么?
f :: A -> [E]
f s = f1 s >>= f2 >>= f3 >>= f4
结果应包含多少个元素?一?零?我们是否意外地创建了一个包含 n^n(n ~ 输入长度)元素的列表?
但是,如果计算将只返回一个值或根本没有值,则正确的类型会立即为我们提供所有必要的信息:
f1 :: A -> Maybe B
f2 :: B -> Maybe C
f3 :: C -> Maybe D
f4 :: D -> Maybe E
f :: A -> Maybe E
f s = f1 s >>= f2 >>= f3 >>= f4
所以就是这样。现在回到梅杰的声明:
Erik Meijer 反复指出,对失败的计算使用 Maybe 类型不是应该做的事情;相反,应该为此使用空列表。
没有任何额外的客观推理,这只是个人喜好。我可以告诉大家,fmap
比map
好,这是我们应该做的事情。在这一点上,你要么相信我,要么你问问题。如果他在讲座中没有说清楚,直接问他。
赞成列表:
-
额外的值不是问题。当有多个结果时,客户端始终可以选择忽略列表的其余部分。
仅使用列表 可以避免在必须混合使用可能和列表之间进行繁琐的转换。无需
listToMaybe
或maybeToList
.catMaybes
变得只是concat
(或join
)。
一个可能的问题是,对列表 monad 重复使用 (>>=)
可能会创建非常大的列表。然而,哈斯克尔很懒。如果我们只使用第一个元素,则不会计算列表的其余部分。
>>> head (let xs = [1..1000000] in xs >>= _ -> xs >>= _ -> xs)
1
我会在这里加入合唱团,说我无法评估梅杰的建议,除非我看到他论证的所有细节。 对我来说,这似乎很简单:
- 对返回 0 或 1 结果的函数使用
Maybe
。 - 对返回 0 或更多结果的函数使用
[]
。 - 如果您需要在做出不同选择的函数之间进行混合和匹配,一种选择是使用
Data.Maybe
中的listToMaybe :: [a] -> Maybe a
和maybeToList :: Maybe a -> [a]
等函数来调整以一种样式编写的函数以另一种风格工作。 - 如果要延迟选择是使用
Maybe
还是[]
,可以使用Alternative
或MonadPlus
类。
第 #4 点的示例:
import Control.Applicative (pure, Alternative(..))
safeDiv :: (Alternative f, Fractional a, Eq a) => a -> a -> f a
safeDiv _ 0 = empty
safeDiv x y = pure (x / y)
{-
>>> safeDiv 5 2 :: Maybe Float
Just 2.5
>>> safeDiv 5 0 :: Maybe Float
Nothing
>>> safeDiv 5 2 :: [Float]
[2.5]
>>> safeDiv 5 0 :: [Float]
[]
-}
bothSqrt :: (Alternative f, Floating a) => a -> f a
bothSqrt x = let x' = sqrt x
in pure x' <|> pure (-x')
{-
>>> bothSqrt 5 :: Maybe Float
Just 2.236068
>>> bothSqrt 5 :: [Float]
[2.236068,-2.236068]
>>> bothSqrt 5 >>= flip safeDiv 2 :: Maybe Float
Just 1.118034
>>>> bothSqrt 5 >>= flip safeDiv 2 :: [Float]
[1.118034,-1.118034]
-}
也许的另一个有价值的观点是,它只是错误处理monads的最简单情况,可用于以方便和一致的方式表示和组合"可破解"计算(另一个例子是要么和纯粹的例外)。列表monad在语义上是不同的(它表示非确定性计算),并且仅在空/单例情况下具有类似的行为,如上所示。