2011-12-20 5 views
27

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

data Foo = Bar !Int !Float 

Я часто думал, что лень это великая вещь. Теперь, когда я просматриваю источники, я вижу строгие поля чаще, чем ! без вариантов.

В чем преимущество этого и почему я не должен оставлять его ленивым, как есть?

ответ

32

Если вы не храните большое вычисление в полях Int и Float, значительные накладные расходы могут возникать из множества тривиальных вычислений, создаваемых в thunks. Например, если вы повторно добавляете 1 к ленивому полю Float в типе данных, он будет использовать все больше и больше памяти, пока вы на самом деле не заставите поле, вычислив его.

Часто вы хотите сохранить дорогостоящие вычисления в поле. Но если вы знаете, что раньше не делаете ничего подобного, вы можете пометить поле строгое, и не нужно вручную добавлять seq всюду, чтобы получить желаемую эффективность.

В качестве дополнительного бонуса, когда данный флаг -funbox-strict-fields GHC распакует жесткие поля типов данных непосредственно в самом тип данных, что возможно, так как он знает, что они всегда будут оценены, и, таким образом, нет преобразователя не должен выделяться; в этом случае значение Bar будет содержать машинные слова, содержащие Int и Float, непосредственно внутри значения Bar в памяти, вместо того, чтобы содержать два указателя на thunks, которые содержат данные.

Laziness - очень полезная вещь, но некоторое время она просто мешает и мешает вычислению, особенно для небольших полей, которые всегда смотрятся (и, следовательно, вынуждены), или которые часто изменяются, но никогда очень дорогостоящие вычисления. Строгие поля помогают преодолеть эти проблемы, не изменяя все виды использования типа данных.

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

Допустим, у вас есть AST с конструктором для инфиксные операций:

data Exp = Infix Op Exp Exp 
     | ... 

data Op = Add | Subtract | Multiply | Divide 

Вы не хотели бы сделать Exp поля строги, как применение политики, как это будет означать, что вся AST оценивается когда вы смотрите на узел верхнего уровня, что явно не то, что вы хотите извлечь из лени. Однако поле Op никогда не будет содержать дорогостоящие вычисления, которые вы хотите отложить до более поздней даты, а накладные расходы на thunk для каждого инфиксного оператора могут стать дорогими, если у вас действительно глубоко вложенные деревья синтаксического анализа. Итак, для конструктора infix вы должны сделать поле Op строгим, но оставьте полями Exp ленивыми.

Только одноконструкторы могут быть распакованы.

+1

Как АСТ не должно быть столь же строгим? – Lanbo

+0

Я расширил свой ответ с некоторой разработки с использованием АСТ в качестве примера. – ehird

4

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

4

Lazyness стоит дорого, иначе каждый язык будет иметь его.

Стоимость 2 раза:

  1. Это может занять больше времени, чтобы настроить преобразователь, (то есть описание того, что должно быть вычислено, когда он будет вычислен в конце концов), чем делать операцию немедленно.
  2. Неоценимые трюки, которые идут как не строгие аргументы другим трюкам, которые идут как не строгие аргументы для других трюков и т. Д., Будут использовать все больше и больше памяти. К сожалению, эти туннели могут также содержать ссылки на память, которая больше не доступна, т. Е. Память, которая может быть освобождена, когда будет оцениваться только тонка, тем самым предотвращая работу сборщика мусора. Примером может быть thunk, который должен обновить определенное значение в дереве. Скажите, что это значение имеет значение 100MB других значений. Если больше нет ссылки на старое дерево, эта память будет потрачена впустую до тех пор, пока бит не будет оценен.
5

В дополнение к информации, предоставляемой другими ответами, имейте в виду:

Насколько мне известно, с !, можно убедиться, что параметр для конструктора данных оценивается прежде, чем значение строится

это интересно посмотреть на то, как deep the parameter is evaluated - это как с seq и $! оцениваемой в WHNF.

Учитывая типы данных

data Foo = IntFoo !Int | FooFoo !Foo | BarFoo !Bar 
data Bar = IntBar Int 

выражение

let x' = IntFoo $ 1 + 2 + 3 
in x' 

оценивали с WHNF производит значение IntFoo 6 (== полностью оценены, == NF).
Кроме того это выражение

let x' = FooFoo $ IntFoo $ 1 + 2 + 3 
in x' 

оценивали с WHNF производит значение FooFoo (IntFoo 6) (== полностью оценены, == NF).
Однако это выражение

let x' = BarFoo $ IntBar $ 1 + 2 + 3 
in x' 

оценивается в WHNF производит значение BarFoo (IntBar (1 + 2 + 3)) (! = Полная оценка,! = NF).

Главная точка: Строгость параметра !Bar не обязательно поможет, если Конструкторы данных о Bar не содержат Строгие сами параметров.

+0

Очень четкие примеры! Могу ли я проверить, насколько глубоко оценивается параметр в ghci или других инструментах? – wenlong

+1

@wenlong См. Еще [SO ответ на визуализацию thunks] (http://stackoverflow.com/questions/16767028/any-way-to-visualize-a-thunk-function-or-how-to-view-a-function -for-a-general), в частности инструмент ['ghc-vis'] (http://felsin9.de/nnis/ghc-vis/#basic-usage) – mucaho