Если Вы смотрите на пример для catches
:
f = expr `catches` [Handler (\ (ex :: ArithException) -> handleArith ex),
Handler (\ (ex :: IOException) -> handleIO ex)]
Это похоже catches
определил пользовательский механизм для соответствия на шаблонах (эти два типа исключительной ситуации). Действительно ли я ошибаюсь, или это может быть обобщено, чтобы позволить тому определять функцию, которая может взять функции лямбды, которые соответствуют на определенном шаблоне?
Править: К вашему сведению ниже источник 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
Это Scoped Type Variables расширение 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
Из экземпляра Exception IOException ...
сразу следует, что результатом является Ничего
, поэтому этот обработчик пропускается. По той же причине будет вызван следующий обработчик, потому что fromException
вернет (Just (IOException ...))
.
Итак, вы использовали сигнатуры типов handleArith
и handleIO
, чтобы указать, когда каждый из них будет вызван, а fromException / toException
убедился, что это случилось так.
Если хотите, вы также можете ограничить типы handleIO
и handleArith
внутри определения f
, используя переменные типа с ограниченной областью видимости. Возможно, это может улучшить читаемость.
Завершение, переменные типа с ограниченным типом здесь не являются основными игроками. Они просто используются для удобства. Основным механизмом для разыгрывания таких трюков является fromException / toException
и другие. Переменные типа с ограниченной областью видимости просто позволяют вам иметь синтаксис, который больше напоминает защитные шаблоны.
case () of
()| foo expr1 -> handleFooCase
| bar expr2 -> handleBarCase
| otherwise -> blah