Что на самом деле означает аргумент, не зависящий от типа, в ограничении?

Например:

map (+1) 2

в ghci дает

<interactive>:23:1: error:
* Non type-variable argument in the constraint: Num [b]
  (Use FlexibleContexts to permit this)
* When checking the inferred type
    it :: forall b. (Num b, Num [b]) => [b]

Я видел много вопросов, похожих на мой, но все они, кажется, отвечают только на то, что мы можем сделать из этого (что тип второго аргумента для map неверен), и как это исправить, но не на то, какая ошибка на самом деле означает . Где именно что-то идет не так?


person Nearoo    schedule 15.03.2020    source источник
comment
Что произошло, когда вы погуглили ошибку?   -  person Michael Litchard    schedule 15.03.2020
comment
Что происходит, так это то, что я вижу, цитирую, много вопросов, похожих на мой, но все они, кажется, отвечают только на то, что мы можем вывести из этого (что тип второго аргумента для карты неверен), и как это исправить - но не на то, что ошибка на самом деле означает.   -  person Nearoo    schedule 15.03.2020
comment
Я считаю, что этот мой ответ охватывает большую часть вашего вопроса (конструктор типа был (->), а не [], но проблема примерно так же).   -  person duplode    schedule 15.03.2020


Ответы (2)


Ошибка возникает при выводе типа вашего утверждения.

С

  1. (+1) относится к типу Num a => a -> a
  2. 2 относится к типу Num a => a
  3. map относится к типу (a -> b) -> [a] -> [b]

Мы знаем, что map (+1) должно быть типа (Num b) => [b] -> [b] и, следовательно, map (+1) 2 типа (Num b, Num [b]) => [b]. Но [b] — это не просто переменная типа, это список некоторой переменной типа, где список — это конструктор данных. В альтернативной версии Haskell, где не существует синтаксического сахара для списков, мы могли бы написать (Num b, Num (List b)).

Это проблема, потому что по умолчанию Haskell не поддерживает аргументы, не зависящие от типа, для ограничений. Таким образом, истинная природа проблемы не в том, что Haskell не знает, как отображать числа, а в том, что он не допускает значений типа, который создает наш вызов функции.

Но это правило не является строго необходимым. Добавляя -XFlexibleContexts при вызове ghci, теперь разрешены типы, которые производит наш метод. Причина этого в том, что литерал 2 в Haskell на самом деле не представляет число — он представляет объект типа Num a => a, который создается из интеграла 2 с использованием fromIntegral. Таким образом, оператор map (+1) 2 эквивалентен map (+1) (fromIntegral (2::Integer)). Это означает, что литерал "2" может представлять что угодно, при условии надлежащего инстанцирования, включая списки.

person Nearoo    schedule 15.03.2020
comment
Зачем вам исправлять это, присваивая результат неиспользуемой переменной, а не просто исключая вызов? - person chepner; 15.03.2020
comment
Вторая половина этого ответа неверна: тип результата не Num [Integer], а скорее Num [Integer] => [Integer]; и проблема в том, что Haskell (по умолчанию) не знает, как отображать одно число, и нет, поскольку вы утверждаете, что он не знает, как отображать результат. - person Daniel Wagner; 15.03.2020
comment
@DanielWagner ty, исправил это. Ко второй части: как бы вы научили его такому поведению? Вам придется переопределить map, верно? Что я бы не стал учить тому, как составлять карту, а скорее изменить то, что означает карта. - person Nearoo; 15.03.2020
comment
@chepner Это чисто теоретическая проблема в образовательных целях. Я не знаю, что должен выдать map (+1) 2, даже если бы это сработало. Это больше о том, что означает сообщение об ошибке / где что-то идет не так. - person Nearoo; 15.03.2020
comment
@Nearoo Например, instance Num a => Num [a] where fromInteger x = [fromInteger x] заставит работать map (+1) 2. Но я не рекомендую этого делать; вместо этого более четко определите, какие вычисления вы хотите выполнить, например, начиная с map (+1) [2] или любого другого списка, который вы на самом деле имели в виду. 2 — это не список, и научить компилятор притворяться, что это хороший способ ввести тонкие ошибки. - person Daniel Wagner; 15.03.2020
comment
@DanielWagner интересно. как этот экземпляр заставляет его работать? Это меня удивляет. - person Nearoo; 15.03.2020
comment
@Nearoo Когда вы пишете 2, компилятор говорит, что это тип Num a => a. Это означает, что 2 может быть любым типом, имеющим экземпляр Num. Запись instance Num a => Num [a] создает экземпляр Num для списков, так что 2 можно интерпретировать как список. - person Daniel Wagner; 15.03.2020
comment
@DanielWagner хорошо... позвольте мне быстро перенести это в чат - person Nearoo; 15.03.2020
comment
Давайте продолжим обсуждение в чате. - person Nearoo; 15.03.2020

2 имеет тип Num a => a; мы не указали, что такое a , за исключением того, что он должен иметь экземпляр Num.

map (+1) имеет тип Num b => [b] -> [b]; мы указали, что такое b, за исключением того, что он должен иметь экземпляр Num.

Когда мы определяем тип map (+1) 2, мы в основном объединяем Num a ~ Num b => [b].

2 ::                  Num a    => a
map (+1) ::    Num b           => [b] -> [b]
map (+1) 2 :: (Num b, Num [b]) =>        [b]

И в этом проблема. Num требует в качестве аргумента переменную типа, например a или b, а не полиморфный тип, например [b].

person chepner    schedule 15.03.2020