#haskell #typeclass
#haskell #typeclass
Вопрос:
Я новичок в Haskell, так что заранее простите меня.
Почему следующий код на haskell не компилируется?
Похоже, компилятор каким-то образом не видит, что типом выражения (maxBound :: a)
является a
то, для которого предоставлен Enum
экземпляр, а не то, type variable ‘a0’
которое ambiguous
есть.
class (Enum a, Bounded a) => SafeEnum a where
ssucc :: a -> a
ssucc x = if (fromEnum x) < (fromEnum (maxBound :: a)) then succ x else minBound
spred :: a -> a
spred x = if (fromEnum x) > (fromEnum (minBound :: a)) then pred x else maxBound
Stepik.hs:3:32: error:
• Could not deduce (Enum a0) arising from a use of ‘fromEnum’
from the context: SafeEnum a
bound by the class declaration for ‘SafeEnum’
at Stepik.hs:(1,1)-(6,82)
The type variable ‘a0’ is ambiguous
These potential instances exist:
instance Enum Ordering -- Defined in ‘GHC.Enum’
instance Enum Integer -- Defined in ‘GHC.Enum’
instance Enum () -- Defined in ‘GHC.Enum’
...plus six others
Комментарии:
1. Не собираюсь указывать в качестве ответа, потому что я не понимаю всех тонкостей (кто-то другой может все это объяснить, кто действительно это понимает!) — но это работает, если вы включаете
ScopedTypeVariables
расширение
Ответ №1:
По умолчанию, даже несмотря на то, что переменные типа ограничены сигнатурами типов определяемого класса для методов класса (т. Е. a
in class SafeEnum a
совпадает a
с a
in ssucc :: a -> a
), они не ограничены сигнатурами типов методов для тел методов, поэтому в выражении maxBound :: a
в телах ваших функций ssucc
и spred
, a
не имеет ничего общего с a
в сигнатурах типов для этих функций.
Вы можете включить ScopedTypeVariables
расширение, вот так:
{-# LANGUAGE ScopedTypeVariables #-}
после чего определение класса будет проверять тип.
Обратите внимание, что это расширение применяется только к «обычным» объявлениям функций, если вы используете forall
ключевое слово. Итак, вне определения класса вам нужно было бы включить это расширение и написать:
ssucc :: forall a. a -> a
ssucc x = ... maxBound :: a ...
или на самом деле:
ssucc :: forall a. (Enum a, Bounded a) => a -> a
ssucc x = ... maxBound :: a ...
но правила отличаются внутри class
предложения.
Подробности см. в документах GHC.
Комментарии:
1. И это работает! Спасибо! Это поведение казалось настолько естественным, что я не мог догадаться, что по умолчанию его там нет…
2. @AntonSavelyev У меня такое чувство, что по умолчанию его там нет, чтобы предотвратить нежелательное поведение в ситуациях, когда вы ожидаете, что переменные типа будут разделены. Например, если вы определяете несколько функций верхнего уровня, которые все используют
a
, вы ожидаете, что они будут разнымиa
. Аналогично, если вы определяете несколько функций в a,let ... in
которые все используютa
, вы, вероятно, не хотите, чтобы все они означали одно и то жеa
.
Ответ №2:
Вам нужно добавить эту строку в начало вашего файла:
{-# LANGUAGE ScopedTypeVariables #-}
Без включения этого расширения, maxBound :: a
не ссылается на то же a
, что и в классе.
По сути, в стандартном Haskell каждая сигнатура типа имеет свои собственные переменные типа, которые независимы от любой другой переменной. Например, этот код
foo :: [a] -> Int
foo xs = length ys
where
ys :: [a]
ys = xs
сбой, поскольку ys :: [a]
действительно означает ys :: [b]
с независимой переменной b
, и ys = xs
не выдает [b]
.
При включенном расширении это компилирует:
foo :: forall a . [a] -> Int
foo xs = length ys
where
ys :: [a]
ys = xs
Возможно, должно быть другое значение по умолчанию, например, расширение должно быть включено по умолчанию. В качестве альтернативы, GHC должен указывать на включение расширения, когда одно и то же a
используется дважды, поскольку часто это проблема.
Комментарии:
1. Большое спасибо за ваш ответ @chi, он соответствует сути. Я только что отметил другой принятый ответ, поскольку он более сложный.