Можно ли определить собственный защитный механизм в Haskell? - PullRequest
6 голосов
/ 16 февраля 2010

Если вы посмотрите на пример для catches:

 f = expr `catches` [Handler (\ (ex :: ArithException) -> handleArith ex),
                     Handler (\ (ex :: IOException)    -> handleIO    ex)]

Похоже, catches определил пользовательский механизм для сопоставления с шаблонами (два типа исключений). Я ошибаюсь, или это можно обобщить, чтобы позволить определить функцию, которая может принимать лямбда-функции, которые соответствуют определенному шаблону?

Edit: FYI ниже - источник GHC для уловов. Если кто-то может пролить свет на то, как это работает, было бы здорово.

catches :: IO a -> [Handler a] -> IO a
catches io handlers = io `catch` catchesHandler handlers

catchesHandler :: [Handler a] -> SomeException -> IO a
catchesHandler handlers e = foldr tryHandler (throw e) handlers
    where tryHandler (Handler handler) res
              = case fromException e of
                Just e' -> handler e'
                Nothing -> res

Ответы [ 2 ]

5 голосов
/ 16 февраля 2010

Это Переменные типа Scoped GHC расширение на работе. Перейдите по ссылке, чтобы узнать больше.

По сути, вы определяете утверждение о типе, которое должно быть удовлетворено паттером, прежде чем он сможет соответствовать. Так что, да, это похоже на охранников, но не совсем так.

Как работает этот конкретный пример? Погрузитесь в источники «базовой» библиотеки , чтобы узнать, что:

class (Show e) => Exception e where
    toException   :: e -> SomeException
    fromException :: SomeException -> Maybe e

data SomeException = forall e . Exception e => SomeException e

instance Exception IOException where
    toException = IOException
    fromException (IOException e) = Just e
    fromException _ = Nothing

instance Exception ArithException where
    toException = ArithException
    fromException (ArithException e) = Just e
    fromException _ = Nothing

Мы видим, что IOException и ArithException - это разные типы, реализующие класс типов Exception. Мы также видим, что toException/fromException - это механизм оборачивания / разворачивания, который позволяет преобразовывать значения типа Exception в / из значений типов IOException, ArithException и т. Д.

Итак, мы могли бы написать:

f = expr `catches` [Handler handleArith,
                    Handler handleIO]

handleArith :: ArithException -> IO ()
handleArith ex = ....

handleIO :: IOException -> IO ()
handleIO ex = ....

Предположим, что IOException происходит. Когда catchesHandler обрабатывает первый элемент списка обработчиков, он вызывает tryHandler, что вызывает fromException. Из определения tryHandler следует, что тип возвращаемого значения fromException должен совпадать с аргументом handleArith. С другой стороны, e имеет тип Exception, а именно - (IOException ...). Таким образом, типы разыгрываются таким образом (это не правильный haskell, но я надеюсь, что вы поняли мою точку зрения):

fromException :: (IOException ...) -> Maybe ArithException

Из instance Exception IOException ... сразу следует, что результат равен Nothing, поэтому этот обработчик пропускается. По той же причине будет вызван следующий обработчик, потому что fromException вернет (Just (IOException ...)).

Итак, вы использовали сигнатуры типов handleArith и handleIO, чтобы указать, когда будет вызываться каждый из них, и fromException/toException убедился, что это произошло именно так.

Если вы хотите, вы можете также ограничить типы handleIO и handleArith внутри определения f, используя переменные типа scoped. Возможно, это может улучшить читаемость.

Завершая, переменные типа Scoped здесь не являются основными игроками. Они просто используются для удобства. Основным механизмом для игры такого рода трюков является fromException/toException и друзья. Переменные типа Scoped просто позволяют вам иметь синтаксис, более близкий к шаблонам защиты.

1 голос
/ 17 февраля 2010
case () of 
  ()| foo expr1 -> handleFooCase
    | bar expr2 -> handleBarCase
    | otherwise -> blah
...