¿Puede un mecanismo protector de encargo se define en Haskell?
-
20-09-2019 - |
Pregunta
Si nos fijamos en el ejemplo de catches
:
f = expr `catches` [Handler (\ (ex :: ArithException) -> handleArith ex),
Handler (\ (ex :: IOException) -> handleIO ex)]
Parece que catches
ha definido un mecanismo de medida para adecuarse a los patrones (los dos tipos de excepciones). ¿Estoy equivocado, o puede esto ser generalizado para permitir que uno de definir una función que puede tener funciones lambda que coinciden en un cierto patrón?
Editar: Para su información a continuación es la fuente GHC para las capturas. Si alguien puede arrojar algo de luz sobre cómo funciona esto sería muy bueno.
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
Solución
Esta es la Scoped variables de tipo extensión GHC en el trabajo. Sigue el enlace para obtener más información.
Básicamente, se define una afirmación acerca del tipo que tienen que ser cumplidos por el golpeteo antes de que pueda igualar. Así que, sí, es similar a los guardias, pero no del todo.
Como funciona este ejemplo en particular? Buceo en fuentes de "base" de la biblioteca para descubrir que:
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
Vemos que IOException
y ArithException
diferentes tipos de aplicación de la Exception
clase de tipos. También vemos que toException/fromException
es un envasado / desenvolver mecanismo que permite convertir valores de tipo Exception
a / de valores de tipos IOException
, ArithException
, etc.
Por lo tanto, podríamos haber escrito:
f = expr `catches` [Handler handleArith,
Handler handleIO]
handleArith :: ArithException -> IO ()
handleArith ex = ....
handleIO :: IOException -> IO ()
handleIO ex = ....
Supongamos que IOException
sucede. Cuando catchesHandler
procesa primer elemento de la lista de controladores, que llama tryHandler
, que llama fromException
. De la definición de tryHandler
se deduce que el tipo de retorno de la fromException
debe ser el mismo que el argumento de handleArith
. Por otro lado, e
es del tipo de excepción, es decir, - (IOException ...). Por lo tanto, los tipos juegan de esta manera (esto no es una Haskell válida, pero espero que mi punto):
fromException :: (IOException ...) -> Maybe ArithException
Desde el instance Exception IOException ...
se deduce inmediatamente que el resultado es Nothing
, por lo que este controlador se salta. Por el mismo razonamiento el siguiente controlador sería llamado, porque fromException
volvería (Just (IOException ...))
.
Por lo tanto, usted ha utilizado las firmas de tipos de handleArith
y handleIO
para especificar cuando cada uno de ellos se llamaría, y fromException/toException
se aseguró de que haya sucedido de esta manera.
Si lo desea, usted podría también constreñimiento tipos de handleIO
y handleArith
dentro de la definición de f
, utilizando variables de tipo con ámbito. Podría decirse que esto podría darle una mejor lectura.
La finalización, con ámbito variables de tipo no son unas primarias jugadores aquí. Ellos sólo se utilizan por conveniencia. máquinas principales para la reproducción de este tipo de trucos es fromException/toException
y amigos. Las variables de tipo de ámbito simplemente le permiten tener la sintaxis que se asemejan más a los patrones de guardia.
Otros consejos
case () of
()| foo expr1 -> handleFooCase
| bar expr2 -> handleBarCase
| otherwise -> blah