Компилятор не подбирает класс типов для значения полиморфной константы

#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, он соответствует сути. Я только что отметил другой принятый ответ, поскольку он более сложный.