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?
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.
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!)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With