Haskell 'seq' оценивает аргументы избыточно?

Если я понимаю, обсуждение здесь правильно, seq не следует оценивать значение в два раза, а в x 'seq' x должны быть оценивая x раз.

Тогда почему у меня такое поведение?

λ> :set +s
λ> let fib x = if x <= 1 then x else fib (x - 1) + fib (x - 2)
(0.01 secs, 102,600 bytes)
λ> fib 30
832040
(2.49 secs, 638,088,448 bytes)
λ> let x = fib 30 in x
832040
(2.47 secs, 638,088,792 bytes)
λ> let x = fib 30 in x 'seq' x
832040
(4.95 secs, 1,276,067,128 bytes)

что, очевидно, двойная оценка? Я что-то не понимаю?

EDIT: как я спросил @danidiaz ниже, я также оценил

λ> (\x -> x 'seq' x) (fib 30)
832040
(2.51 secs, 638,087,888 bytes)
λ> let x = (fib 30) :: Int in x 'seq' x
832040
(2.52 secs, 732,476,640 bytes)

которые еще более удивительны.

EDIT 2: Я вижу, что этот вопрос был отмечен как дубликат более раннего вопроса, который задает вопрос об ограничении мономорфизма. Когда я столкнулся с этой проблемой, я понятия не имел, что это связано с ограничением. Поэтому, если кто-то найдет меня в моем положении, я думаю, что ответ на этот вопрос будет полезен.

Ответы

Ответ 1

Для первой части этого ответа :set -XNoMonomorphismRestriction в ghci. Это будет объяснено позже.

Наивно можно было бы ожидать, что в Haskell let x = 5 in (x + 1,x + 2) всегда будет эквивалентно (\x → (x + 1, x + 2)) 5. Но у них разные типы!

let x = 5 in (x + 1,x + 2) :: (Num a, Num b) => (a, b)

(\x -> (x + 1,x + 2)) 5 :: Num b => (b, b)

Причина - особенность Haskell, называемого let-bound полиморфизмом. В отличие от lambda-связанных идентификаторов, идентификаторы, связанные в let могут быть созданы экземплярами по-разному в теле let. Например:

ghci> let f = id in (f True, f 'a')
(True,'a')

ghci> (\f -> (f True, f 'a')) id
*** ERROR ***

Теперь вы не указали типу подписи для функции fib, и тот, который выведен, является чем-то вроде

fib :: (Ord a, Num a) => a -> a

который будет работать для разных экземпляров Num таких как Int, Float и т.д.

Но из-за этого, когда вы пишете x 'seq' x, ghci не может быть уверен, что два x фактически одного типа! И если они могут быть разными, тогда их нельзя разделить.

То, что (\x → x 'seq' x) (fib 30) действительно имеет общий доступ. Поскольку x привязан к lambda, компилятор уверен, что оба вхождения действительно имеют одинаковое значение. То же самое для let x = (fib 30) :: Int in x 'seq' x потому что мы удалили полиморфизм с использованием явного типа.

Там другой выход. Включение -XMonomorphismRestriction расширение увеличивает количество типа невыплаты, в результате чего let выражение более мономорфное чем можно было бы ожидать. Этого должно быть достаточно для восстановления совместного использования в этом случае.