Это декларация строгости. По сути, это означает, что при создании значения структуры данных оно должно быть оценено как так называемая «слабая нормальная форма заголовка». Давайте рассмотрим пример, чтобы понять, что это значит:
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)
, оставляя thunk внутри неоцененным. Это важно знать, хотя я думаю, что все последствия этого выходят за рамки этого вопроса.
Вы можете аннотировать аргументы функций таким же образом, если вы включите расширение языка BangPatterns
:
f x !y = x*y
f (1+1) (2+2)
вернет гром (1+1)*4
.