{-# LANGUAGE LambdaCase #-}
Funziona come un esempio di typeclass?
Ho un sacco di funzioni che codificano il fallimento in vari modi. Per esempio:
f :: A -> Bool
rendimentiFalse
in caso di fallimentog :: B -> Maybe B'
rendimentiNothing
in caso di fallimentoh :: C -> Either Error C'
rendimentiLeft ...
in caso di fallimento
voglio catena di queste operazioni nello stesso modo come il Maybe
Monade , quindi la funzione di concatenamento deve sapere se ciascuna funzione è fallita prima di passare alla successiva. Per questo ho scritto questa classe:
class Fail a where
isFail :: a -> Bool
instance Fail() where
isFail() = False
instance Fail Bool where -- a
isFail = not
instance Fail (Maybe a) where -- b
isFail = not . isJust
instance Fail (Either a b) where -- c
isFail (Left _) = True
isFail _ = False
Tuttavia, è possibile che le funzioni che non sono conformi esistono:
f' :: A -> Bool
rendimentiTrue
in caso di fallimentog' :: B -> Maybe Error
rendimentiJust Error
in caso di fallimento (Nothing
in caso di successoh' :: C -> Either C' Error
restituisceRight ...
in caso di errore
Questi possano essere risolti semplicemente avvolgendoli con funzioni che trasformano, per esempio:
f'' = not . f'
.g'' = (\case Nothing -> Right(); Just e -> Left e) . g'
h'' = (\case Left c -> Right c; Right e -> Left e) . h'
Tuttavia, l'utente della funzione concatenamento prevede di poter combinare f
, g
, h
, f'
, g'
e h'
e farli funzionare. Non saprebbe che il tipo di ritorno di una funzione deve essere trasformato a meno che non guardi la semantica di ciascuna funzione che sta combinando e verifichi se corrispondono a qualsiasi istanza di Fail
che ha in ambito. Questo è tedioso e troppo sottile per l'utente medio da notare, specialmente con l'inferenza di tipo che evita all'utente di scegliere le istanze giuste.
Queste funzioni non sono state create con la conoscenza di come sarebbero state utilizzate. Quindi potrei creare un tipo data Result a b = Fail a | Success b
e creare wrapper attorno a ciascuna funzione.Per esempio:
fR = (\case True -> Sucess(); False -> Fail()) . f
f'R = (\case False -> Sucess(); True -> Fail()) . f'
gR = (\case Just a -> Sucess a; Nothing -> Fail()) . g
g'R = (\case Nothing -> Sucess(); Just e -> Fail e) . g'
hR = (\case Left e -> Fail e; Right a -> Sucess a) . h
h'R = (\case Right e -> Fail e; Left a -> Sucess a) . h'
Tuttavia, questo si sente sporca. Quello che stiamo facendo è solo certificare/spiegare come ciascuno di f
, g
, h
, f'
, g'
e h'
vengono utilizzati nel contesto della funzione di combinazione. C'è un modo più diretto per farlo? Quello che voglio è esattamente un modo per dire quale istanza del Fail
typeclass deve essere utilizzato per ogni funzione, vale a dire, (utilizzando i nomi dati alle istanze typeclass di cui sopra), f
→ a
, g
→ b
, h
→ c
e f'
→ a'
, g'
→ b'
, h'
→ c'
per le funzioni "non valido", dove a'
, b'
, e c'
sono definiti come i seguenti casi (che si sovrappongono a quelli precedenti, quindi avresti bisogno di essere in grado di prenderli per nome in qualche modo):
instance Fail Bool where -- a'
isFail = id
instance Fail (Maybe a) where -- b'
isFail = isJust
instance Fail (Either a b) where -- c'
isFail (Right _) = True
isFail _ = False
Tuttavia, non deve necessariamente essere eseguito tramite i typeclass. Forse c'è un modo per farlo diverso da quello con le classifiche?
Se vuoi incatenarli insieme come in una monade (con la notazione 'do'), allora è necessario convertirli tutti in un unico tipo per cui è possibile creare un'istanza Monad. Si afferma che si desidera che il sistema tipo si limiti a capire cosa significa una funzione in caso di errore senza suggerimenti o contesto. In teoria, potrei avere un numero infinito di funzioni che restituiscono un intero intero che rappresenta un errore, come dovrebbe sapere il compilatore quando un intero specifico è un errore? Ha solo un valore come contesto. Non puoi aspettarti che il compilatore scriva il tuo programma per te, altrimenti useremmo tutti Agda. – bheklilr
Sì, non voglio necessariamente renderlo una monade, ma sarà simile. Non mi aspetto che il compilatore sappia quali numeri interi sono falliti, voglio in qualche modo indicare quali numeri interi sono falliti per 'f', e indicare un diverso set per' f2', e così via. Ad esempio, in Python potrei semplicemente creare una funzione di mappatura delle variabili globali su un equivalente delle istanze del typeclass. Quindi si arresterebbe in fase di runtime invece del tempo di compilazione se non vi è alcuna mappatura da parte di una funzione che l'utente desidera utilizzare. Ma sarebbe ancora più sicuro. –