:sprint для полиморфных значений?

Мне интересно, почему :sprint сообщает xs = _ в этом случае:

Prelude> xs = map (+1) [1..10]
Prelude> length xs
10
Prelude> :sprint xs
xs = _

но не в этом случае:

Prelude> xs = map (+1) [1..10] :: [Int]
Prelude> length xs
10
Prelude> :sprint xs
xs = [_,_,_,_,_,_,_,_,_,_]

Примечание. Я запускаю ghci с -XNoMonomorphismRestriction. Связано ли это с тем, что тип xs в первом случае полиморфен, а во втором нет? Я хотел бы знать, что происходит внутри.


person ErikR    schedule 03.02.2014    source источник
comment
Это именно та путаница, для предотвращения которой предназначено ограничение мономорфизма, поэтому вам следует определенно прочитать вики-страница на нем.   -  person Daniel Wagner    schedule 03.02.2014


Ответы (1)


Суть в том, что с полиморфным xs он имеет тип вида

xs :: Num a => [a]

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

xs :: NumDict a -> [a]

Итак, когда вы бежите

Prelude> length xs

Он должен выбрать какое-то значение для a и найти соответствующее значение NumDict. IIRC заполнит его Integer, поэтому вы фактически вызываете функцию и проверяете длину полученного списка.

Когда вы затем :sprint xs, вы снова заполняете этот аргумент, на этот раз с новой переменной типа. Но дело в том, что вы получаете совершенно другой список, вы дали ему другой NumDict, поэтому он никоим образом не навязывается, когда вы вызывали length раньше.

Это сильно отличается от явно мономорфного списка, поскольку там действительно только один список, есть только одно значение, которое нужно принудительно использовать, поэтому, когда вы вызываете length, он принудительно использует его для всех будущих применений xs.

Чтобы сделать это немного яснее, рассмотрим код

data Smash a = Smash { smash :: a -> a -> a }
-- ^ Think of Monoids

intSmash :: Smash Int
intSmash = Smash (+)

listSmash :: Smash [a]
listPlus = Smash (++)

join :: Smash a -> [a] -> a
join (Smash s) xs = foldl1' s xs

Это действительно то, на что похожи классы типов под капотом, GHC автоматически заполнит этот первый аргумент Smash a для нас. Теперь ваш первый пример похож на join, мы не можем делать никаких предположений о том, каким будет результат, поскольку мы применяем его к разным типам, но ваш второй пример больше похож на

join' :: [Int] -> Int
join' = join intSmash
person Daniel Gratzer    schedule 03.02.2014
comment
Этому объяснению трудно следовать. - person mljrg; 08.09.2015
comment
@mljrg Какая-то конкретная часть? - person Daniel Gratzer; 08.09.2015
comment
Поскольку внутренняя работа классов типов и материалов NumDict не детализирована и не проиллюстрирована, трудно понять ваш ответ. Можете дать ссылку, где это хорошо объясняется? В любом случае кажется, что :sprint страдает от некоторых побочных эффектов реализации, что иронично для Haskell... Обратите внимание, что если length xs нужно перемещаться по всему списку, то :sprint всегда должно печатать [_,_, ...] независимо от типа элементов в списке. список. Вокруг какой-то баг... - person mljrg; 08.09.2015