Simon writes
| Warren makes an excellent point, though he doesn't highlight it:
|
| unlifted tuples are INCOMPATIBLE with seq
|
| By "incompatible" I mean that you need parallel specualative evaluation
| to implement it. So a truly polymorphic seq is out. That takes us back
| to an overloaded version, except that seq couldn't have an instance for
| (unlifted) tuples!
I mentioned the other day that in fact it could. The question is whether
this would be desirable. Consider the following:
class Strict a where
seq :: a -> b -> b
strict :: (a -> b) -> a -> b
seq x y = y
strict f x = seq x (f x)
instance Strict (a,b)
instance Strict [a] where
seq [] y = y
seq (_:_) y = y
The question is what we want seq to mean. One possibility is
seq x y = _|_ if x = _|_
y otherwise
Another is more operational:
seq x y = y if x has a whnf
_|_ otherwise
Since seq definitely has an operational flavor to it anyway, maybe this
isn't so bad. The above declarations conform to this second approach.
It's like saying that a pair is necessarily in whnf, as it has a (the only
possible) constructor wrapped around it. Is this a good idea? On the one
hand a programmer might be surprised that writing seq (x,y) z doesn't
accomplish much of anything. (As far as that goes, he might also be
surprised that seq (map f xs) y only resolves the first argument to
nil or cons before proceeding with y.) On the other hand, if we have
g = strict f
where f is polymorphic in its argument type, an operational intent is
that applications of g need not build a suspension for their arguments,
which is the case for an (unlifted) pair, since it's already suspended.
Thus, in a sense, seq and strict can have something resembling polymorphic
implementations after all.
| The reason for this is that we can't implement unlifted tuples directly at
| all. Instead, we simulate them by making pattern matching lazy, so that
| _|_ behaves identically to (_|_,_|_), even though the two may have different
| runtime representations. The seq function exposes the trick! That's why
| Miranda fails in Warren's example.
As I said before, I think the issue is one of abstraction. Our
implementations tend to have lifted tuples (and functions) but that shouldn't
limit what the abstract values are. Naively adding a new operation to
an ADT may reveal that the abstraction function was not an isomorphism.
--Joe