Странное поведение компилятора GHCi Haskell
В тесте меня просят вывести тип:
let pr = map head.group.sortBy(flip compare)
Я сам сделал вывод, что тип был:
Ord a => [a] -> [a]
Однако при выполнении :t
в GHCi он говорит, что тип:
pr :: [()] -> [()]
Что здесь происходит?
Также если в GHCi я делаю:
map head.group.sortBy(flip compare) [1,2,3,4,100,50,30,25,51,70,61]
Я получаю ошибку:
Couldn't match expected type `a0 -> [b0]' with actual type `[a1]'
In the return type of a call of `sortBy'
Probable cause: `sortBy' is applied to too many arguments
In the second argument of `(.)', namely
`sortBy (flip compare) [1, 2, 3, 4, ....]'
In the second argument of `(.)', namely
`group . sortBy (flip compare) [1, 2, 3, 4, ....]'
Однако если я сделаю:
sortBy(flip compare) [1,2,3,4,100,50,30,25,51,70,61]
[100,70,61,51,50,30,25,4,3,2,1]
Работает просто отлично. Почему первое выражение терпит неудачу, когда второе вычисляет sortBy
просто хорошо с точно такими же аргументами?
2 ответа
Ваша первая проблема - это страшная комбинация ограничения мономорфизма, неспособности GHCi сразу увидеть всю программу и расширенные правила по умолчанию GHCi.
В двух словах, Haskell не любит выводить типы с полиморфными ограничениями классов типов (Ord a =>
часть подписи вашего типа) для привязок верхнего уровня, которые записываются как уравнения, которые синтаксически не имеют аргументов. pr = map head.group.sortBy(flip compare)
нарушает это правило (это функция, так что семантически у нее есть аргументы, но у уравнения, которое вы используете для ее определения, нет), поэтому Haskell хочет, чтобы Ord-ограничивался a
быть чем-то конкретным.
Если вы поместите это в исходный файл и скомпилируете его (даже через GHCi):
import Data.List
pr = map head.group.sortBy(flip compare)
Вы получаете прямые ошибки, такие как:
foo.hs:3:33:
No instance for (Ord b0) arising from a use of `compare'
The type variable `b0' is ambiguous
Possible cause: the monomorphism restriction applied to the following:
pr :: [b0] -> [b0] (bound at foo.hs:3:1)
Probable fix: give these definition(s) an explicit type signature
or use -XNoMonomorphismRestriction
Note: there are several potential instances:
instance Integral a => Ord (GHC.Real.Ratio a)
-- Defined in `GHC.Real'
instance Ord () -- Defined in `GHC.Classes'
instance (Ord a, Ord b) => Ord (a, b) -- Defined in `GHC.Classes'
...plus 22 others
In the first argument of `flip', namely `compare'
In the first argument of `sortBy', namely `(flip compare)'
In the second argument of `(.)', namely `sortBy (flip compare)'
Failed, modules loaded: none.
В частности, для некоторых типов (особенно числовых) эта ошибка типа "переменная неоднозначного типа" встречается очень часто и будет раздражать, поэтому в Haskell есть некоторые правила по умолчанию. Например, он будет принимать переменную неоднозначного типа, ограниченную только Num
должно быть Integer
, Конечно, если вы используете функцию где-нибудь в одном файле, вот так:
import Data.List
pr = map head.group.sortBy(flip compare)
answer = pr [1,2,3,4,100,50,30,25,51,70,61]
тогда Haskell может принять во внимание. Он по-прежнему отказывается выводить полиморфный тип для pr
, но в этом случае pr
только когда-либо используется, как если бы это было [Integer] -> [Integer]
, так что это даст ему этот тип и позволит вашему коду скомпилировать, а не выдавать неоднозначную ошибку переменной типа (Integer
само по себе также является результатом дефолта типа).
В GHCi ваш код компилируется по одному выражению за раз, поэтому он не может учитывать ваше использование pr
решить, какой тип дать. Это даст вам неоднозначную ошибку типа, за исключением того, что GHCi расширил правила по умолчанию, которые здесь используются, чтобы "сохранить день" и позволить вашему выражению скомпилироваться. По умолчанию Ord a => a
переменная типа к типу единицы ()
Ваше объявление может быть интерпретировано как определение функции для сжатия произвольных списков ()
в [()]
(или же []
если вход был пустым). Спасибо GHCi!
Вы можете решить эту проблему несколькими способами. Одним из них является добавление аргумента к обеим сторонам вашего определения pr
, вот так:
let pr z = map head.group.sortBy(flip compare) $ z
Теперь уравнение, определяющее pr
имеет аргумент синтаксически (его тип / значение по-прежнему имеет то же количество аргументов), ограничение мономорфизма не срабатывает, и Хаскелл рад вывести полиморфный тип для pr
,
Другой - явно сказать, что вы не хотите использовать ограничение мономорфизма, либо добавив {-# LANGUAGE NoMonomorphismRestriction #-}
в верхней части вашего модуля, или с помощью :set -XNomonomorphismRestriction
по приглашению GHCi. Затем он снова выведет тип Ord a => [a] -> [a]
за pr
,
Третий способ - явно указать полиморфную сигнатуру для вашей функции:
import Data.List
pr :: Ord a => [a] -> [a]
pr = map head.group.sortBy(flip compare)
Или в GHCi:
> let { pr :: Ord a => [a] -> [a] ; pr = map head.group.sortBy(flip compare) }
Поскольку даже с ограничением мономорфизма в силе Хаскелл рад pr
чтобы иметь полиморфный тип, он просто не будет выводить один за него.
Явная сигнатура типа, вероятно, является наиболее распространенным способом избежать этой проблемы в скомпилированных файлах, потому что многие считают хорошим стилем всегда предоставлять сигнатуры типов для определений верхнего уровня. В GHCi это довольно раздражает, как вы можете видеть; Я обычно отключаю ограничение мономорфизма там.
Что касается вашей второй проблемы, я боюсь этого:
map head.group.sortBy(flip compare) [1,2,3,4,100,50,30,25,51,70,61]
сильно отличается от этого:
pr [1,2,3,4,100,50,30,25,51,70,61]
Когда у тебя есть pr
определяется как функция, pr
относится ко всей функции map head.group.sortBy(flip compare)
таким образом, передавая ему аргумент, он передает аргумент этой функции. Но когда вы выписываете все выражение целиком, просто прикрепление списка справа от него не передает его в качестве аргумента всему выражению. Это анализируется немного больше, как это:
(map head) . (group) . (sortBy (flip compare) [1,2,3,4,100,50,30,25,51,70,61])
Как видите, список находится внутри последней функции в конвейере; sortBy (flip compare) [1,2,3,4,100,50,30,25,51,70,61]
используется в качестве функции, которая будет принимать аргумент и передавать свой вывод дальше по конвейеру (для group
). Это явно не имеет смысла, и именно поэтому вы получаете сообщение об ошибке с жалобой на слишком много аргументов sortBy
; это не значит, что вы предоставили слишком много аргументов sortBy
, а скорее, что вы предоставили все его аргументы, а затем использовали его в положении, когда он мог бы иметь возможность принять еще один.
Иногда это может удивлять, пока вы не привыкнете к этому, но любая альтернатива удивляет чаще (вы неявно зависели от парсинга, работающего таким образом при использовании map head
а также sortBy (flip compare)
). Все, что вам нужно сделать, это помнить, что обычное приложение-функция (просто вставляя два выражения рядом друг с другом) всегда имеет более высокий приоритет, чем инфиксные операторы (например, .
); всякий раз, когда у вас есть выражение, смешивающее инфиксные операторы и обычное приложение, каждая нормальная цепочка приложений (группы неоператорных выражений, разделенных только пробелами) становится только одним аргументом в отношении инфиксных операторов (а затем приоритет / ассоциативность используется для определения аргументов инфиксных операторов).
Чтобы исправить это, вам нужно добавить круглые скобки вокруг конвейера композиции, прежде чем вводить аргумент, например:
(map head.group.sortBy(flip compare)) [1,2,3,4,100,50,30,25,51,70,61]
Или использовать $
поставить "стену" между составным конвейером и аргументом, вот так:
map head.group.sortBy(flip compare) $ [1,2,3,4,100,50,30,25,51,70,61]
Это работает, потому что $
это другой инфиксный оператор, поэтому он принудительно разрешает все последовательности "обычного приложения" слева и справа, прежде чем один из них может быть применен к другому. Это также оператор с очень низким приоритетом, поэтому он почти всегда работает, когда в игре есть и другие инфиксные операторы (например, .
). Это довольно распространенная идиома в Хаскеле для написания выражений вида f . g . h $ a
,
Вас укусил дефолт, куда GHCi (интерактивный GHCi, а не GHC что-то компилирует) поместит
()
в любом неопределенном параметре типа в определенных случаях.Я думаю, что вы перепутали
.
а также$
, Рассмотрим ваше оригинальное выражение:map head . group . sortBy(flip compare) [1,2,3,4,100,50,30,25,51,70,61]
Это составляет функции
map head
,group
, а такжеsortBy (flip compare) [...]
, К несчастью,sortBy (flip compare) [...]
это список, а не функция, поэтому его нельзя так составить.sortBy (flip compare)
однако, есть, и если мы скомпонуем эти функции вместе, а затем применим эту функцию к списку, это сработает:map head . group . sortBy (flip compare) $ [1,2,3,4,100,50,30,25,51,70,61]