Tying knots with strict constructors

Previous Topic Next Topic
classic Classic list List threaded Threaded
1 message Options
Reply | Threaded
Open this post in threaded view

Tying knots with strict constructors

David Feuer-2
Once in a while, one desires to tie a recursive knot and is stymied by a
strict data constructor. I recently encountered this problem trying to
improve the implementation of `never` in the `streaming` package.
The Stream type is defined thus:

data Stream f m r = Step !(f (Stream f m r))
                  | Effect (m (Stream f m r))
                  | Return r

It would be nice to be able to write

never :: Applicative f => Stream f m r
never = fix (Step . pure)

Unfortunately, if `pure` is strict (e.g., Identity), this won't work. The
Step wrapper attempts to force `pure never` and then apply the Step worker.
This will never work. The streaming package works around the problem
by representing the `never` stream in a different way, at the potential
cost of some efficiency. In other contexts, there may be no (safe) workaround
at all.

This is terribly frustrating, because it seems almost possible to express what
I want in Core, and even possible to express it in Haskell with a really awful
unsafeCoerce. The nasty version looks like this:

data StreamL f m r = StepL (f (StreamL f m r))
                   | EffectL (m (StreamL f m r))
                   | ReturnL r

never :: forall f m r. Applicative f => Stream f m r
never = case loop of
          StepL x -> x `pseq` unsafeCoerce loop
     loop :: StreamL f m r
     loop = StepL (pure loop) in loop
     {-# NOINLINE loop #-}
{-# NOINLINE never #-}

That is, I make a copy of the Stream type, but with a lazy version of the Step constructor,
I tie my knot, I make very sure that the strict field is evaluated, and then I unsafeCoerce
the whole thing in a thoroughly unsupported fashion to get back to the right type. Ideally,
It would be nice to get GHC to manufacture, and expose to users, bidirectional patterns
that offer more access to the raw representation of a type.

Basically, I want to get a bidirectional pattern for Step that:

1. Is lazy when used as a constructor (applying the "worker" constructor directly)
2. Is viewed as lazy on matching, so the strictness analysis comes out right.

Using such a feature will presumably always be dangerous (unless someone
does a ton of work to find an efficient way to make it safe), but I'd rather have
a reasonable dangerous way to do it than an unreasonable dangerous way,
if that can be accomplished.

Unfortunately, I haven't been able to think of a reasonable design for such a
language feature. Does anyone else have any ideas? Or some other thought about
how such things might be done?

ghc-devs mailing list
[hidden email]