Можно ли определить собственный механизм защиты в Haskell?
-
20-09-2019 - |
Вопрос
Если вы посмотрите на пример для 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
Решение
Это Переменные ограниченного типа Расширение 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
, используя переменные ограниченного типа.Возможно, это может улучшить читабельность.
И наконец, переменные ограниченного типа здесь не являются основными игроками.Они используются просто для удобства.Основным механизмом для выполнения подобных трюков является fromException/toException
и друзья.Переменные ограниченного типа просто позволяют вам иметь синтаксис, который больше напоминает шаблоны защиты.
Другие советы
case () of
()| foo expr1 -> handleFooCase
| bar expr2 -> handleBarCase
| otherwise -> blah