繁体   English   中英

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

[英]returning two different types from one function

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

我想做的事情如下:

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

我有必要的语言背景。

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

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

然后,类型takeEq a => [a] -> [a] -> Either String Bool 使用Either进行错误处理的约定是Left表示错误, Right表示正常的返回类型。

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

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

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

因此,如果您想明确表示类型失败的可能性,可以使用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]上下文中工作(如果失败则返回空列表,这有时很有用)。

您可以将error函数用于异常:

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

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

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

我选择了MaybeEither方法,然后添加一个小问题(稍微高一点,但你最终可能想要): MaybeEither a都可以制成monad,这可以用来编写代码这两者之间的选择是中立的。 这篇博客文章讨论了解决问题的八种不同方法 ,其中包括上面提到的三种方法,第四种方法使用Monad类型来抽象MaybeEither之间的差异,还有其他四种方法。

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

{-# 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是有争议的,所以我预计会对这种方法提出合理的反对意见。 这里使用fail不是必要的,但它可以被任何函数f :: (Monad m, ErrorClass m) => String -> ma替换,使得f errMaybeNothing ,在Eitherf :: (Monad m, ErrorClass m) => String -> ma Left err

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM