简体   繁体   中英

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. 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 . The convention with Either for error handling is that Left represents the error and Right represents the normal return type.

When you have an Either type, you can pattern match against it to see which value it contains:

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) :

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. 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 ) :

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. It can even works in a [Bool] context (returns an empty list in case of failure, which is sometimes useful).

You can use the error functions for exceptions:

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:

  • Use the error function signal an 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.
  • 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" ).

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. 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.

The blog entry is from 2007 so it looks a bit dated, but I managed to get #4 working this way:

{-# 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:

*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. 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 .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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