2011-01-11 10 views
11

Rozumiem prawie całą resztę języka, ale za każdym razem, gdy zanurzam stopy w znaczący sposób w klasie, uzyskuję permutnie zakorzenione .Nie mogę wymyślić zmiennych typu zmieszanych z klasami

Dlaczego ten wyjątkowo prosty kod nie działa?

data Room n = Room n n deriving Show 

class HasArea a where 
    width :: (Num n) => a -> n 

instance (Num n) => HasArea (Room n) where 
    width (Room w h) = w 

Tak więc szerokość pokoju jest oznaczona przez ints lub może być pływająca, nie chcę w tym momencie jej ograniczać. Zarówno klasa i instancja ograniczyć polecenia n Nums, ale jeszcze go nie lubi, a ja dostać ten błąd:

Couldn't match expected type `n1' against inferred type `n' 
    `n1' is a rigid type variable bound by 
     the type signature for `width' at Dungeon.hs:11:16 
    `n' is a rigid type variable bound by 
     the instance declaration at Dungeon.hs:13:14 
In the expression: w 
In the definition of `width': width (Room w h) = w 
In the instance declaration for `HasArea (Room n)' 

Więc to mówi mi typy nie pasuje, ale to nie robi powiedz mi, jakie typy on uważa, że ​​są, co byłoby naprawdę pomocne. Na marginesie, czy istnieje prosty sposób debugowania takiego błędu? Jedynym sposobem, w jaki to robię, jest losowa zmiana rzeczy, dopóki nie zadziała.

Odpowiedz

18

Błąd, który otrzymujesz, informuje Cię, jaki powinien być typ; niestety oba typy są oznaczone zmiennymi typu, co utrudnia ich dostrzeżenie. Pierwsza linia mówi, że podałeś typ wyrażenia n, ale chciał nadać mu typ n1. Aby dowiedzieć się, co to jest, spojrzeć na kilka następnych liniach:

`n1' is a rigid type variable bound by 
    the type signature for `width' at Dungeon.hs:11:16 

Ten mówi, że n1 jest typ zmiennej, której wartość jest znana, a więc nie można zmienić (jest „sztywna”). Ponieważ jest związany przez podpis typu dla width, wiesz, że jest związany linią width :: (Num n) => a -> n. Jest jeszcze jeden zakres n, więc zmieniono nazwę na n na n1 (width :: (Num n1) => a -> n1). Następnie mamy

`n' is a rigid type variable bound by 
    the instance declaration at Dungeon.hs:13:14 

ta jest informacją, że Haskell znaleźć typ n z linii instance (Num n) => HasArea (Room n) where.Zgłaszany problem polega na tym, że n, który jest typem GHC obliczonym dla width (Room w h) = w, nie jest tym samym, co n1, który jest oczekiwanym typem.

Powodem tego problemu jest to, że twoja definicja width jest mniej polimorficzna niż oczekiwano. Sygnatura typu width to (HasArea a, Num n1) => a -> n1, co oznacza, że ​​dla każdego typu, który jest instancją HasArea, można dowolnie reprezentować jego szerokość z numerem o dowolnym numerze. Jednak w definicji instancji linia width (Room w h) = w oznacza, że ​​width ma typ . Zauważ, że nie jest to wystarczająco polimorficzne: podczas gdy Room n jest instancją HasArea, wymagałoby to, aby width miał typ (Num n, Num n1) => Room n -> n1. Ta właśnie niezdolność do zunifikowania specyficznego n z ogólnym n1 powoduje błąd typu.

Istnieje kilka sposobów, aby to naprawić. Jednym podejściem (i prawdopodobnie najlepszym podejściem), które można zobaczyć w sepp2k's answer, jest sprawienie, aby HasArea stała się zmienną typu * -> *; oznacza to, że zamiast być typem sam w sobie, rzeczy takie jak a Int lubsą typami. Maybe i [] są przykładami typów z rodzaju * -> *. (Typy zwykłe, takie jak Int lub Maybe Double mają rodzaj *.) Jest to prawdopodobnie najlepszy zakład.

Jeśli masz jakieś typy rodzaju * które mają powierzchnię (np, data Space = Space (Maybe Character), gdzie jest zawsze 1width), ale to nie będzie działać. Innym sposobem (który wymaga pewnych rozszerzeń Haskell98/Haskell2010) jest, aby HasArea klasa typu multi-parametr:

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-} 

data Room n = Room n n deriving Show 

class Num n => HasArea a n where 
    width :: a -> n 

instance Num n => HasArea (Room n) n where 
    width (Room w h) = w 

Teraz przechodzą typ szerokości jako parametr do samej klasy typu, więc width ma typ (HasArea a n, Num n) => a -> n. Możliwe jednak, że można zadeklarować instance HasArea Foo Int i instance HasArea Foo Double, co może być problematyczne. Jeśli tak, to aby rozwiązać ten problem, możesz użyć zależności funkcjonalnych lub rodzin typów. Funkcjonalne zależności pozwalają ci określić, że dany jeden typ, inne typy są jednoznacznie określone, tak jakbyś miał zwykłą funkcję. Korzystanie z tych daje kod

{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances, FunctionalDependencies #-} 

data Room n = Room n n deriving Show 

class Num n => HasArea a n | a -> n where 
    width :: a -> n 

instance Num n => HasArea (Room n) n where 
    width (Room w h) = w 

nieco GHC mówi, że jeśli można to wywnioskować a, to można również wywnioskować n, ponieważ jest tylko jeden n dla każdego a. Zapobiega to rodzajowi przypadków omówionych powyżej.

typów rodzin są różne:

{-# LANGUAGE MultiParamTypeClasses, FlexibleContexts, TypeFamilies #-} 

data Room n = Room n n deriving Show 

class Num (Area a) => HasArea a where 
    type Area a :: * 
    width :: a -> Area a 

instance Num n => HasArea (Room n) where 
    type Area (Room n) = n 
    width (Room w h) = w 

Ten mówi, że oprócz posiadania width funkcji, klasa HasArea posiada również Areatyp (lub funkcja typu, jeśli chcesz o tym myśleć w ten sposób). Dla każdego HasArea a określasz, jaki jest typ Area a (który, dzięki ograniczeniu nadklasy, musi być instancją Num), a następnie użyj tego typu jako swojego rodzaju numeru.

Co do debugowania takich błędów?Szczerze mówiąc, moja najlepsza rada to "Ćwicz, ćwicz, ćwicz". Z czasem będziesz bardziej przyzwyczajony do zastanowienia się nad (a) błędami i (b) co prawdopodobnie poszło nie tak. Zmienianie rzeczy losowo jest jednym ze sposobów na zrobienie tego uczenia się. Jednak największą radą, jaką mogę dać, jest zwrócenie uwagi na linie Couldn't match expected type `Foo' against inferred type `Bar'. To one informują o obliczeniach kompilatora (Bar) i oczekiwanym (Foo) dla tego typu, a jeśli potrafisz dokładnie określić, które to typy, pomaga to ustalić, gdzie znajduje się błąd.

+0

Wow, naprawdę wybiłeś to z parku. Przeglądałem dokładnie te tematy, ponieważ od razu zauważyłem brak możliwości użycia tego dla typów z wieloma rodzajami. Mało tego, przegapiłem wiele typowych czcionek, które są zdecydowanie najłatwiejsze z nich. Dziękuję Ci. –

+0

+1 Myślę, że typologie wieloparametrowe + fundeps są tutaj najprostszym podejściem. –

+0

+1 Świetna odpowiedź! – asm

9
class HasArea a where 
    width :: (Num n) => a -> n 

typu (Num n) => a -> n oznacza, że ​​dla każdego typu n będącego przykładem Num, width musi powrócić do wartości tego typu. Tak więc dla każdej wartości v typu T, gdzie T jest instancją HasArea następujący kod musi być ważny:

let x :: Integer = width v 
    y :: Double = width v 
in 
    whatever 

Jednak nie jest to w przypadku wystąpień Room. Na przykład dla Room Integery :: Dobule = width v jest niepoprawny.

Aby dokonać przykład pracować mógłby zrobić coś takiego:

data Room n = Room n n deriving Show 

class HasArea a where 
    width :: (Num n) => a n -> n 

instance HasArea Room where 
    width (Room w h) = w 

Tu nie mówimy, że Room Integer, Room Float itp są przypadki HasArea, ale zamiast Room jest instancją HasArea. Typ width jest taki, że generuje wartość typu n, gdy podana jest wartość typu a n, więc jeśli wstawisz Room Integer, otrzymasz ponownie Integer. W ten sposób typ pasuje.

+0

Widzę, dziękuję. To ma sens. –

Powiązane problemy