In a data declaration, you can flag some fields with !
as “strict”.
For example, here is a data type with two Int
fields, where the second one is flagged as strict:
data J = Ctor Int !Int
“Operational” means computational steps. Most Haskell compilers use lazy evaluation for many (but not all) computational steps. Beware that the Haskell standard does not require lazy evaluation, in fact it mostly speaks denotationally. (See the next section for the denotational story.) So the operational story is only true for now, not future-proof. But where this story is true, the strictness flag requests less laziness.
The first field of Ctor does not have a strictness flag, so it is evaluated lazily as usual. If I put a problematic expression there, then go on to avoid it, no problem happens.
case Ctor (div 1 0) 5 of Ctor _ n -> n
It evaluates to 5 successfully.
The second field of Ctor has a strictness flag. It is evaluated more eagerly. If I put a problematic expression there, it is more likely to cause problems, and earlier.
case Ctor 5 (div 1 0) of Ctor m _ -> m
I already try to avoid the problematic expression, but it will still cause failure.
The precise criterion: The strict field is evaluated when the parent is evaluated (for example I used pattern matching to evaluate the parent).
To show the flip side of the criterion, here is an example of bearing the problematic expression but still causing no problem, since I avoid evaluating the parent, I only build it, give it a name, then give it more names.
let x = Ctor 5 (div 1 0) in case x of v -> ()
“Denotational” means you don't describe computational steps (the “how”), only whether you will get an answer, and if so, what answer (the “what”). (Also, it means defining answers by structural induction on expression trees.) The Haskell standard is mostly denotational, and “strict” is a denotational notion, not an operational notation.
This is bound to be lost on most people, since they cannot imagine that there could be any story to tell other than computational steps, or any story at a higher level. Therefore I began with the operational story first, even though it is neither universal nor future-proof.
The symbol ⊥ stands for “no answer”. Where there is no answer, we still like to ironically say “the answer is ⊥”, for covenience and uniformity. It is why we make a symbol ⊥ for this.
A strict field means that we want this: If the field has no answer, then this
causes the parent to have no answer either. Example: div 1 0
has no
answer, so if we put it in a strict field, the parent Ctor 5 (div 1
0)
has no answer either. Again, we also say: the answer of div 1
0
is ⊥, so the answer of the whole Ctor 5 (div 1 0)
is also
⊥.
The negation of “strict” is called “non-strict”. A field without a
!
flag is a non-strict field, which means that even when the field
has no answer, we still consider the parent to have an answer other than
⊥. Example: The answer of Ctor (div 1 0) 5
is Ctor ⊥ 5, and this
does not equal ⊥. (This looks boringly tautological, but this is only because we
are talking about data, there is nothing to simplify further. Strict functions
and non-strict functions will be more interesting.)
To better observe their difference, it is useful to know the denotational story of pattern matching against a data constructor. The answer of this pattern-matching expression
case expr of Ctor m n -> body
depends on the the answer of expr:
If the answer of expr is ⊥, then the answer of the whole case is also ⊥.
This happens when expr is Ctor 5 (div 1 0)
.
If the answer of expr is Ctor subanswer1
subanswer2, then the answer of the whole case is the answer of
body with subanswer1 plugged into m
,
subanswer2 plugged into n
.
This happens when expr is Ctor (div 1 0) 5
, i.e.,
Ctor ⊥ 5. We plug ⊥ into m
, 5 into n
. If furthermore
body is simply n
, then the final answer is 5.
I have more Haskell Notes and Examples