Суть в том, что с полиморфным 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