ChatGPT解决这个技术问题 Extra ChatGPT

What does the exclamation mark mean in a Haskell declaration?

I came across the following definition as I try to learn Haskell using a real project to drive it. I don't understand what the exclamation mark in front of each argument means and my books didn't seem to mention it.

data MidiMessage = MidiMessage !Int !MidiMessage
I suspect that this might be a very common question; I do clearly remember wondering about the exact same thing myself, way back when.

s
stenlan

It's a strictness declaration. Basically, it means that it must be evaluated to what's called "weak head normal form" when the data structure value is created. Let's look at an example, so that we can see just what this means:

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

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

The function f above, when evaluated, will return a "thunk": that is, the code to execute to figure out its value. At that point, a Foo doesn't even exist yet, just the code.

But at some point someone may try to look inside it, probably through a pattern match:

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

This is going to execute enough code to do what it needs, and no more. So it will create a Foo with four parameters (because you can't look inside it without it existing). The first, since we're testing it, we need to evaluate all the way to 4, where we realize it doesn't match.

The second doesn't need to be evaluated, because we're not testing it. Thus, rather than 6 being stored in that memory location, we'll just store the code for possible later evaluation, (3+3). That will turn into a 6 only if someone looks at it.

The third parameter, however, has a ! in front of it, so is strictly evaluated: (4+4) is executed, and 8 is stored in that memory location.

The fourth parameter is also strictly evaluated. But here's where it gets a bit tricky: we're evaluating not fully, but only to weak normal head form. This means that we figure out whether it's Nothing or Just something, and store that, but we go no further. That means that we store not Just 10 but actually Just (5+5), leaving the thunk inside unevaluated. This is important to know, though I think that all the implications of this go rather beyond the scope of this question.

You can annotate function arguments in the same way, if you enable the BangPatterns language extension:

f x !y = x*y

f (1+1) (2+2) will return the thunk (1+1)*4.


This is very helpful. I can't help wondering however whether the Haskell terminology is making things more complicated for people to understand with terms such as "weak normal head form", "strict" and so forth. If I understand you correctly, it sounds like the ! operator simply means to store the evaluated value of an expression rather than storing an anonymous block to evaluate it later. Is that a reasonable interpretation or is there something more to it?
@David The question is how far to evaluate the value. Weak-head normal form means: evaluate it until you reach the outermost constructor. Normal form means evaluate the entire value until no unevaluated components are left. Because Haskell allows for all sorts of levels of evaluation depth, it has a rich terminology to describe this. You don't tend to find these distinctions in languages that only support call-by-value semantics.
@David: I wrote a more in-depth explanation here: Haskell: What is Weak Head Normal Form?. Though I don't mention bang patterns or strictness annotations, they are equivalent to using seq.
Just to be sure I understood: does that laziness relates only to an unknown at compile time arguments? I.e. if the source code really have a statement (2+2), would it still be optimized to 4 instead of having a code to add the known numbers?
Hi-Angel, that's really up to how far the compiler wants to optimize. Though we use bangs to say we'd like to force certain things to weak normal head form, we don't have (and nor do we need or want) a way to say "please ensure you don't yet evaluate this thunk one step further." From a semantic point of view, given a thunk "n = 2 + 2", you can't even tell if any particular reference to n is being evaluated now or was previously evaluated.
C
Chris Conway

A simple way to see the difference between strict and non-strict constructor arguments is how they behave when they are undefined. Given

data Foo = Foo Int !Int

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

Since the non-strict argument isn't evaluated by second, passing in undefined doesn't cause a problem:

> second (Foo undefined 1)
1

But the strict argument can't be undefined, even if we don't use the value:

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

This is better than than Curt Sampson's answer because it actually explains user-observable effects the ! symbol has, rather than delving into internal implementation details.
C
Chris Vest

I believe it is a strictness annotation.

Haskell is a pure and lazy functional language, but sometimes the overhead of lazyness can be too much or wasteful. So to deal with that, you can ask to compiler to fully evaluate the arguments to a function instead of parsing thunks around.

There's more information on this page: Performance/Strictness.


Hmm, the example on that page you referenced seems to talk about the use of ! when invoking a function. But that seems different than putting it in a type declaration. What am I missing?
Creating an instance of a type is also an expression. You can think of the type constructors as functions that return new instances of the specified type, given the supplied arguments.
In fact, for all intents and purposes, type constructors are functions; you can partially apply them, pass them to other functions (e.g., map Just [1,2,3] to get [Just 1, Just 2, Just 3]) and so on. I find it helpful to think of the ability to pattern match with them as well as a completely unrelated facility.