Подтвердить что ты не робот

Системные нюансы типа 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.

И я не понимаю, почему.

Примечание. Пожалуйста, не спрашивайте: "Что вы пытаетесь сделать?" Я надеюсь, что очевидно, что я просто возился в абстрактном контексте, чтобы исследовать, как работает этот язык. У меня нет цели, кроме изучения чего-то нового.

Спасибо

4b9b3361

Ответ 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 (или любыми экземплярами, определенными в других модулях, поэтому удаление этих экземпляров не приведет к компиляции программы).