Системные нюансы типа Haskell
Я попадал в ничтожный тип файловой системы haskell и пытаюсь понять тонкости классов классов. Я узнал кучу, но я нажимаю на стену на следующих фрагментах кода.
Используя эти определения класса и экземпляра:
class Show a => C a where
f :: Int -> a
instance C Integer where
f x = 1
instance C Char where
f x = if x < 10 then 'c' else 'd'
Почему это проходит проверку типа:
g :: C a => a -> Int -> a
g x y = f y
yes :: C a => a -> Int -> String
yes x y = show (g x y)
но это не так?
g :: C a => a -> Int -> String
g x y = show(f y)
Я нахожу вторую альтернативу более читаемой, и это, по-видимому, лишь незначительная разница (обратите внимание на подписи типа). Однако, пытаясь получить это, результат typechecker приводит к:
*Main> :l typetests.hs
[1 of 1] Compiling Main ( typetests.hs, interpreted )
typetests.hs:11:14:
Ambiguous type variable `a0' in the constraints:
(C a0) arising from a use of `f' at typetests.hs:11:14
(Show a0) arising from a use of `show' at typetests.hs:11:9-12
Probable fix: add a type signature that fixes these type variable(s)
In the first argument of `show', namely `(f y)'
In the expression: show (f y)
In an equation for `g': g x y = show (f y)
Failed, modules loaded: none.
И я не понимаю, почему.
Примечание. Пожалуйста, не спрашивайте: "Что вы пытаетесь сделать?" Я надеюсь, что очевидно, что я просто возился в абстрактном контексте, чтобы исследовать, как работает этот язык. У меня нет цели, кроме изучения чего-то нового.
Спасибо
Ответы
Ответ 1
Здесь играет забавная игрушка. Рассмотрим стандартную функцию Prelude asTypeOf
.
asTypeOf :: a -> a -> a
asTypeOf = const
Он просто возвращает свой первый аргумент, независимо от второго аргумента. Но сигнатура типа на нем устанавливает дополнительное ограничение, что оба его аргумента должны быть одного и того же типа.
g :: C a => a -> Int -> String
g x y = show (f y `asTypeOf` x)
Теперь GHC знает, что такое тип f y
. Он аналогичен типу первого аргумента g
. Без этой информации, вы получите сообщение об ошибке, которое вы видели. Недостаточно информации для определения типа f y
. И поскольку тип важен (он используется для определения того, какой экземпляр используется для show
), GHC должен знать, какой тип вы хотите генерировать код.
Ответ 2
Это вариант пресловутой проблемы show . read
. В классической версии используется
read :: Read a => String -> a
show :: Show a => a -> String
поэтому композиция может показаться простым старым преобразователем String
moo :: String -> String
moo = show . read
за исключением того, что в программе нет информации для определения типа в середине, следовательно, что нужно для read
, а затем show
.
Ambiguous type variable `b' in the constraints:
`Read b' arising from a use of `read' at ...
`Show b' arising from a use of `show' at ...
Probable fix: add a type signature that fixes these type variable(s)
Пожалуйста, не то, что ghci делает кучу сумасшедших дополнительных дефолтов, разрешая неоднозначность произвольно.
> (show . read) "()"
"()"
Ваш C
класс является вариантом read
, за исключением того, что он декодирует Int
вместо чтения String
, но проблема в основном одинаков.
В энтузиасты системных систем будут замечены, что переменные поддержанного типа сами по себе не являются серьезным делом. Это двусмысленный вывод о том, что проблема здесь. Рассмотрим
poo :: String -> a -> a
poo _ = id
qoo :: (a -> a) -> String
qoo _ = ""
roo :: String -> String
roo = qoo . poo
В построении roo
никогда не определяется, какой должен быть тип в середине, и не является roo
полиморфным в этом типе. Вывод типа не решает и не обобщает переменную! Тем не менее,
> roo "magoo"
""
это не проблема, потому что конструкция является параметрической в неизвестном типе. Тот факт, что тип не может быть определен, приводит к тому, что тип не имеет значения.
Но неизвестные случаи явно имеют значение. Результат полноты для вывода типа Хиндли-Милнера зависит от параметричности и, таким образом, теряется при добавлении перегрузки. Давайте не будем плакать о нем.
Ответ 3
В
g :: C a => a -> Int -> a
g x y = f y
возвращаемый тип f y
фиксируется сигнатурой типа, поэтому, если вы вызываете, например. g 'a' 3
, instance C Char
. Но в
g :: C a => a -> Int -> String
g x y = show(f y)
существует два ограничения на тип возвращаемого значения f
: он должен быть экземпляром C
(чтобы можно было использовать f
) и Show
(чтобы можно было использовать Show
). И это все! Совпадение имен переменных типа a
в определениях f
и g
ничего не значит. Таким образом, у компилятора нет возможности выбирать между instance C Char
и instance C Integer
(или любыми экземплярами, определенными в других модулях, поэтому удаление этих экземпляров не приведет к компиляции программы).