2012-02-27 4 views
2

Как вернуть значения нескольких типов из одной функции?, возвращающий два разных типа из одной функции

Я хочу сделать что-то вроде:

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

У меня есть опыт в императивных языках.

+3

Это было бы неверно на типизированных императивных языках, таких как C и Java. –

ответ

9

Существует конструктор типа 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 
3

Вы можете использовать error функции исключения:

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

Ошибки не являются исключениями: https://wiki.haskell.org/Error_vs._Exception – Evi1M4chine

9

Существует несколько решения вашей проблемы в зависимости от вашего намерения: хотите ли вы сделать манифест в своем типе, что ваша функция может потерпеть неудачу (и в этом случае вы хотите вернуть причину сбоя, что может быть ненужным, если есть только один мод e неудачи, как здесь), или вы считаете, что получение пустого списка в этой функции не должно происходить вообще, и поэтому хотите немедленно сбой и выброс исключения?

Так что, если вы хотите, чтобы сделать явную возможность отказа в вашем типе, вы можете использовать Может быть, просто указать отказ без объяснения причин (в конце концов, в документации):

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. С помощью первых двух способов сайт вызова должен быть изменен, чтобы обрабатывать случай сбоя (и может), если только сам вызов вызывает «ошибку».

Существует один окончательное решение, которое позволяет вызывающий код, чтобы выбрать, какой режим отказа вы хотите (с использованием пакета отказа 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 

Это имитирует Может и решение Либо, или вы можете использовать в качестве IO Bool, который будет генерировать исключение, если он терпит неудачу. Он может работать даже в контексте [Bool] (возвращает пустой список в случае сбоя, что иногда полезно).

1

три ответы, которые вы получили до сих пор (от Тихона Jelvis, Jedai и Philipp) охватывают три традиционных способа обработки такого рода ситуации:

  • Используйте функцию error сигнализируют об ошибке. Однако это часто неодобрительно, потому что это затрудняет работу программ, которые используют вашу функцию для восстановления после ошибки.
  • Используйте Maybe, чтобы указать случай, когда нет ответа Boolean.
  • Используйте Either, который часто используется для того, чтобы делать то же самое, что и Maybe, но может дополнительно содержать дополнительную информацию об ошибке (Left "error : empty list").

Я бы второй по Maybe и Either подходу, и добавить одну пикантные (который немного более продвинутый, но вы можете получить в итоге): как Maybe и Either a могут быть сделаны в монады, и это может использоваться для написания кода, который является нейтральным между выбором между этими двумя. This blog post discusses eight different ways to tackle your problem, который включает в себя три упомянутых выше, четвертый, который использует класс типа 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 -> m a таким образом, чтобы f err был Nothing в Maybe и Left err в Either.

+0

В будущем Fail будет помещен в класс MonadFail, так что это все равно можно выполнить, выполнив MonadFail для Либо. – Evi1M4chine

Смежные вопросы