2011-12-16 17 views
11

Giocando intorno con tipo-classes mi si avvicinò con l'apparentemente innocenteesempio Typeclass con dipendenze funzionali non funziona

class Pair p a | p -> a where 
    one :: p -> a 
    two :: p -> a 

Questo sembra funzionare bene, per esempio

instance Pair [a] a where 
    one [x,_] = x 
    two [_,y] = y 

Tuttavia corro nei guai per le tuple. Anche se la seguente definizione compila ...

instance Pair (a,a) a where 
    one p = fst p 
    two p = snd p 

... non posso usarlo come mi aspettavo:

main = print $ two (3, 4) 

No instance for (Pair (t, t1) a) 
    arising from a use of `two' at src\Main.hs:593:15-23 
Possible fix: add an instance declaration for (Pair (t, t1) a) 
In the second argument of `($)', namely `two (3, 4)' 
In the expression: print $ two (3, 4) 
In the definition of `main': main = print $ two (3, 4) 

C'è un modo per definire correttamente l'istanza? O devo ricorrere a un wrapper newtype?

risposta

18

L'istanza funziona correttamente. Osservare:

main = print $ two (3 :: Int, 4 :: Int) 

Funziona come previsto. Quindi, perché non funziona senza l'annotazione del tipo, quindi? Bene, considera il tipo di tupla: (3, 4) :: (Num t, Num t1) => (t, t1). Poiché i valori letterali numerici sono polimorfici, nulla richiede che lo sia lo stesso tipo. L'istanza è definita per (a, a), ma l'esistenza di tale istanza non indica a GHC di unificare i tipi (per una serie di buoni motivi). A meno che GHC non possa dedurre con altri mezzi che i due tipi siano uguali, non sceglierà l'istanza desiderata, anche se i due tipi potrebbero essere uguali a.

Per risolvere il problema, è sufficiente aggiungere annotazioni di tipo, come ho fatto sopra. Se gli argomenti vengono da altrove, di solito non è necessario perché saranno già noti per essere dello stesso tipo, ma diventa maldestro rapidamente se si desidera utilizzare valori letterali numerici.

Una soluzione alternativa è notare che, a causa di come funziona la selezione dell'istanza, avere un'istanza per (a, a) significa che non è possibile scrivere un'istanza come (a, b) anche se si desidera. Così possiamo imbrogliare un po ', per forzare l'unificazione utilizzando la classe tipo, in questo modo:

instance (a ~ b) => Pair (a,b) a where 

che ha bisogno l'estensione TypeFamilies per il ~ contesto, credo. Ciò che fa è consentire all'istanza di coincidere su qualsiasi tupla all'inizio, perché la selezione dell'istanza ignora il contesto. Dopo aver scelto l'istanza, tuttavia, il contesto a ~ b asserisce l'uguaglianza di tipo, che produrrà un errore se sono diversi ma, cosa più importante, unificherà le variabili di tipo se possibile. Usando questo, la tua definizione di main funziona così com'è, senza annotazioni.

+0

Grazie, molto interessante! – Landei

6

Il problema è che un numero letterale ha un tipo polimorfico. Non è ovvio al tipografo che entrambi i letterali dovrebbero avere lo stesso tipo (Int). Se usi qualcosa che non è polimorfico per le tue tuple, il tuo codice dovrebbe funzionare. Considera questi esempi:

*Main> two (3,4) 

<interactive>:1:1: 
    No instance for (Pair (t0, t1) a0) 
     arising from a use of `two' 
    Possible fix: add an instance declaration for (Pair (t0, t1) a0) 
    In the expression: two (3, 4) 
    In an equation for `it': it = two (3, 4) 
*Main> let f = id :: Int -> Int -- Force a monomorphic type 
*Main> two (f 3,f 4) 
4 
*Main> two ('a','b') 
'b' 
*Main> two ("foo","bar") 
"bar" 
*Main> two (('a':),('b':)) "cde" 
"bcde" 
*Main> 
Problemi correlati