Question

I've seen a few use cases for rank-2 polymorphism (the most prominent example being the ST monad), but none for a higher rank than that. Does anyone know of such a use case?

Was it helpful?

Solution

I may be able to help, although such beast are inevitably a bit involved. Here's a pattern I sometimes use in developing well-scoped syntax with binding and de Bruijn indexing, bottled.

mkRenSub ::
  forall v t x y.                      -- variables represented by v, terms by t
    (forall x. v x -> t x) ->            -- how to embed variables into terms
    (forall x. v x -> v (Maybe x)) ->    -- how to shift variables
    (forall i x y.                       -- for thingies, i, how to traverse terms...
      (forall z. v z -> i z) ->              -- how to make a thingy from a variable
      (forall z. i z -> t z) ->              -- how to make a term from a thingy
      (forall z. i z -> i (Maybe z)) ->      -- how to weaken a thingy
      (v x -> i y) ->                    -- ...turning variables into thingies
      t x -> t y) ->                     -- wherever they appear
    ((v x -> v y) -> t x -> t y, (v x -> t y) -> t x -> t y)
                                                 -- acquire renaming and substitution
mkRenSub var weak mangle = (ren, sub) where
  ren = mangle id var weak         -- take thingies to be vars to get renaming
  sub = mangle var id (ren weak)   -- take thingies to be terms to get substitution

Normally, I'd use type classes to hide the worst of the gore, but if you unpack the dictionaries, this is what you'll find.

The point is that mangle is a rank-2 operation which takes a notion of thingy equipped with suitable operations polymorphic in the variable sets over which they work: operations which map variables to thingies get turned into term-transformers. The whole thing shows how to use mangle to generate both renaming and substitution.

Here's a concrete instance of that pattern:

data Id x = Id x

data Tm x
  = Var (Id x)
  | App (Tm x) (Tm x)
  | Lam (Tm (Maybe x))

tmMangle :: forall i x y.
             (forall z. Id z -> i z) ->
             (forall z. i z -> Tm z) ->
             (forall z. i z -> i (Maybe z)) ->
             (Id x -> i y) -> Tm x -> Tm y
tmMangle v t w f (Var i) = t (f i)
tmMangle v t w f (App m n) = App (tmMangle v t w f m) (tmMangle v t w f n)
tmMangle v t w f (Lam m) = Lam (tmMangle v t w g m) where
  g (Id Nothing) = v (Id Nothing)
  g (Id (Just x)) = w (f (Id x))

subst :: (Id x -> Tm y) -> Tm x -> Tm y
subst = snd (mkRenSub Var (\ (Id x) -> Id (Just x)) tmMangle)

We implement the term traversal just once, but in a very general way, then we get substitution by deploying the mkRenSub pattern (which uses the general traversal in two different ways).

For another example, consider polymorphic operations between type operators

type (f :-> g) = forall x. f x -> g x

An IMonad (indexed monad) is some m :: (* -> *) -> * -> * equipped with polymorphic operators

ireturn :: forall p. p :-> m p
iextend :: forall p q. (p :-> m q) -> m p :-> m q

so those operations are rank 2.

Now any operation which is parametrized by an arbitrary indexed monad is rank 3. So, for example, constructing the usual monadic composition,

compose :: forall m p q r. IMonad m => (q :-> m r) -> (p :-> m q) -> p :-> m r
compose qr pq = iextend qr . pq

relies on rank 3 quantification, once you unpack the definition of IMonad.

Moral of story: once you're doing higher order programming over polymorphic/indexed notions, your dictionaries of useful kit become rank 2, and your generic programs become rank 3. This is, of course, an escalation that can happen again.

OTHER TIPS

Perhaps the best ending to an abstract I've read yet: "Multiplate only requires rank 3 polymorphism in addition to the normal type class mechanism of Haskell.". (Oh, only rank-3 polymorphism, no big deal!)

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top