Давайте посмотрим на этот пример.
data A = A [Int]
deriving (Show)
cons :: Int -> A -> A
cons x (A xs) = A (x:xs)
ones :: A
ones = cons 1 ones
Мы ожидаем, что ones
должно быть A [1,1,1,1...]
, потому что все, что мы сделали, это обернули список в конструкторе data
.Но мы были бы неправы.Напомним, что сопоставления с образцами являются строгими для data
конструкторов.То есть cons 1 undefined = undefined
, а не A (1 : undefined)
.Поэтому, когда мы пытаемся оценить совпадения с шаблоном ones
, cons
по второму аргументу, что заставляет нас оценивать ones
... у нас возникает проблема.
newtype
s не делаютэтот.Во время выполнения newtype
конструкторы невидимы, так что, как если бы мы написали эквивалентную программу в простых списках
cons :: Int -> [Int] -> [Int]
cons x ys = x:ys
ones = cons 1 ones
, которая является совершенно производительной, поскольку при попытке оценить ones
возникает :
конструктор между нами и следующая оценка ones
.
Вы можете вернуть семантику newtype
, сделав ваш шаблон конструктора данных совпадающим с ленивым:
cons x ~(A xs) = A (x:xs)
Этопроблема с вашим кодом (я столкнулся с этой проблемой, делая именно эту вещь).Есть несколько причин, по которым data
соответствие шаблонов строго по умолчанию;самое убедительное, что я вижу, это то, что сопоставление с образцом в противном случае было бы невозможно, если бы у типа было более одного конструктора.Существует также небольшая накладная нагрузка времени на сопоставление с ленивым шаблоном, чтобы исправить некоторые незначительные утечки GC;подробности, связанные в комментариях.