简体   繁体   English

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

[英]returning two different types from one function

How can I return values of multiple types from a single function? 如何从单个函数返回多个类型的值?

I want to do something like: 我想做的事情如下:

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

I have a background in imperative languages. 我有必要的语言背景。

There is a type constructor called Either that lets you create a type that could be one of two types. 有一个名为Either的类型构造函数,允许您创建可以是两种类型之一的类型。 It is often used for handling errors, just like in your example. 它通常用于处理错误,就像在您的示例中一样。 You would use it like this: 你会像这样使用它:

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

The type of take would then be something like Eq a => [a] -> [a] -> Either String Bool . 然后,类型takeEq a => [a] -> [a] -> Either String Bool The convention with Either for error handling is that Left represents the error and Right represents the normal return type. 使用Either进行错误处理的约定是Left表示错误, Right表示正常的返回类型。

When you have an Either type, you can pattern match against it to see which value it contains: 当您拥有Either类型时,可以对其进行模式匹配以查看它包含的值:

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

There is several solutions to your problem, depending on your intention : do you want to make manifest in your type that your function can fail (and in this case do you want to return the cause of the failure, which may be unnecessary if there is only one mode of failure like here) or do you estimate that getting an empty list in this function shouldn't happen at all, and so want to fail immediately and by throwing an exception ? 您的问题有几种解决方案,具体取决于您的意图:您是否希望在您的类型中制作您的功能可能失败的清单(在这种情况下,您希望返回失败的原因,如果有的话,这可能是不必要的这里只有一种失败模式)或者你估计在这个函数中得到一个空列表根本不应该发生,所以想要立即失败并抛出异常?

So if you want to make explicit the possibility of failure in your type, you can use Maybe, to just indicate failure without explanation (eventually in your documentation) : 因此,如果您想明确表示类型失败的可能性,可以使用Maybe,只是在没有解释的情况下指出失败(最终在您的文档中):

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

Or Either to register the reason of the failure (note that Either would be the answer to "returning two types from one function" in general, though your code is more specific) : 或者要么注册失败的原因(注意,一般来说,或者是“从一个函数返回两种类型”的答案,尽管你的代码更具体):

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

Finally you can signal that this failure is completely abnormal and can't be handled locally : 最后,您可以发出信号,表示此故障完全异常且无法在本地处理:

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

Note that with this last way, the call site don't have to immediately handle the failure, in fact it can't, since exceptions can only be caught in the IO monad. 请注意,使用这种最后一种方式,调用站点不必立即处理失败,实际上它不能,因为异常只能在IO monad中捕获。 With the first two ways, the call site have to be modified to handle the case of failure (and can), if only to itself call "error". 使用前两种方式,必须修改调用站点以处理失败(并且可以)的情况,如果仅对其自身调用“错误”。

There is one final solution that allows the calling code to choose which mode of failure you want (using the failure package http://hackage.haskell.org/package/failure ) : 有一个最终解决方案允许调用代码选择您想要的失败模式(使用失败包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

This can mimics the Maybe and the Either solution, or you can use take as an IO Bool which will throw an exception if it fails. 这可以模仿Maybe和Either解决方案,或者你可以使用take作为IO Bool,如果它失败将抛出异常。 It can even works in a [Bool] context (returns an empty list in case of failure, which is sometimes useful). 它甚至可以在[Bool]上下文中工作(如果失败则返回空列表,这有时很有用)。

You can use the error functions for exceptions: 您可以将error函数用于异常:

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

The three answers you've gotten so far (from Tikhon Jelvis, Jedai and Philipp) cover the three conventional ways of handling this sort of situation: 你到目前为止得到的三个答案(来自Tikhon Jelvis,Jedai和Philipp)涵盖了处理这种情况的三种常规方法:

  • Use the error function signal an error. 使用error功能信号出错。 This is often frowned upon, however, because it makes it hard for programs that use your function to recover from the error. 然而,这通常是不受欢迎的,因为它使得使用您的函数的程序很难从错误中恢复。
  • Use Maybe to indicate the case where no Boolean answer can be produced. 使用Maybe表示不能生成Boolean答案的情况。
  • Use Either , which is often used to do the same thing as Maybe , but can additionally include more information about the failure ( Left "error : empty list" ). 使用Either ,它通常用于执行与Maybe相同的操作,但可以另外包含有关失败的更多信息( Left "error : empty list" )。

I'd second the Maybe and Either approach, and add one tidbit (which is slightly more advanced, but you might want to get to eventually): both Maybe and Either a can be made into monads, and this can be used to write code that is neutral between the choice between those two. 我选择了MaybeEither方法,然后添加一个小问题(稍微高一点,但你最终可能想要): MaybeEither a都可以制成monad,这可以用来编写代码这两者之间的选择是中立的。 This blog post discusses eight different ways to tackle your problem , which includes the three mentioned above, a fourth one that uses the Monad type class to abstract the difference between Maybe and Either , and yet four others. 这篇博客文章讨论了解决问题的八种不同方法 ,其中包括上面提到的三种方法,第四种方法使用Monad类型来抽象MaybeEither之间的差异,还有其他四种方法。

The blog entry is from 2007 so it looks a bit dated, but I managed to get #4 working this way: 博客条目是从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

Now this take function works with both cases: 现在这个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"

Though it's important to note that fail is controversial, so I anticipate reasonable objections to this approach. 虽然重要的是要注意fail是有争议的,所以我预计会对这种方法提出合理的反对意见。 The use of fail here is not essential, though—it could be replaced with any function f :: (Monad m, ErrorClass m) => String -> ma such that f err is Nothing in Maybe and Left err in Either . 这里使用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