Это Переменные типа 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 просто позволяют вам иметь синтаксис, более близкий к шаблонам защиты.