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]