Rank-N Types and Existential Types

35.1 Rank-N Types

{-# LANGUAGE RankNTypes #-}

-- Rank-1: the CALLER chooses a. This signature can't apply f to both an [Int] and a String:
-- applyToBoth' :: ([a] -> [a]) -> ([Int], String) -> ([Int], String)   -- f would be fixed to one type

-- Rank-2: the CALLEE chooses: f must work for ALL types
applyToBoth :: (forall a. [a] -> [a]) -> ([Int], String) -> ([Int], String)
applyToBoth f (xs, s) = (f xs, f s)
-- applyToBoth reverse ([1, 2, 3], "abc") == ([3,2,1],"cba")

-- runST uses rank-2 to keep mutable state from escaping:
-- runST :: (forall s. ST s a) -> a

-- Natural transformations
type f ~> g = forall x. f x -> g x
maybeToList' :: Maybe ~> []
maybeToList' Nothing = []
maybeToList' (Just x) = [x]

35.2 Existential Types

Existentials enable heterogeneous collections. But often a simpler design is a record of functions (data Shape = Shape {area :: Double, name :: String}) or a sum type.

{-# LANGUAGE ExistentialQuantification #-}

class Shape a where
  area :: a -> Double
  name :: a -> String

data Circle = Circle Double
data Square = Square Double
instance Shape Circle where area (Circle r) = pi * r * r; name _ = "circle"
instance Shape Square where area (Square s) = s * s; name _ = "square"

-- Hide the concrete type, keep the capability
data AnyShape = forall s. Shape s => AnyShape s

shapes :: [AnyShape]
shapes = [AnyShape (Circle 1), AnyShape (Square 2)]

describeAll :: [AnyShape] -> [String]
describeAll xs = [name s ++ ": " ++ show (area s) | AnyShape s <- xs]