2015-03-13 17 views
7

Piszę aplikację podobną do CRUD i mam wiele wyszukiwań według klucza podstawowego (klucze podstawowe mogą mieć różne typy). Więc zdefiniowane następujące typeclass:Inferring Eq typeclass

{-# LANGUAGE MultiParamTypeClasses #-} 

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

Teraz mogę napisać:

import Data.Maybe 

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

Teraz, gdy chcę porównać dwie rzeczy PK, po prostu chcę, aby porównać ich PK-tych. Więc staram się zdefiniować następująco:

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

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

Ale teraz daje mi:

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. 

Może ktoś wyjaśnić ten błąd do mnie? Czy jestem na dobrej drodze, czy istnieje bezpieczniejszy sposób osiągnięcia tego, czego chcę?

Odpowiedz

9

Trzeba funkcjonalna zależność tutaj: użyj

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

i umożliwić rozszerzenie GHC punktów cokolwiek do.

Problem leży w możliwość posiadania wielu PKs dla tego samego typu, jak w

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

Ponieważ powyżej podwójne instancja może zostać dodana później, kod jak (==) `on` getPK jest potencjalnie wieloznaczne. Rzeczywiście, po dodaniu powyższych instancji, nie określa się, czy użyć parametru Int PK, czy też Int PK lub Int PK.


Jednak instancja jak

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

może powodować problemy, ponieważ pokrywa się z żadnym innym Eq instancji. Uważaj tutaj. Chciałbym zamiast pisać

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

a następnie dostarczyć jednoznacznych instancji do wszystkich odpowiednich typów jak ten:

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

Jako inną alternatywę, można użyć rodzinę typu takiego jak

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

Tak, po zdefiniowaniu tej instancji otrzymałem 'src/Utils.hs: 52: 20: nakładające się instancje dla Eq Integer wynikające z użycia '/ =' ... Przykłady dopasowania: instancja Eq Integer - Zdefiniowana w instancji 'integer-gmp: GHC.Integer.Type' (Eq b, HasPK a b) => Eq a'. Ale nie spodziewałem się tego błędu, ponieważ 'Integer' nie ma instancji' HasPk Integer b'. Zrozumiałbym błąd, jeśli zdefiniuję zarówno Eq, jak i HasPK, ale ponieważ nie ma "HasPK Integer", czy nie powinien on bezpośrednio używać zwykłej instancji 'Eq'? – utdemir

+2

@utdemir Problem polega na tym, że 'instancja C a => Eq a' odnosi się do każdego typu, nawet do tych, dla których' C a' jest fałszywe (!). Haskell zobowiąże się do użycia tej instancji, a gdy "C a" to będzie błędne, zamiast wycofywać i szukać innych instancji. Dzieje się tak, ponieważ przy cofaniu problem staje się znacznie trudniejszy, a projektanci Haskella zajęli się czasem kompilacji.GHC ma rozszerzenie 'OverlappingInstances', które rozluźnia to ograniczenie, ale nie polecam go. Należy unikać nakładania się instancji, IMHO. – chi

+0

Dziękuję, poszedłem z rozwiązaniem typu rodziny, ponieważ wygląda na to, że najmniej kontrowersyjne jest rozszerzenie. – utdemir