Since newtypes are effectively removed during compilation, they don’t have thunks, just values. So what happens if I ask for its WHNF using rseq? For example in
Sum (lengthyComputation :: Int) `using` rseq
where Sum is defined as
newtype Sum a = Sum { getSum :: a }
will lengthyComputation get evaluated or not? Is it specified/documented somewhere so that I can count on it?
Update: Let me explain my doubts in more detail. Intuitively one says: “newtype is strict so clearly its WHNF is the WHNF of what’s wrapped inside”. But I feel this is a very imprecise shortcut and the reasoning is not so clear. Let me give an example:
For standard data types, WHNF can defined as a form where we know which constructor was used for constructing the value. If, for example, we didn’t have seq, we could create our own
seqMaybe :: Maybe a -> b -> b
seqMaybe Nothing = id
seqMaybe _ = id
and similarly for any data type, just by pattern matching on one of its constructors.
Now let’s take
newtype Identity a = Identity { runIdentity :: a }
and create a similar seqIdentity function:
seqIdentity :: Identity a -> b -> b
seqIdentity (Identity _) = id
clearly, nothing is forced to WHNF here. (After all, we always know what constructor was used.) After compilation, seqIdentity will be identical to const id. In fact, it isn’t possible to create polymorphic seqIdentity such that it would force evaluation of a value wrapped inside Identity! We could define WHNF a of a newtype to be simply the value unmodified, and it would be consistent. So I believe the question is, how is WHNF defined for newtypes? Or is there no rigorous definition, and the behavior “it’s the WHNF of what’s inside” is simply assumed as something obvious?
Per the section on Datatype renamings in the report,
Here
so the weak head normal form of a newtype is the WHNF of the wrapped type, and
evaluates
lengthyComputation(when the entire expression is evaluated, a mere bindingof course doesn’t, but that is the same without the newtype constructor).
The defining equations for
seqareand hence
and in
the
seqis required to find out (sorry for the anthropomorphism) whetherlengthyComputation :: Intis ⊥ or not. To do that, it must evaluatelengthyComputation :: Int.Re update:
newtypes are unlifted, that means that the constructor is not a value constructor semantically (only syntactically). Pattern-matching on anewtypeconstructor is, in contrast to pattern matching on adataconstructor not strict. Givena “pattern match”
is completely equivalent to
The match always succeeds, and evaluates nothing. The constructor only coerces the type and “pattern matching” on it un-coerces the type of the value.
seqis magic, it cannot be implemented in Haskell. You can write a function that does the same asseqfor adatatype, like yourseqMaybeabove, in Haskell, but not for a (polymorphic)newtype, because “pattern matching” on the newtype constructor is not strict. You would have to match on the constructor(s) of the wrapped type, but for a polymorphicnewtype, you don’t have them.