un mécanisme de protection personnalisé peut être défini dans Haskell?
-
20-09-2019 - |
Question
Si vous regardez l'exemple pour catches
:
f = expr `catches` [Handler (\ (ex :: ArithException) -> handleArith ex),
Handler (\ (ex :: IOException) -> handleIO ex)]
On dirait que catches
a défini un mécanisme personnalisé pour correspondre à motifs (les deux types d'exception). Je me trompe, ou peut-il être généralisé pour permettre de définir une fonction qui peut prendre des fonctions lambda qui correspondent à un certain motif?
Edit: Pour votre information ci-dessous est la source GHC pour les prises. Si quelqu'un peut faire la lumière sur la façon dont cela fonctionne, il serait grand.
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
La solution
est Variables déclarées Type l'extension GHC au travail. Suivez le lien pour en savoir plus.
En fait, vous définissez une affirmation sur le type qui doivent être pris en charge par le crépitement avant qu'il ne puisse égaler. Alors, oui, il est semblable à des gardes, mais pas tout à fait.
Comment fonctionne cet exemple? Plongez dans pour savoir 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
On voit que IOException
et ArithException
sont différents types d'application de la Exception
de classe de types. On voit aussi que toException/fromException
est un emballage / mécanisme déballant qui permet de convertir des valeurs de type Exception
à / à partir des valeurs de types IOException
, ArithException
, etc.
Alors, on aurait pu écrire:
f = expr `catches` [Handler handleArith,
Handler handleIO]
handleArith :: ArithException -> IO ()
handleArith ex = ....
handleIO :: IOException -> IO ()
handleIO ex = ....
Supposons que IOException
arrive. Lorsque catchesHandler
traite premier élément de la liste des gestionnaires, il appelle tryHandler
, qui appelle fromException
. De la définition de tryHandler
il suit ce type de retour de la fromException
devrait être le même que l'argument de handleArith
. D'autre part, e
est de type Exception, à savoir - (IOException ...). Ainsi, les types jouent de cette façon (ce n'est pas valide haskell, mais j'espère que vous obtenez mon point):
fromException :: (IOException ...) -> Maybe ArithException
De l'instance Exception IOException ...
il suit immédiatement que le résultat est Nothing
, donc ce gestionnaire est sauté. Par le même raisonnement le gestionnaire suivant serait appelé, parce que fromException
retournerait (Just (IOException ...))
.
Alors, vous avez utilisé les signatures de type de handleArith
et handleIO
pour spécifier quand chacun d'entre eux seraient appelés, et fromException/toException
fait en sorte qu'il est arrivé de cette façon.
Si vous le souhaitez, vous pouvez également contrainte de types handleIO
et handleArith
dans la définition de f
, en utilisant des variables de type scope. On peut dire que, cela pourrait vous donner une meilleure lisibilité.
Finaliser, Variables déclarées de type ne sont pas un joueur primaire ici. Ils sont juste utilisés pour plus de commodité. Les machines principales pour jouer ce genre de trucs est fromException/toException
et les amis. Variables de type scope simplement vous permettre d'avoir une syntaxe qui ressemblent plus étroitement les modèles de garde.
Autres conseils
case () of
()| foo expr1 -> handleFooCase
| bar expr2 -> handleBarCase
| otherwise -> blah