Что означает восклицательный знак в декларации Haskell?

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

data MidiMessage = MidiMessage !Int !MidiMessage

Ответ 1

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

data Foo = Foo Int Int !Int !(Maybe Int)

f = Foo (2+2) (3+3) (4+4) (Just (5+5))

Функция f выше, когда оценивается, вернет "thunk": то есть код для выполнения, чтобы выяснить его значение. В этот момент Foo еще не существует, просто код.

Но в какой-то момент кто-то может попытаться заглянуть внутрь, возможно, через совпадение с шаблоном:

case f of
     Foo 0 _ _ _ -> "first arg is zero"
     _           -> "first arge is something else"

Это позволит выполнить достаточно кода, чтобы делать то, что ему нужно, и не более того. Таким образом, он создаст Foo с четырьмя параметрами (потому что вы не можете заглядывать в него без его наличия). Во-первых, поскольку мы тестируем его, мы должны оценить весь путь до 4, где мы понимаем, что он не соответствует.

Второе не нужно оценивать, потому что мы его не тестируем. Таким образом, вместо сохранения 6 в этом месте памяти мы просто сохраним код для возможной последующей оценки (3+3). Это превратится в 6, только если кто-то посмотрит на него.

Третий параметр, однако, имеет перед ним !, поэтому строго оценивается: (4+4) выполняется, а 8 хранится в этой ячейке памяти.

Четвертый параметр также строго оценивается. Но здесь, где это становится немного сложно: мы оцениваем не полностью, но только для слабой нормальной формы головы. Это означает, что мы выясняем, есть ли это Nothing или Just что-то, и сохраним это, но мы идем дальше. Это означает, что мы сохраняем не Just 10, а фактически Just (5+5), оставляя тнк внутри неоценимым. Это важно знать, хотя я думаю, что все последствия этого скорее выходят за рамки этого вопроса.

Вы можете аннотировать аргументы функции таким же образом, если вы включите расширение языка BangPatterns:

f x !y = x*y

f (1+1) (2+2) вернет thunk (1+1)*4.

Ответ 2

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

data Foo = Foo Int !Int

first (Foo x _) = x
second (Foo _ y) = y

Поскольку нестрогий аргумент не оценивается second, передача в undefined не вызывает проблемы:

> second (Foo undefined 1)
1

Но строгий аргумент не может быть undefined, даже если мы не используем значение:

> first (Foo 1 undefined)
*** Exception: Prelude.undefined

Ответ 3

Я считаю, что это аннотация строгости.

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

Более подробная информация на этой странице: Performance/Strictness.