Thu 26 Jun 2008
Memoizing Context
Posted by Edward Kmett under Haskell , Category Theory , Comonads , Mathematics[2] Comments
[Edit: re-introduced the domain function]
Recently Andy Gill posted a nice use of data families to memoize a narrow range of values to optimize Conal Elliott's functional linear maps.
I've tweaked Andy's definition slightly below to use a type family for D e instead of a data family, which avoids some of the syntactic noise of the domain function when D e = e, and lets it this be used transparently in some places. This change lacks substance however, and the source code links at the bottom include the code with and without this change.
Since I'm using data and type families, you'll need GHC 6.9.
class Applicative ((:~*) e) => NarrowMemo e where data (:~*) e :: * -> * type D e :: * apply :: (e :~* r) -> D e -> r memo :: (D e -> r) -> (e :~* r) domain :: D e -> e instance NarrowMemo Bool where data Bool :~* a = MemoBool a a type D Bool = Bool apply (MemoBool o1 o2) True = o1 apply (MemoBool o1 o2) False = o2 memo f = MemoBool (f True) (f False) domain = id
We can quickly add in the missing bits for the Bool demo he supplied:
instance Functor ((:~*) Bool) where fmap f (MemoBool a b) = MemoBool (f a) (f b) instance Applicative ((:~*) Bool) where pure x = MemoBool x x MemoBool f g < *> MemoBool a b = MemoBool (f a) (g b)
And we can automatically generate an instance of Monad for (:~*)e if we really want to:
instance NarrowMemo e => Monad ((:~*) e) where return = pure m >>= k = memo $ apply (fmap k m) >>= apply -- in (->)e
And we can drop in a couple more instances to make it interesting. The tensor product:
instance (NarrowMemo a, NarrowMemo b) => NarrowMemo (a,b) where data (a,b) :~* e = MemoBoth (a :~* (b :~* e)) type D (a,b) = (D a, D b) apply (MemoBoth f) = uncurry (apply . apply f) memo f = MemoBoth $ memo $ \a -> memo (f . (,) a) domain = domain *** domain instance (NarrowMemo a, NarrowMemo b) => Functor ((:~*) (a,b)) where fmap f (MemoBoth g) = MemoBoth (fmap (fmap f) g) instance (NarrowMemo a, NarrowMemo b) => Applicative ((:~*) (a,b)) where pure = MemoBoth . pure . pure f < *> a = MemoBoth $ memo $ \da -> memo $ \db -> let e = (da, db) in apply f e (apply a e)
and a disjoint sum, if only so we have some more memo tables to play with.
instance (NarrowMemo a, NarrowMemo b) => NarrowMemo (Either a b) where data Either a b :~* e = MemoEither (a :~* e) (b :~* e) type D (Either a b) = Either (D a) (D b) apply (MemoEither l _) (Left a) = apply l a apply (MemoEither _ r) (Right b) = apply r b memo f = MemoEither (memo (f . Left)) (memo (f . Right)) domain = domain +++ domain instance (NarrowMemo a, NarrowMemo b) => Functor ((:~*) (Either a b)) where fmap f (MemoEither a b) = MemoEither (fmap f a) (fmap f b) instance (NarrowMemo a, NarrowMemo b) => Applicative ((:~*) (Either a b)) where pure f = MemoEither (pure f) (pure f) MemoEither f g < *> MemoEither a b = MemoEither (f < *> a) (g < *> b)
Now, the reason I wanted to play with this was I hacked up a memoizing state-in-context comonad a couple of weeks back using unsafePerformIO and a memo table, but using Andy's trick we can build up a pure memoizing context comonad for smaller or more regular domains.
Recall the state-in-context comonad from category-extras.
class Comonad w => ComonadContext s w | w -> s where getC :: w a -> s modifyC :: (s -> s) -> w a -> a data Context s a = Context (s -> a) s instance ComonadContext s (Context s) where getC (Context _ s) = s modifyC m (Context f c) = f (m c) instance Functor (Context s) where fmap f (Context f' s) = Context (f . f') s instance Copointed (Context s) where extract (Context f a) = f a instance Comonad (Context s) where duplicate (Context f a) = Context (Context f) a
We can modify the definition to replace (->) with the version above of Andy's (:~*) to obtain a narrowly memoized version:
data NarrowContext e a = NarrowContext (e :~* a) (D e) instance NarrowMemo e => Functor (NarrowContext e) where fmap f (NarrowContext t d) = NarrowContext (memo (f . apply t)) d instance NarrowMemo e => Copointed (NarrowContext e) where extract (NarrowContext t d) = apply t d instance NarrowMemo e => Comonad (NarrowContext e) where duplicate (NarrowContext f a) = NarrowContext (memo (NarrowContext f)) a instance NarrowMemo e => ComonadContext (D e) (NarrowContext e a) where getC (NarrowContext _ e) = e modifyC f (NarrowContext g e) = apply g (f e)
Now any computation done in this modified state-in-context comonad is memoized and compositions of those computations are also memoized and built up from the memoized intermediate steps.
We should be able to play similar games with a lot of other (co)monads that use exponentials as well.
For instance (:~*) itself can play the role of a narrowly-memoized anonymous exponential comonad.
instance (NarrowMemo e, Monoid (D e)) => Copointed ((:~*) e) where extract t = apply t mempty instance (NarrowMemo e, Monoid (D e)) => Comonad ((:~*) e) where duplicate f = memo $ m -> memo (apply f . mappend m)
[ Source Code (using data families only) ]
[ Source Code (using data and type families) ]

and
consists of a pair of functors
, and
and a natural isomorphism:
the left adjoint functor, and
the right adjoint functor and
an adjoint pair, and write this relationship as
=
=
if and only if the right Kan extension
exists and is preserved by
. It has a