在Haskell中返回空值而不限制多态性

时间:2011-11-10 10:47:56

标签: haskell

我正在关注“真实世界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”多态的。

5 个答案:

答案 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中比其他语言更难)这是大多数列表函数使用的解决方案,例如headtail等等......

另一个解决方案是明确声明该函数可能无法生成良好的数据。 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了解更多可能性。