从一个函数返回两种不同的类型

时间:2012-02-27 09:15:34

标签: function haskell types return-value

如何从单个函数返回多个类型的值?

我想做类似的事情:

take x y  | x == []   = "error : empty list"
          | x == y    = True
          | otherwise = False

我有使用命令式语言的背景。

4 个答案:

答案 0 :(得分:9)

有一个名为Either的类型构造函数,可以让您创建一个可以是两种类型之一的类型。它通常用于处理错误,就像在您的示例中一样。你可以这样使用它:

take x y | x == []   = Left "error : empty list"
         | x == y    = Right True
         | otherwise = Right False

take的类型将类似于Eq a => [a] -> [a] -> Either String BoolEither用于错误处理的约定是Left表示错误,Right表示正常返回类型。

如果您拥有Either类型,则可以对其进行模式匹配,以查看其包含的值:

case take x y of
  Left errorMessage -> ... -- handle error here
  Right result      -> ... -- do what you normally would

答案 1 :(得分:9)

根据您的意图,您的问题有多种解决方案:您是否希望在您的类型中制作您的功能可能失败的清单(在这种情况下,您是否希望返回失败的原因,这可能是不必要的如果这里只有一种失败模式,或者你估计在这个函数中得到一个空列表根本不会发生,所以想立即失败并抛出异常?

因此,如果您想明确表示类型失败的可能性,您可以使用Maybe,只是在没有解释的情况下指出失败(最终在您的文档中):

take :: (Eq a) => [a] -> [a] -> Maybe Bool
take [] _ = Nothing
take x y  = x == y

或者要么注册失败的原因(请注意,要么是“从一个函数返回两种类型”的答案,一般来说,尽管你的代码更具体):

take :: (Eq a) => [a] -> [a] -> Either String Bool
take [] _ = Left "Empty list"
take x y  = Right $ x == y

最后,您可以发出信号,表示此故障完全异常且无法在本地处理:

take :: (Eq a) => [a] -> [a] -> Bool
take [] _ = error "Empty list"
take x y  = x == y

请注意,使用这种最后一种方式,调用站点不必立即处理失败,实际上它不能,因为异常只能在IO monad中捕获。使用前两种方式,必须修改调用站点以处理失败(并且可以)的情况,如果只是为了自己调用“错误”。

有一个最终解决方案允许调用代码选择所需的失败模式(使用失败包http://hackage.haskell.org/package/failure):

take :: (Failure String m, Eq a) => [a] -> [a] -> m Bool
take [] _ = failure "Empty list"
take x y  = return $ x == y

这可以模仿Maybe和Either解决方案,或者你可以使用take作为IO Bool,如果它失败将抛出异常。它甚至可以在[Bool]上下文中工作(如果失败则返回空列表,这有时很有用)。

答案 2 :(得分:3)

您可以将error函数用于例外:

take :: Eq a => [a] -> [a] -> Bool
take [] _ = error "empty list"
take x y = x == y

答案 3 :(得分:1)

到目前为止你得到的三个答案(来自Tikhon Jelvis,Jedai和Philipp)涵盖了处理这种情况的三种常规方法:

  • 使用error功能信号出错。但是,这通常是不受欢迎的,因为它使得使用您的函数的程序很难从错误中恢复。
  • 使用Maybe表示无法生成Boolean答案的情况。
  • 使用Either,它通常用于执行与Maybe相同的操作,但可以另外包含有关失败的更多信息(Left "error : empty list")。

我选择了MaybeEither方法,然后添加一个花絮(稍微高一点,但最终可能要达到):Maybe和{{} {1}}可以组成monad,这可以用来编写在这两者之间选择中立的代码。 This blog post discusses eight different ways to tackle your problem,其中包括上述三个,第四个使用Either a类型类来抽象MonadMaybe之间的差异,还有其他四个。

博客文章是从2007年开始的,所以看起来有点陈旧,但我设法让#4以这种方式工作:

Either

现在这个{-# LANGUAGE FlexibleInstances #-} take :: (Monad m, Eq a) => [a] -> [a] -> m Bool take x y | x == [] = fail "error : empty list" | x == y = return True | otherwise = return False instance Monad (Either String) where return = Right (Left err) >>= _ = Left err (Right x) >>= f = f x fail err = Left err 函数适用于两种情况:

take

虽然重要的是要注意*Main> Main.take [1..3] [1..3] :: Maybe Bool Just True *Main> Main.take [1] [1..3] :: Maybe Bool Just False *Main> Main.take [] [1..3] :: Maybe Bool Nothing *Main> Main.take [1..3] [1..3] :: Either String Bool Right True *Main> Main.take [1] [1..3] :: Either String Bool Right False *Main> Main.take [] [1..3] :: Either String Bool Left "error : empty list" 是有争议的,所以我预计会对这种方法提出合理的反对意见。这里使用fail并不重要,但可以将其替换为任何函数failf :: (Monad m, ErrorClass m) => String -> m a f err NothingMaybeLeft err