Преимущества строгих полей в типах данных - PullRequest
30 голосов
/ 20 декабря 2011

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

data Foo = Bar !Int !Float

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

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

Ответы [ 4 ]

36 голосов
/ 20 декабря 2011

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

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

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

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

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

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

data Exp = Infix Op Exp Exp
         | ...

data Op = Add | Subtract | Multiply | Divide

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

1 Распаковать можно только типы с одним конструктором.

8 голосов
/ 06 июня 2015

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

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

Интересно посмотреть, как глубоко оценивается параметр - это похоже на 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 сами не содержат строгих параметров.

4 голосов
/ 21 декабря 2011

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

Стоимость в два раза:

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

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

...