Ответ 1
Ошибка, которую вы получаете, говорит вам, что, по его мнению, тип должен быть; к сожалению, оба типа обозначаются переменными типа, что затрудняет их просмотр. В первой строке указано, что вы указали тип выражения n
, но он хотел дать ему тип n1
. Чтобы выяснить, что это такое, посмотрите на следующие несколько строк:
`n1' is a rigid type variable bound by
the type signature for `width' at Dungeon.hs:11:16
Это говорит о том, что n1
- это переменная типа, значение которой известно и, следовательно, не может меняться ( "жестко" ). Поскольку он связан сигнатурой типа для width
, вы знаете, что он связан линией width :: (Num n) => a -> n
. Там еще n
в области видимости, поэтому этот n
переименован в n1
(width :: (Num n1) => a -> n1
). Далее, мы имеем
`n' is a rigid type variable bound by
the instance declaration at Dungeon.hs:13:14
Это говорит вам, что Haskell нашел тип n
из строки instance (Num n) => HasArea (Room n) where
. Проблема, о которой сообщается, заключается в том, что n
, который является типом GHC, вычисленным для width (Room w h) = w
, не совпадает с n1
, который является ожидаемым типом.
Причина, по которой у вас возникает эта проблема, заключается в том, что ваше определение width
менее полиморфно, чем ожидалось. Типичная подпись width
равна (HasArea a, Num n1) => a -> n1
, что означает, что для каждого типа, являющегося экземпляром HasArea
, вы можете представить его ширину с любым номером вообще. Однако в определении вашего экземпляра строка width (Room w h) = w
означает, что width
имеет тип Num n => Room n -> n
. Обратите внимание, что это недостаточно полиморфно: while Room n
является экземпляром HasArea
, для этого требуется width
иметь тип (Num n, Num n1) => Room n -> n1
. Это неспособность унифицировать конкретный n
с общим n1
, который вызывает вашу ошибку типа.
Есть несколько способов исправить это. Один подход (и, вероятно, лучший подход), который вы можете увидеть в sepp2k answer, состоит в том, чтобы сделать HasArea
тип переменной типа * -> *
; это означает, что вместо a
, являющегося самим типом, такие вещи, как a Int
или a n
, являются типами. Maybe
и []
являются примерами типов с видом * -> *
. (Обычные типы типа Int
или Maybe Double
имеют вид *
.) Это, вероятно, лучший выбор.
Если у вас есть некоторые типы типов *
, у которых есть область (например, data Space = Space (Maybe Character)
, где width
всегда 1
), однако это не сработает. Другой способ (который требует некоторых расширений для Haskell98/Haskell2010) состоит в том, чтобы сделать HasArea
класс с несколькими параметрами:
{-# 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
Теперь вы передаете тип ширины в качестве параметра самому классу типа, поэтому width
имеет тип (HasArea a n, Num n) => a -> n
. Возможно, это связано с тем, что вы можете объявить instance HasArea Foo Int
и instance HasArea Foo Double
, что может быть проблематичным. Если это так, то для решения этой проблемы вы можете использовать функциональные зависимости или типы семейств. Функциональные зависимости позволяют указать, что заданный один тип, другие типы определяются однозначно, как если бы у вас была обычная функция. Используя те, вы получаете код
{-# 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
Бит | a -> n
сообщает GHC, что если он может вывести a
, то он также может вывести n
, так как для каждого a
существует только один n
. Это предотвращает описанные выше экземпляры.
Типы семейств более разные:
{-# 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
Это говорит о том, что помимо функции width
класс HasArea
также имеет тип Area
(или функцию типа, если вы хотите об этом думать). Для каждого HasArea a
вы указываете тип Area a
(который, благодаря ограничению суперкласса, должен быть экземпляром Num
), а затем использовать этот тип как ваш тип номера.
Как отлаживать такие ошибки? Честно говоря, мой лучший совет - "Практика, практика, практика". Со временем вы будете больше использовать для выяснения (а) того, что говорят ошибки, и (б) что, вероятно, пошло не так. Случайное изменение материала - это один из способов сделать это. Однако самый большой совет, который я могу дать, - обратить внимание на строки Couldn't match expected type `Foo' against inferred type `Bar'
. Они сообщают вам, что вычислил компилятор (Bar
) и ожидаемый (Foo
) для этого типа, и если вы можете точно определить, какие именно эти типы, это поможет вам выяснить, где ошибка.