un meccanismo di protezione personalizzato può essere definito in Haskell?
-
20-09-2019 - |
Domanda
Se si guarda l'esempio per catches
:
f = expr `catches` [Handler (\ (ex :: ArithException) -> handleArith ex),
Handler (\ (ex :: IOException) -> handleIO ex)]
Sembra che catches
ha definito un meccanismo personalizzato per abbinare sui modelli (i due tipi di eccezione). Sbaglio o questo può essere generalizzato per consentire di definire una funzione che può assumere le funzioni lambda che corrispondono a un determinato modello?
Modifica: FYI sotto è la fonte GHC per le catture. Se qualcuno può far luce su come funziona sarebbe bello.
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
Soluzione
Questo è il tipo con ambito Variabili estensione GHC sul posto di lavoro. Segui il link per saperne di più.
In sostanza, si definisce un'affermazione sul tipo che devono essere soddisfatte dal ticchettio prima che possa eguagliare. Quindi, sì, è simile a guardie, ma non del tutto così.
Come funziona questo particolare esempio? Tuffati in fonti di "base" della biblioteca per scoprire che:
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
Si vede che IOException
e ArithException
sono diversi tipi di attuazione del Exception
typeclass. Vediamo anche che toException/fromException
è un involucro / meccanismo di togliere l'involucro che permette di convertire i valori di tipo Exception
a / da valori di tipo IOException
, ArithException
, ecc.
Quindi, avremmo potuto scrivere:
f = expr `catches` [Handler handleArith,
Handler handleIO]
handleArith :: ArithException -> IO ()
handleArith ex = ....
handleIO :: IOException -> IO ()
handleIO ex = ....
Si supponga che IOException
accade. Quando catchesHandler
elabora primo elemento della lista gestori, chiama tryHandler
, che chiama fromException
. Dalla definizione di tryHandler
segue che tipo di ritorno del fromException
dovrebbe essere lo stesso come argomento di handleArith
. D'altra parte, e
è di tipo eccezione, cioè - (IOException ...). Così, i tipi di giocare fuori in questo modo (questa non è una valida Haskell, ma spero che si ottiene il mio punto):
fromException :: (IOException ...) -> Maybe ArithException
Dal instance Exception IOException ...
segue immediatamente che il risultato è Nothing
, quindi questo gestore viene saltato. Con lo stesso ragionamento il seguente gestore sarebbe chiamato, perché fromException
sarebbe tornato (Just (IOException ...))
.
Quindi, hai utilizzato tipo firme di handleArith
e handleIO
per specificare quando ciascuno di essi sarebbe stato chiamato, e fromException/toException
fatto in modo che è successo in questo modo.
Se si vuole, si potrebbe anche vincolo tipi di handleIO
e handleArith
all'interno della definizione di f
, utilizzando le variabili di tipo di ambito. Probabilmente, questo potrebbe dare una migliore leggibilità.
Tipo finalizzazione, con ambito variabili non sono un primario giocatori qui. Essi sono solo utilizzati per convenienza. macchine principali per la riproduzione di questo tipo di trucchi è fromException/toException
e gli amici. Variabili di tipo di ambito solo permetterà di avere la sintassi che assomigliano più da vicino i modelli di guardia.
Altri suggerimenti
case () of
()| foo expr1 -> handleFooCase
| bar expr2 -> handleBarCase
| otherwise -> blah