В чем смысл декларации строгости?

Я запускаю Haskell и смотрю на некоторые библиотеки, где типы данных определяются с помощью "!". Пример из библиотеки байтов:

data ByteString = PS {-# UNPACK #-} !(ForeignPtr Word8) -- payload
                     {-# UNPACK #-} !Int                -- offset
                     {-# UNPACK #-} !Int                -- length

Теперь я увидел этот вопрос как объяснение того, что это значит, и я думаю, это довольно легко понять. Но теперь мой вопрос: какой смысл использовать это? Поскольку выражение будет оцениваться всякий раз, когда это необходимо, почему вы вынуждаете раннюю оценку?

Во втором ответе на этот вопрос C.V. Хансен говорит: "[...] иногда накладные расходы на ленивость могут быть слишком большими или расточительными". Предполагается ли это, что он используется для сохранения памяти (сохранение значения дешевле, чем сохранение выражения)?

Объяснение и пример будут замечательными!

Спасибо!

[ EDIT] Я думаю, что я должен был выбрать пример без {- # UNPACK # -}. Поэтому позвольте мне сделать это сам. Будет ли это когда-либо иметь смысл? Да, почему и в какой ситуации?

data MyType = Const1 !Int
            | Const2 !Double
            | Const3 !SomeOtherDataTypeMaybeMoreComplex

Ответ 1

Цель здесь - не строгость, а упаковка этих элементов в структуру данных. Без строгости любой из этих трех аргументов конструктора мог указывать либо на структуру значений, распределенную по кучу, либо на выделенную кучу задержанную оценку. Со строгостью он может указывать только на структуру значений, распределенных по кучам. Со строгими и упакованными структурами можно сделать эти значения встроенными.

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

В более общем случае аннотация по строгости может помочь уменьшить утечки пространства. Рассмотрим такой случай:

data Foo = Foo Int

makeFoo :: ReallyBigDataStructure -> Foo
makeFoo x = Foo (computeSomething x)

Без аннотации строгости, если вы просто вызываете makeFoo, он построит a Foo, указывая на thunk, указывающий на ReallyBigDataStructure, сохраняя его в памяти до тех пор, пока что-то не заставит thunk оценивать. Если мы вместо этого имеем

data Foo = Foo !Int

Это заставляет оценку computeSomething действовать немедленно (ну, как только что-то заставляет makeFoo), что позволяет избежать ссылки на ReallyBigDataStructure.

Обратите внимание, что это другой вариант использования, чем код байтовой строки; код bytestring сильно заставляет его параметры, что вряд ли приведет к утечке пространства. Вероятно, лучше всего интерпретировать код байтовой строки как чистую оптимизацию, чтобы избежать разметки указателя.