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

Reply via email to