2015-02-23 9 views
5

{-# 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 rendimenti False in caso di fallimento
  • g :: B -> Maybe B' rendimenti Nothing in caso di fallimento
  • h :: C -> Either Error C' rendimenti Left ... 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 rendimenti True in caso di fallimento
  • g' :: B -> Maybe Error rendimenti Just Error in caso di fallimento (Nothing in caso di successo
  • h' :: C -> Either C' Error restituisce Right ... 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), fa, gb, hc 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?

+1

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

+0

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. –

risposta

10

Non farlo. Il sistema di tipo statico e la trasparenza referenziale di Haskell ti offrono una garanzia tremendamente utile: puoi essere certo che un determinato valore , , indipendentemente da come è stato prodotto. Non c'è né la capacità di interferire con questo, né la versione in stile dinamico reinterpretazione runtime ”, come avresti bisogno per l'attività che sembri immaginare.

Se quelle funzioni che hai lì non rispettano tali specifiche di conseguenza, beh, allora questo è male. Meglio sbarazzarsi di loro (almeno, nascondere e esportare solo una versione ridefinita con comportamento unificato). O dire agli utenti che dovranno vivere guardando le specifiche di ciascuno. Ma non cercare di modificare in qualche modo questo particolare sintomo di definizioni errate.

un cambiamento facile si potrebbe applicare a poco “ bandiera ” le funzioni in cui il fallimento significa l'opposto in quanto altrimenti fa è quello di farli tornare un tale risultato avvolto:

newtype Anti a = Anti { profail :: a } 

instance (Anti a) => Fail (Anti a) where 
    isFail (Anti a) = not $ isFail a 

Mente: “ stessa cosa ” in un senso forse molto astratto.Non c'è bisogno di Left di essere universalmente un “ sicuro costruttore ”, è sufficiente che sia chiaro che è il costruttore variante associata al primo argomento tipo, che non quello funtore/istanza monade opera su – da che è segue automaticamente che questo sarà “ media errore ” in un'applicazione monadica.
I.e, quando hai scelto i tipi giusti, le cose dovrebbero essere poco ambigue in modo automatico; ovviamente è vero il contrario quando sei solo tossing around booleans, quindi forse dovresti sbarazzartene completamente ...

+0

Dovrò rileggere la tua risposta, ma l'output dei booleans è stato utile nel mio caso d'uso effettivo, poiché invece di funzioni sto usando davvero operazioni monadiche che mostrano se ci sono riuscite. –

+1

Si noti che la base 4.8 aggiunge un tipo 'Alt' a' Data.Monoid' (trasformando un 'Alternative' in' 'Monoid'), in modo che questo non sia il nome migliore da scegliere. – dfeuer

+0

@og_loc: le operazioni monadiche non dovrebbero emettere booleani per segnalare un guasto, non più di quanto dovrebbero fare le normali funzioni. Per quest'ultimo si usa un wrapper 'Maybe' o' Either' adatto; il primo dovrebbe essere fatto con il corrispondente trasformatore monad. – leftaroundabout

Problemi correlati