空列表vs可能表示Haskell中失败的计算
在会议上,Erik Meijer反复声明,对于失败的计算,使用空列表vs可能表示Haskell中失败的计算,haskell,maybe,Haskell,Maybe,在会议上,Erik Meijer反复声明,对于失败的计算,使用Maybe类型是不应该做的;相反,我们应该使用空列表 我的理解是,也许类型是一件好事,我们应该使用它。然而,一个列表似乎可以模拟一切,也许可以模拟更多。。。那么,我们为什么需要类型呢 然而,一个列表似乎可以模拟所有可能可以模拟的东西,甚至更多 “和更多”是使用可能的一个很好的理由。作为列表的使用者,您需要能够处理零、一个或多个值。作为的消费者,您只需要能够处理零或一个值。因此,在多个值没有意义的情况下,最好使用也许,以便静态地知道不会
Maybe
类型是不应该做的;相反,我们应该使用空列表
我的理解是,也许
类型是一件好事,我们应该使用它。然而,一个列表似乎可以模拟一切,也许可以模拟更多。。。那么,我们为什么需要类型呢
然而,一个列表似乎可以模拟所有可能可以模拟的东西,甚至更多
“和更多”是使用可能的一个很好的理由。作为列表的使用者,您需要能够处理零、一个或多个值。作为的消费者,您只需要能够处理零或一个值。因此,在多个值没有意义的情况下,最好使用也许
,以便静态地知道不会得到无意义的值。列表可以模拟任意数量的结果。另一方面,可能只对一个结果建模,或者根本不建模
考虑以下功能:
f1 :: A -> [B]
f2 :: B -> [C]
f3 :: C -> [D]
f4 :: D -> [E]
不清楚返回的元素有多少。那么,如果你给它们排序,会发生什么呢
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
就这样。现在回到Meijer的声明:
Erik Meijer反复声明,对于失败的计算,使用Maybe类型是不应该做的;相反,我们应该使用空列表
没有任何额外的客观推理,这只是个人偏好。我可以告诉大家,fmap
比map
好,这是我们应该做的事情。在这一点上,你要么相信我,要么问问题。如果他在演讲中没有说清楚,直接问他。另一个有价值的观点是,这只是错误处理单子的最简单的例子,可以用一种方便一致的方式来表示和组成“可破坏的”计算(另一个例子是或者纯粹的例外).列表单子在语义上是不同的(它表示非确定性计算),并且只有在空/单例情况下才具有类似的行为,如上所示。支持列表:
- 额外的值不是问题。当有多个结果时,客户端可以选择忽略列表的其余部分
- 仅使用列表避免了在必须混合使用Maybe和list时在Maybe和list之间进行繁琐的转换。无需使用
listToMaybe
或maybeToList
- 只变成
concat
(或join
)
一个可能的问题是,对列表monad重复使用(>>=)
,可能会创建非常大的列表。但是,Haskell很懒惰。如果我们只使用第一个元素,则不会计算列表的其余部分
>>> head (let xs = [1..1000000] in xs >>= \_ -> xs >>= \_ -> xs)
1
我想我会加入唱诗班,说我不能评价梅杰的建议,除非我得到他支持的所有细节。对我来说,这似乎很简单:
对于返回0或1结果的函数,可以使用
对于返回0或更多结果的函数,请使用[]
如果您需要在做出不同选择的函数之间进行混合和匹配,一个选项是使用类似于listToMaybe::[a]->maybeToList和maybeToList::maybeToList::maybeToList->[a]
from这样的函数来调整以一种样式编写的函数,使其在另一种样式下工作
如果您想延迟选择是否使用可能
或[]
,您可以使用备选方案
或MonadPlus
类
第4点示例:
import Control.Applicative(纯,可选(…)
safeDiv::(备选方案f,分数a,等式a)=>a->a->f a
safeDiv_u0=空
安全分区x y=纯(x/y)
{-
>>>safeDiv 5 2::可能是浮动的
只有2.5
>>>safeDiv 5 0::可能是浮动的
没有什么
>>>safeDiv 5 2::[浮动]
[2.5]
>>>safeDiv 5 0::[浮动]
[]
-}
bothSqrt::(备选方案f,浮动a)=>a->f a
bothSqrt x=设x'=sqrt x
纯x'纯(-x')
{-
>>>可能是浮动的
只有2.236068
>>>两个QRT 5::[浮动]
[2.236068,-2.236068]
>>>bothSqrt 5>>=翻转safeDiv 2::可能是浮动
只有1.118034
>>>>bothSqrt 5>>=翻转安全分区2::[Float]
[1.118034,-1.118034]
-}
一个例子能说明千言万语。另外,你提到的课程的链接也很好。@Jubobs:可能:。@Zeta当然可以,但添加链接的责任在OP:)对于那些非常成功的计算,列表是一个很好的选择,它们返回多个值:-)这是一个我没有想到的好点。但是作为一些人e使用一个接受列表的函数,如果它丢掉了尾部,我想我会有点惊讶。例如,想象一下如果map
被实现为map f[]=[];map f(x:xs)=[fx]
。
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]
-}