[英]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
然后,类型take
为Eq 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"
)。 我选择了Maybe
和Either
方法,然后添加一个小问题(稍微高一点,但你最终可能想要): Maybe
和Either a
都可以制成monad,这可以用来编写代码这两者之间的选择是中立的。 这篇博客文章讨论了解决问题的八种不同方法 ,其中包括上面提到的三种方法,第四种方法使用Monad
类型来抽象Maybe
和Either
之间的差异,还有其他四种方法。
博客条目是从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 err
在Maybe
是Nothing
,在Either
是f :: (Monad m, ErrorClass m) => String -> ma
Left err
。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.