2015-03-13 19 views
7

Sto scrivendo un'applicazione CRUD e ho un sacco di ricerche per chiave primaria (le chiavi primarie possono avere tipi diversi). Così ho definito seguente typeclass:Inferring Eq typeclass

{-# LANGUAGE MultiParamTypeClasses #-} 

class Eq b => HasPK a b where 
    getPK :: a -> b 

adesso posso scrivere:

import Data.Maybe 

lookupPK :: HasPK a b => b -> [a] -> Maybe a 
lookupPK s = listToMaybe . filter ((== s) . getPK) 

Ora, quando voglio confrontare due cose con PK, voglio solo per confrontare le loro PK di. Quindi, sto cercando di definire questo:

{-# LANGUAGE FlexibleInstances #-} 
{-# LANGUAGE UndecidableInstances #-} 

instance (HasPK a b) => Eq a where 
    (==) = (==) `on` getPK 

Ma ora mi dà:

src/Utils.hs:61:10: Could not deduce (HasPK a b0) … 
     arising from the ambiguity check for an instance declaration 
    from the context (HasPK a b) 
     bound by an instance declaration: HasPK a b => Eq a 
     at /home/utdemir/workspace/.../Utils.hs:61:10-28 
    The type variable ‘b0’ is ambiguous 
    In the ambiguity check for: forall a b. HasPK a b => Eq a 
    To defer the ambiguity check to use sites, enable AllowAmbiguousTypes 
    In the instance declaration for ‘Eq a’ 
Compilation failed. 

Qualcuno può spiegare questo errore a me? Sono sulla buona strada, o c'è un modo più sicuro per ottenere ciò che voglio?

risposta

9

Hai bisogno di un dipendenza funzionale qui: utilizzare

class Eq b => HasPK a b | a -> b where 
    getPK :: a -> b 

e attivare punti di qualunque estensione GHC a.

Il problema risiede nella possibilità di avere più PK per lo stesso tipo, come in

instance HasPK MyType Int where ... 
instance HasPK MyType String where ... 

Da quanto precede doppia esempio potrebbe essere aggiunto in seguito, il codice come (==) `on` getPK è potenzialmente ambigua. Infatti, quando vengono aggiunte le istanze di cui sopra, non specifica se utilizzare la PK Int o la String.


Tuttavia, un'istanza come

instance (HasPK a b) => Eq a where 
    ... 

è probabile che a causare problemi, in quanto si sovrappone con qualsiasi altro Eq esempio. Stai attento qui. Vorrei invece scrivere

equalPK :: HasPK a b => a -> a -> Bool 
equalPK = (==) `on` getPK 

e quindi fornire le istanze esplicite a tutti i tipi rilevanti come questo:

instance Eq MyType1 where (==) = equalPK 
instance Eq MyType2 where (==) = equalPK 
... 

Come ulteriore alternativa, è possibile utilizzare un tipo di famiglia come

class HasPK a where 
    type PK a 
    getPK :: a -> PK a 

equalPK :: Eq (PK a) => a -> a -> Bool 
equalPK = (==) `on` getPK 

instance Eq MyType1 where (==) = equalPK 
... 
+0

Sì, dopo aver definito quell'istanza, ho ottenuto 'src/Utils.hs: 52: 20: Sovrapposizione di istanze per Eq Integer derivanti da un uso di '/ =' ... Istanze di corrispondenza: istanza Intero Eq - Definito nell'istanza 'integer-gmp: GHC.Integer.Type' (Eq b, HasPK a b) => Eq a'. Ma non mi aspettavo quell'errore poiché "Integer" non ha un'istanza "HasPk Integer b". Capirei l'errore se definisco sia Eq che HasPK, ma poiché non c'è 'HasPK Integer', non dovrebbe usare direttamente l'istanza' Eq'? – utdemir

+2

@utdemir Il problema è: un 'esempio C a => Eq a' si applica ad ogni tipo, anche a quelli per i quali 'C a' è falso (!). Haskell si impegna a utilizzare questa istanza, e quando 'C a' è falso, si verificherà un errore, invece di tornare indietro e cercare altre istanze. Ciò avviene perché con il backtracking il problema diventa molto più difficile ei progettisti di Haskell si occupano del tempo di compilazione.GHC ha un'estensione 'OverlappingInstances' che rilassa questa restrizione, ma non la consiglierei. Le istanze sovrapposte sono le migliori da evitare, IMHO. – chi

+0

Grazie, sono andato con la soluzione family type poiché sembra che l'estensione meno controversa sia quella. – utdemir