我正在关注“真实世界Haskell”,并且正在进行第2章,其中练习是创建一个“lastButOne”函数,它返回列表中倒数第二个值。我原来的代码是:
lastButOne xs = if null (tail (tail xs))
then head xs
else lastButOne (tail xs)
除非我给它一个只有1或2个项目的列表,否则哪个工作正常。所以我修改了它:
lastButOne xs = if null xs || null (tail xs)
then head xs
else
if null (tail (tail xs))
then head xs
else lastButOne (tail xs)
哪个检查它是否只有1个或2个项目,但如果因为调用头部而只有1个项目则会失败。我的问题是我不能想到除了“头xs”以外的任何其他东西,理想情况下我希望它返回null或类似的东西,但我找不到允许该函数仍然存在的“null”多态的。
答案 0 :(得分:28)
也许您正在寻找Maybe
类型:
lastButOne :: [a] -> Maybe a
lastButOne [x,_] = Just x
lastButOne (_:y:ys) = lastButOne (y:ys)
lastButOne _ = Nothing
答案 1 :(得分:10)
[我]我真的希望它返回null或类似的东西,但我找不到允许函数仍然是多态的“null”。
关于如何使这项工作已经发布了许多答案,所以让我回答一个更为基本的问题:“为什么不存在'空'?”
这是Haskell中类型检查器的哲学的一部分。大多数语言都包含某种形式的特殊值,表示错误或缺少数据。示例包括0
,-1
""
(空字符串)JavaScripts null
或Ruby的nil
。问题是,这会产生一整类潜在的错误,其中调用函数只处理“好”值,当返回特殊值时程序崩溃或更糟糕的数据会破坏数据。还有一个问题是将特殊值作为常规值 - 即0
实际上是一个数字而不是失败状态。
在您的问题中,可以设置问题,“调用lastButOne
的函数是否知道如何处理null
?”
对于某些其他格式良好的输入没有有效答案的问题在Haskell中非常常见,并且有两个典型的解决方案。第一个来自Erlang:快速而干净地失败。在此解决方案下,调用者有责任确保数据“良好”。如果不是程序因错误而死(或者异常被捕获,但这在Haskell中比其他语言更难)这是大多数列表函数使用的解决方案,例如head
,tail
等等......
另一个解决方案是明确声明该函数可能无法生成良好的数据。 Maybe a
类型是这些解决方案中最简单的类型。它允许您返回值Nothing
,其值与您正在寻找的null
非常相似。但这需要付出代价。您的函数的类型签名变为lastButOne :: [a] -> Maybe a
。现在每个调用函数必须不是a
而是Maybe a
,并且在得到它的答案之前,它必须告诉Haskell如果答案是Nothing
将会怎么做。
答案 2 :(得分:5)
嗯,这取决于您实际想要做多少错误检查。
我怀疑,鉴于这只是在第2章,你可能想要忽略这个边缘情况。 Haskell本身就抛出异常:
Prelude> head []
*** Exception: Prelude.head: empty list
另一种选择,如果这是您需要处理的情况,则将lastButOne
的返回类型更改为Maybe a
并在其工作时返回Just (head xs)
,以及Nothing
失败时。但这意味着你总是要处理Maybe
monad内部的生活,这可能并不理想(因为你需要“展开”Just
值才能使用它)。
答案 3 :(得分:3)
为什么不使用模式匹配来处理只有1个或2个元素的情况? 类似的东西:
lastButOne :: [a] -> Maybe a
lastButOne [x,y] = Just x
lastButOne (x:y:t) = lastButOne (y:t)
lastButOne _ = Nothing
答案 4 :(得分:0)
@Andrew Jaffe提到的“扩展错误处理”的例子可能是:
{-# LANGUAGE FlexibleContexts #-} module Main where
import Control.Monad.Error
lastButOne :: MonadError String m => [a] -> m a
lastButOne (_ : y : []) = return y
lastButOne (_ : t) = lastButOne
lastButOne _ = throwError "Error in lastButOne"
在这种情况下,可以在成功时返回Either
类型:Right x
,如果失败则返回Left "Error in lastButOne"
,但MonadError String m
的许多其他实例都是可能的。
另请阅读8 ways to report errors in Haskell了解更多可能性。