{-# LANGUAGE TypeOperators, TypeFamilies, UndecidableInstances, CPP
, DeriveFunctor, StandaloneDeriving
, FlexibleContexts
#-}
{-# OPTIONS_GHC -Wall -fno-warn-orphans #-}
{-# OPTIONS_GHC -fno-warn-unused-binds -fno-warn-unused-imports #-} -- TEMP
----------------------------------------------------------------------
-- |
-- Module : FunctorCombo.NonstrictMemo
-- Copyright : (c) Conal Elliott 2010
-- License : BSD3
--
-- Maintainer : conal@conal.net
-- Stability : experimental
--
-- Functor-based memo tries. See
--
----------------------------------------------------------------------
module FunctorCombo.NonstrictMemo
(
HasTrie(..),(:->:),memo,memo2,memo3
) where
#define NonstrictMemo
import Control.Arrow (first)
import Control.Applicative ((<$>))
import qualified Data.IntTrie as IT -- data-inttrie
import Data.Tree
import Control.Compose (result) -- TypeCompose
#ifdef NonstrictMemo
import Data.Lub
#endif
import FunctorCombo.Strict
import FunctorCombo.Functor
import FunctorCombo.Regular
{--------------------------------------------------------------------
Misc
--------------------------------------------------------------------}
type Unop a = a -> a
bool :: a -> a -> Bool -> a
bool t e b = if b then t else e
{--------------------------------------------------------------------
Class
--------------------------------------------------------------------}
infixr 0 :->:
#define FunctorSuperClass
#ifdef FunctorSuperClass
#define HasTrieContext(Ty) Functor (STrie (Ty))
#define HF(Ty) HasTrie (Ty)
#else
#define HasTrieContext(Ty) ()
#define HF(Ty) HasTrie (Ty), Functor (STrie (Ty))
#endif
#ifdef NonstrictMemo
data Trie k v = Trie v (STrie k v)
deriving instance Functor (STrie k) => Functor (Trie k)
-- instance Functor (STrie k) => Functor (Trie k) where
-- fmap f (Trie b t) = Trie (f b) (fmap f t)
type k :->: v = Trie k v
-- Bottom
bottom :: a
bottom = error "MemoTrie: evaluated bottom. Oops!"
-- | Create the trie for the entire domain of a function
trie :: (HasTrie k{-, HasLub v-}) => (k -> v) -> (k :->: v)
trie f = Trie (f bottom) (sTrie f)
-- | Convert k trie to k function, i.e., access k field of the trie
untrie :: (HasTrie k, HasLub v) => (k :->: v) -> (k -> v)
untrie (Trie b t) = const b `lub` sUntrie t
#else
type Trie k = STrie k
type k :->: v = k :-> v
-- Bogus HasLub constraint
#define HasLub(v) ()
trie :: HasTrie k => (k -> v) -> (k :->: v)
trie = sTrie
untrie :: HasTrie k => (k :->: v) -> (k -> v)
untrie = sUntrie
#endif
-- | Strict memo trie from k to v
type k :-> v = STrie k v
-- | Domain types with associated memo tries
class HasTrieContext(k) => HasTrie k where
-- | Representation of trie with domain type @a@
type STrie k :: * -> *
-- | Create the trie for the entire domain of a function
sTrie :: {- HasLub(v) => -} (k -> v) -> (k :-> v)
-- | Convert k trie to k function, i.e., access k field of the trie
sUntrie :: HasLub(v) => (k :-> v) -> (k -> v)
-- -- | List the trie elements. Order of keys (@:: k@) is always the same.
-- enumerate :: HasLub(v) => (k :-> v) -> [(k,v)]
-- -- | Domain elements of a trie
-- domain :: HasTrie a => [a]
-- domain = map fst (enumerate (trie (const oops)))
-- where
-- oops = error "Data.MemoTrie.domain: range element evaluated."
-- TODO: what about enumerate and strict/nonstrict?
{--------------------------------------------------------------------
Memo functions
--------------------------------------------------------------------}
-- | Trie-based function memoizer
memo :: HasLub(v) => HasTrie k => Unop (k -> v)
memo = untrie . trie
-- | Memoize a binary function, on its first argument and then on its
-- second. Take care to exploit any partial evaluation.
memo2 :: HasLub(a) => (HasTrie s,HasTrie t) => Unop (s -> t -> a)
-- | Memoize a ternary function on successive arguments. Take care to
-- exploit any partial evaluation.
memo3 :: HasLub(a) => (HasTrie r,HasTrie s,HasTrie t) => Unop (r -> s -> t -> a)
-- | Lift a memoizer to work with one more argument.
mup :: HasLub(c) => HasTrie t => (b -> c) -> (t -> b) -> (t -> c)
mup mem f = memo (mem . f)
memo2 = mup memo
memo3 = mup memo2
{--------------------------------------------------------------------
Instances
--------------------------------------------------------------------}
instance HasTrie () where
type STrie () = Id
sTrie f = Id (f ())
sUntrie (Id v) = \ () -> v
-- enumerate (Id a) = [((),a)]
instance (HasTrie a, HasTrie b) => HasTrie (Either a b) where
type STrie (Either a b) = Trie a :*: Trie b
sTrie f = trie (f . Left) :*: trie (f . Right)
sUntrie (ta :*: tb) = untrie ta `either` untrie tb
-- enumerate (ta :*: tb) = enum' Left ta `weave` enum' Right tb
-- enum' :: HasLub(b) => HasTrie a => (a -> a') -> (a :->: b) -> [(a', b)]
-- enum' f = (fmap.first) f . enumerate
-- weave :: [a] -> [a] -> [a]
-- [] `weave` as = as
-- as `weave` [] = as
-- (a:as) `weave` bs = a : (bs `weave` as)
-- To do: rethink enumerate and come back to it. How might enumeration
-- work in the presence of nonstrict memo tries? Maybe lub the
-- approximation into each of the values enumerated from the strict memo tries.
-- Would it help any??
instance (HF(a), HasTrie b) => HasTrie (a , b) where
type STrie (a , b) = Trie a :. Trie b
-- sTrie f = O (trie (trie . curry f))
-- sUntrie (O tt) = uncurry (untrie . untrie tt)
sTrie f = O (fmap trie (trie (curry f)))
sUntrie (O tt) = uncurry (untrie (fmap untrie tt))
-- enumerate (O tt) =
-- [ ((a,b),x) | (a,t) <- enumerate tt , (b,x) <- enumerate t ]
-- With the first definitions of sTrie and sUntrie:
--
-- Could not deduce (HasLub (Trie b v)) from the context (HasLub v)
-- arising from a use of `trie'
--
-- Could not deduce (HasLub (Trie b v)) from the context (HasLub v)
-- arising from a use of `untrie'
--
-- Solution: switch from inner-then-outer to outer-then-inner.
-- Experiment: strict sums & pairs.
-- TODO: re-work non-strict versions in terms of strict ones and Lift
instance (HasTrie a, HasTrie b) => HasTrie (a :+! b) where
type STrie (a :+! b) = STrie a :*: STrie b
sTrie f = sTrie (f . Left') :*: sTrie (f . Right')
sUntrie (ta :*: tb) = sUntrie ta `either'` sUntrie tb
-- enumerate (ta :*: tb) = enum' Left' ta `weave` enum' Right' tb
instance (HF(a), HasTrie b) => HasTrie (a :*! b) where
type STrie (a :*! b) = STrie a :. STrie b
-- sTrie f = O (trie (trie . curry f))
-- sUntrie (O tt) = uncurry (untrie . untrie tt)
sTrie f = O (fmap sTrie (sTrie (curry' f)))
sUntrie (O tt) = uncurry' (sUntrie (fmap sUntrie tt))
-- Lift a has an additional bottom. A strict function or trie is
-- only strict in the lower (outer) one.
instance (HF(a)) => HasTrie (Lift a) where
type STrie (Lift a) = Trie a
sTrie f = trie (f . Lift)
sUntrie t = untrie t . unLift
#define HasTrieIsomorph(Context,Type,IsoType,toIso,fromIso) \
instance Context => HasTrie (Type) where { \
type STrie (Type) = STrie (IsoType); \
sTrie f = sTrie (f . (fromIso)); \
sUntrie t = sUntrie t . (toIso); \
}
-- enumerate = (result.fmap.first) (fromIso) enumerate; \
HasTrieIsomorph( (), Bool, Either () ()
, bool (Left ()) (Right ())
, either (\ () -> True) (\ () -> False))
HasTrieIsomorph( (HF(a),HF(b), HasTrie c)
, (a,b,c), ((a,b),c)
, \ (a,b,c) -> ((a,b),c), \ ((a,b),c) -> (a,b,c))
HasTrieIsomorph( (HF(a),HF(b),HF(c), HasTrie d)
, (a,b,c,d), ((a,b,c),d)
, \ (a,b,c,d) -> ((a,b,c),d), \ ((a,b,c),d) -> (a,b,c,d))
-- OOPS! Fix the isomorphs above
-- As well as the functor combinators themselves
HasTrieIsomorph( HasTrie x, Const x a, x, getConst, Const )
HasTrieIsomorph( HasTrie a, Id a, a, unId, Id )
HasTrieIsomorph( ( HF(f a), HasTrie (g a) )
, (f :*: g) a, (f a,g a)
, \ (fa :*: ga) -> (fa,ga), \ (fa,ga) -> (fa :*: ga) )
HasTrieIsomorph( (HasTrie (f a), HasTrie (g a))
, (f :+: g) a, Either (f a) (g a)
, eitherF Left Right, either InL InR )
HasTrieIsomorph( HasTrie (g (f a))
, (g :. f) a, g (f a) , unO, O )
-- Strict variants
HasTrieIsomorph( ( HF(f a), HasTrie (g a) )
, (f :*:! g) a, (f a :*! g a)
, \ (fa :*:! ga) -> (fa :*! ga), \ (fa :*! ga) -> (fa :*:! ga) )
HasTrieIsomorph( (HasTrie (f a), HasTrie (g a))
, (f :+:! g) a, (f a :+! g a)
, fToSum, sumToF)
-- Factored out to avoid clash between primes and GHC's use of classic CPP
sumToF :: (f a :+! g a) -> (f :+:! g) a
sumToF = either' InL' InR'
fToSum :: (f :+:! g) a -> (f a :+! g a)
fToSum = eitherF' Left' Right'
newtype ListSTrie a v = ListSTrie (PF [a] [a] :-> v)
deriving instance Functor (STrie a) => Functor (ListSTrie a)
instance (HF(a)) => HasTrie [a] where
type STrie [a] = ListSTrie a
sTrie f = ListSTrie (sTrie (f . wrap))
sUntrie (ListSTrie t) = sUntrie t . unwrap
-- enumerate (ListSTrie t) = (result.fmap.first) wrap enumerate $ t
-- Compiles fine, but has many points of undefinedness than a list does. See
--
--
-- Experiment with some alternatives.
-- Now a trie for ListSTrie a v. Use the isomorphism with PF [a] [a] :-> v
-- HasTrieIsomorph( HasTrie (PF ([a]) ([a]) :->: v)
-- , ListSTrie a v, PF ([a]) ([a]) :->: v
-- , \ (ListSTrie w) -> w, ListSTrie )
-- instance HasTrie (PF ([a]) ([a]) :->: v) => HasTrie (ListSTrie a v) where
-- type STrie (ListSTrie a v) = STrie (PF ([a]) ([a]) :->: v)
-- sTrie f = sTrie (f . ListSTrie)
-- -- sUntrie t = sUntrie t . (\ (ListSTrie w) -> w)
-- Could not deduce (HasTrie (Trie ((:*:) (Const a) Id [a]) v),
-- Functor (STrie (Trie (Const () [a]) v)),
-- HasTrie (Trie (Const () [a]) v))
-- from the context (HasLub v1)
-- arising from a use of `sTrie'
-- Couldn't match expected type `STrie
-- (Trie ((:+:) Unit (Const a :*: Id) [a]) v)'
-- against inferred type `Trie (Trie (Const () [a]) v)
-- :. Trie (Trie ((:*:) (Const a) Id [a]) v)'
-- NB: `STrie' is a type function, and may not be injective
-- In the expression: sTrie (f . ListSTrie)
{-
f :: ListSTrie a v -> u
ListSTrie :: (PF [a] [a] :-> v) -> ListSTrie a v
f . ListSTrie :: (PF [a] [a] :-> v) -> u
sTrie (f . ListSTrie) :: HasTrie (PF [a] [a] :-> v) => (PF [a] [a] :-> v) :-> u
:: HasTrie (PF [a] [a] :-> v) => ListSTrie a v :-> u
ListSTrie a v :-> u
(PF [a] [a] :-> v) :-> u
((Unit :+: Const a :*: Id) [a] :-> v) :-> u
(Either (Unit [a]) ((Const a :*: Id) [a]) :-> v) :-> u
STrie (Either (Unit [a]) ((Const a :*: Id) [a])) v :-> u
(Trie (Unit [a]) :*: Trie ((Const a :*: Id) [a])) v :-> u
(Trie (Unit [a]) v , Trie ((Const a :*: Id) [a]) v) :-> u
(Unit [a] :->: v , (Const a :*: Id) [a] :->: v) :-> u
STrie (Unit [a] :->: v , (Const a :*: Id) [a] :->: v) u
(Trie (Unit [a] :->: v) :. Trie ((Const a :*: Id) [a] :->: v)) u
(Trie (Unit [a] :->: v) :. Trie ((Const a [a], Id [a]) :->: v)) u
(Trie (Unit [a] :->: v) :. Trie ((Const a [a], Id [a]) :->: v)) u
(v , (Const a [a], Id [a]) :-> v)
(v , Const a [a] :->: Id [a] :->: v)
(v , (Id [a] :->: v, Const a [a] :-> Id [a] :->: v))
(v , (Id [a] :->: v, Const a [a] :-> (v, Id [a] :-> v)))
(v , ((v, Id [a] :-> v), Const a [a] :-> (v, Id [a] :-> v)))
(v , ((v, [a] :->: v), a :->: (v, [a] :->: v)))
ListSTrie a v
PF [a] [a] :-> v
(Unit :+: Const a :*: Id) [a] :-> v
Either (Unit [a]) ((Const a :*: Id) [a]) :-> v
STrie (Either (Unit [a]) ((Const a :*: Id) [a])) v
(Trie (Unit [a]) :*: Trie ((Const a :*: Id) [a])) v
-}
-- Now abstract into a macro
#define HasTrieRegular(Context,Type,TrieType,TrieCon) \
newtype TrieType v = TrieCon (PF (Type) (Type) :->: v); \
instance Context => HasTrie (Type) where { \
type STrie (Type) = TrieType; \
sTrie f = TrieCon (sTrie (f . wrap)); \
sUntrie (TrieCon t) = sUntrie t . unwrap; \
}; \
HasTrieIsomorph( HasTrie (PF (Type) (Type) :->: v) \
, TrieType v, PF (Type) (Type) :->: v \
, \ (TrieCon w) -> w, TrieCon )
-- enumerate (TrieCon t) = (result.fmap.first) wrap enumerate t; \
-- For instance,
-- HasTrieRegular(HasTrie a, [a] , ListSTrie a, ListSTrie)
-- HasTrieRegular(HasTrie a, Tree a, TreeTrie a, TreeTrie)
-- Simplify a bit with a macro for unary regular types.
-- Make similar defs for binary etc as needed.
#define HasTrieRegular1(TypeCon,TrieCon) \
HasTrieRegular(HasTrie a, TypeCon a, TrieCon a, TrieCon)
-- HasTrieRegular1([] , ListSTrie)
-- HasTrieRegular1(Tree, TreeTrie)
-- HasTrieIsomorph(Context,Type,IsoType,toIso,fromIso)
-- HasTrieIsomorph( HasTrie (PF [a] [a] :->: v)
-- , ListSTrie a v, PF [a] [a] :->: v
-- , \ (ListSTrie w) -> w, ListSTrie )
-- enumerateEnum :: (Enum k, Num k, HasTrie k) => (k :->: v) -> [(k,v)]
-- enumerateEnum t = [(k, f k) | k <- [0 ..] `weave` [-1, -2 ..]]
-- where
-- f = untrie t
#define HasTrieIntegral(Type) \
instance HasTrie Type where { \
type STrie Type = IT.IntTrie; \
sTrie = (<$> IT.identity); \
sUntrie = IT.apply; \
}
-- enumerate = enumerateEnum;
HasTrieIntegral(Int)
HasTrieIntegral(Integer)
-- Memoizing higher-order functions
HasTrieIsomorph((HasTrie a, HasTrie (a :->: b), HasLub b)
,a -> b, a :->: b, trie, untrie)
{--------------------------------------------------------------------
Regular instances.
--------------------------------------------------------------------}
-- Re-think where to put these instances. I want different versions for
-- list, depending on whether I'm taking care with bottoms.
instance Regular [a] where
type PF [a] = Unit :+:! Const (Lift a) :*:! Lift
unwrap [] = InL' (Const ())
unwrap (a:as) = InR' (Const (Lift a) :*:! Lift as)
wrap (InL' (Const ())) = []
wrap (InR' (Const (Lift a) :*:! Lift as)) = a:as
-- Rose tree (from Data.Tree)
--
-- data Tree a = Node a [Tree a]
-- instance Functor Tree where
-- fmap f (Node a ts) = Node (f a) (fmap f ts)
instance Regular (Tree a) where
type PF (Tree a) = Const a :*: []
unwrap (Node a ts) = Const a :*: ts
wrap (Const a :*: ts) = Node a ts
-- Note that we're using the non-strict pairing functor.
-- Does PF (Tree a) have the right strictness?
-- I think so, since a tree can be either _|_ or Node applied to a
-- possibly-_|_ value and a possibly-_|_ list.
{-
{--------------------------------------------------------------------
Testing
--------------------------------------------------------------------}
fib :: Integer -> Integer
fib m = mfib m
where
mfib = memo fib'
fib' 0 = 0
fib' 1 = 1
fib' n = mfib (n-1) + mfib (n-2)
-- The eta-redex in fib is important to prevent a CAF.
-}
{-
ft1 :: (Bool -> a) -> [a]
ft1 f = [f False, f True]
f1 :: Bool -> Int
f1 False = 0
f1 True = 1
trie1a :: HasTrie a => (Bool -> a) :->: [a]
trie1a = trie ft1
trie1b :: HasTrie a => (Bool :->: a) :->: [a]
trie1b = trie1a
trie1c :: HasTrie a => (Either () () :->: a) :->: [a]
trie1c = trie1a
trie1d :: HasTrie a => ((Trie () :*: Trie ()) a) :->: [a]
trie1d = trie1a
trie1e :: HasTrie a => (Trie () a, Trie () a) :->: [a]
trie1e = trie1a
trie1f :: HasTrie a => (() :->: a, () :->: a) :->: [a]
trie1f = trie1a
trie1g :: HasTrie a => (a, a) :->: [a]
trie1g = trie1a
trie1h :: HasTrie a => (Trie a :. Trie a) [a]
trie1h = trie1a
trie1i :: HasTrie a => a :->: a :->: [a]
trie1i = unO trie1a
ft2 :: ([Bool] -> Int) -> Int
ft2 f = f (alts 15)
alts :: Int -> [Bool]
alts n = take n (cycle [True,False])
f2 :: [Bool] -> Int
f2 = length . filter id
-- Memoization fails:
-- *FunctorCombo.MemoTrie> ft2 f2
-- 8
-- *FunctorCombo.MemoTrie> memo ft2 f2
-- ... (hang forever) ...
-- Would nonstrict memoization work?
f3 :: Bool -> Integer
f3 = const 3
-- *FunctorCombo.MemoTrie> f3 undefined
-- 3
-- *FunctorCombo.MemoTrie> memo f3 undefined
-- *** Exception: Prelude.undefined
f4 :: () -> Integer
f4 = const 4
-- *FunctorCombo.MemoTrie> f4 undefined
-- 4
-- *FunctorCombo.MemoTrie> memo f4 undefined
-- 4
f5 :: ((),()) -> Integer
f5 = const 5
-- *FunctorCombo.MemoTrie> f5 undefined
-- 5
-- *FunctorCombo.MemoTrie> memo f5 undefined
-- 5
f6 :: Either () () -> Integer
f6 = const 6
-- *FunctorCombo.MemoTrie> f6 undefined
-- 6
-- *FunctorCombo.MemoTrie> memo f6 undefined
-- *** Exception: Prelude.undefined
-- Aha!
t6 :: Either () () :-> Integer
t6 = trie f6
-- *FunctorCombo.MemoTrie> t6
-- Id 6 :*: Id 6
-}