haskellpolymorphism

Practical applications of Rank 2 polymorphism?


I'm covering polymorphism and I'm trying to see the practical uses of such a feature.

My basic understanding of Rank 2 is:

type MyType = ∀ a. a -> a

subFunction :: a -> a
subFunction el = el

mainFunction :: MyType -> Int
mainFunction func = func 3

I understand that this is allowing the user to use a polymorphic function (subFunction) inside mainFunction and strictly specify it's output (Int). This seems very similar to GADT's:

data Example a where
 ExampleInt :: Int -> Example Int
 ExampleBool :: Bool -> Example Bool

1) Given the above, is my understanding of Rank 2 polymorphism correct?

2) What are the general situations where Rank 2 polymorphism can be used, as opposed to GADT's, for example?


Solution

  • If you pass a polymorphic function as an argument to a Rank2-polymorphic function, you're essentially passing not just one function but a whole family of functions – for all possible types that fulfill the constraints.

    Typically, those forall quantifiers come with a class constraint. For example, I might wish to do number arithmetic with two different types simultaneously (for comparing precision or whatever):

    data FloatCompare = FloatCompare {
         singlePrecision :: Float
       , doublePrecision :: Double
       }
    

    Now I might want to modify those numbers through some maths operation. Something like:

    modifyFloat :: (Num -> Num) -> FloatCompare -> FloatCompare
    

    But Num is not a type, only a type class. I could of course pass a function that would modify any particular number type, but I could not use that to modify both a Float and a Double value, at least not without some ugly (and possibly lossy) converting back and forth.

    Solution: Rank-2 polymorphism!

    modifyFloat :: (∀ n . Num n => n -> n) -> FloatCompare -> FloatCompare
    mofidyFloat f (FloatCompare single double)
        = FloatCompare (f single) (f double)
    

    The best single example of how this is useful in practice are probably lenses. A lens is a “smart accessor function” to a field in some larger data structure. It allows you to access fields, update them, gather results... while at the same time composing in a very simple way. How it works: Rank2-polymorphism; every lens is polymorphic, with the different instantiations corresponding to the “getter” / “setter” aspects, respectively.