|
newtype Point a = Point {x :: a, y :: a, z :: a} |
|
|
|
-- Let's say you start out by wanting to write this getxPair function. |
|
|
|
getxPair :: forall a b. Point a -> Point b -> Tuple a b |
|
getxPair p1 p2 = Tuple ((\(Point {x}) -> x) p1) ((\(Point {x}) -> x) p2) |
|
|
|
-- And then you write a getyPair function. But the repeated function \(Point {x}) -> x) |
|
-- doesn't look so nice, so this time you name it in a where clause. |
|
|
|
getyPair :: forall a. Point a -> Point a -> Tuple a a |
|
getyPair p1 p2 = Tuple (gety p1) (gety p2) |
|
where |
|
gety (Point {y}) = y |
|
|
|
-- Interestingly, the inferred types are different. The previous one is more general! |
|
|
|
-- Maybe you decide that the get functions are useful enough to be at the top level. |
|
|
|
getz :: forall a. Point a -> a |
|
getz (Point {z}) = z |
|
|
|
getzPair :: forall a b. Point a -> Point b -> Tuple a b |
|
getzPair p1 p2 = Tuple (getz p1) (getz p2) |
|
|
|
-- Now you got the general type back again! What's going on? |
|
|
|
-- Now that you have the top-level getters, you don't want to repeat all the getPair |
|
-- functions, so you take the getter as an argument. |
|
|
|
getPair :: forall a b. (a -> b) -> a -> a -> Tuple b b |
|
getPair get p1 p2 = Tuple (get p1) (get p2) |
|
|
|
-- This function does not depent on Point at all, but the two arguments must be the |
|
-- same. What if you have points of different of types? You'd want the return value |
|
-- to be Tuple a b, not Tuple b b. |
|
|
|
-- It seem like we need to either write specialized functions for each property, |
|
-- or require that the type parameter in Point is the same. But there is a solution. |
|
|
|
-- The problem in getyPair is that the type for gety is `Point a -> a`, like |
|
|
|
getyPair' :: forall a. Point a -> Point a -> Tuple a a |
|
getyPair' p1 p2 = Tuple (gety p1) (gety p2) |
|
where |
|
gety :: Point a -> a |
|
gety (Point {y}) = y |
|
|
|
-- The a in gety, is the same as in getyPair'. But we know that gety is more general, |
|
-- and if we just take the type from the top-level definition of getz, we would write |
|
|
|
getyPair'' :: forall a b. Point a -> Point b -> Tuple a b |
|
getyPair'' p1 p2 = Tuple (gety p1) (gety p2) |
|
where |
|
gety :: forall c. Point c -> c -- Note the "forall". |
|
gety (Point {y}) = y |
|
|
|
-- where I renamed the type variable in gety to c to avoid confusion. The inferred type |
|
-- is now as general as getxPair, returning Tuple a b. If we now change gety to be an |
|
-- argument, and explicitly paste in the type, we get |
|
|
|
getyPair''' :: forall a b. (forall c. Point c -> c) -> Point a -> Point b -> Tuple a b |
|
getyPair''' gety p1 p2 = Tuple (gety p1) (gety p2) |
|
|
|
-- This is exactly the same definition as getPair, but the return type is more general. |
|
-- In fact, we no longer need depend on Point, so that can be quantified over as well. |
|
-- See Rank2.purs. |
|
|
|
-- Also, see Applicative.purs for a completely different approach. |
|
|
|
-- Maybe it's worth commenting on why the type of getPair cannot be |
|
-- forall a b c. (Point c -> c) -> Point a -> Point b -> Tuple a b |
|
-- i.e. with the forall moved out of the parenthesis. That would mean that |
|
-- there is ONE set of types, (a, b, c), that matches the implementaiton. |
|
-- But how do you choose c so that you can do both (Point a -> a) AND (Point b -> b)? |
|
-- You can't. That's why you need to say that the function can take all types, including |
|
-- a and b, and this you do through the nested forall. |