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 1
width
), 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ż Area
typ (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.
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. –
+1 Myślę, że typologie wieloparametrowe + fundeps są tutaj najprostszym podejściem. –
+1 Świetna odpowiedź! – asm