Abstract Nonsense

Abstract Nonsense

Ramblings by Benjamin Kovach

Theme adapted from minimal by orderedlist.

I love Profunctors. They're so easy.

Published by Ben Kovach on August 23, 2014

Tags: haskell, profunctors, category theory

Notes on I love Profunctors. They’re so easy..

Normal Haskell functors are covariant. Example of a contravariant functor:

newtype Predicate a = Predicate{ getPredicate :: a -> Bool } 

class Contravariant f where
    contramap :: (b -> a) -> f a -> f b

instance Contravariant Predicate where
    -- p :: a -> Bool
    -- g :: b -> a
    contramap g (Predicate p) = Predicate (p . g)

veryOdd :: Predicate Integer
veryOdd = contramap (`div` 2) (Predicate odd)

-- True iff n `div` 2 is odd.
main = print $ getPredicate veryOdd <$> [0..11]

Here are a couple more contravariant functors:

newtype Const a b = Const a
instance Contravariant (Const a) where
    -- _ :: c -> b
    -- a :: a
    contramap _ (Const a) = Const a

newtype Comparison a = Comparison (a -> a -> Ordering)
instance Contravariant Comparison where
    -- g :: b -> a
    -- c :: a -> a -> Ordering
    contramap g (Comparison c) = Comparison (comp `on` g) 

newtype Op a b = Op (b -> a)
instance Contravariant (Op a) where
    -- g :: b -> a
    -- f :: a -> c
    contramap g (Op f) = Op (f . g)

Next, bifunctors. EZPZ.

A profunctor is a bifunctor that is contravariant in the first argument and covariant in the second.

class Profunctor f where
    dimap  (c  a)  (b  d)  f a b  f c d

-- exercise
instance Profunctor (->) where
    -- dimap :: (c → a) → (b → d) → (a → b) → (c → d)
    -- f :: c -> a
    -- g :: b -> d
    -- h :: a -> b
    -- want : c -> d
    dimap f g h = g . h . f

Here’s another example; check checks user input, and step mutates state by clicking an up/down button or pressing the value directly.

data Limits a = 
  Limits 
    { step :: a -> (a, a)
    , check :: a -> a -> Bool
    }

By generalizing this, we get a profunctor:

type Limits a = Limits' a a
data Limits' a = 
  Limits
    { step :: a -> (b, b)
    , check :: a -> a -> Bool 
    }

instance Profunctor Limits' where
  dimap g h Limits{..} = Limits
    { step = (h *** h) . step . g
    -- ^ Regular old covariance (mostly)
    , check = check `on` g
    -- ^ Looks exactly like Comparison from before.
    }

Another example: Containers with Keys

M.map        ::      (a -> b) -> Map i a -> Map i b
M.mapWithKey :: (i -> a -> b) -> Map i a -> Map i b

Let’s use Indexed:

newtype Indexed i a b = Indexed{ runIndexed :: i -> a -> b }

-- exercise (?????)
instance Profunctor (Indexed i) where
  -- dimap :: (c -> a) -> (b -> d) -> Indexed i a b -> Indexed i c d
  -- g :: c -> a
  -- h :: b -> d
  -- f :: i -> a -> b
  dimap g h (Indexed f) = Indexed (dimap g h . f)

With Indexable, we can unify M.map and M.mapWithKey:

class Indexable i a p where indexed :: p a b -> i -> a b
instance Indexable i (Indexed i) where indexed = runIndexed
instance Indexible i (->)        where indexed = const

mapIndexable :: Indexable i p  => p a b -> Map i a -> Map i b
             ≈  Indexed i a p  -> Map i a -> Map i b
             ≈        (a -> b) -> Map i a -> Map i b
mapIndexable :: Indexable i p => p a b -> Map i a -> Map i b
mapIndexable idx m = M.mapWithKey . indexed

Author also mentions UpStar, DownStar, Strong and Choice to look into.

Discussion on reddit.

comments powered by Disqus