{-# LANGUAGE MultiParamTypeClasses, FlexibleInstances #-} -- Explicit sharing across several expressions: -- Explicit sharing with sklansky -- This code is to answer the challenge by Matthew Naylor. {- Matthew Naylor wrote: http://www.haskell.org/pipermail/haskell-cafe/2008-February/039671.html If a 128-bit processor were being developed, sklansky could reasonably be passed 128 elements, *Main> test_sklansky 128 -- on an AMD64 2200 (3.71 secs, 296129440 bytes) and could form part of a larger expression, and be called several times. So I think this is close to a real situation where CSE is not practical. But like you say, a human would not write such a humungous expression by hand; hand-written expressions may therefore be ideal for crunching by your CSE method. Still, we might like to write generators like sklansky. Hope is offered by the new "let_" construct, but it's not clear how to use it in this case. The problem is that sklansky is a function over lists of expressions rather than single expressions, and the "let_" construct can only bind single expressions and return single expressions. To write sklansky using your approach, it seems (to me) that the DSL's expression type needs to be extended to support lists. Will this not be complicated and introduce notational inconvenience? (In an earlier post, I outlined a method for embedding algebraic data types in Haskell, and it does have some notational burden.) -} -- We demonstrate the explicit sharing in the sklansky example, without -- adding lists to our DSL and without explicit use of monads. -- We explicitly avoid rewriting sklansky in the monadic style. -- The challenge is to insert the statically unknown number of -- let_ expressions to indicate the explicit sharing. module ExpLetList where import ExpF (N(..), NodeId, DAG(..), Exp(..), do_bench) import ExpLet (ExpLet(..)) import BiMap (empty) import Control.Monad.State -- The key are two operations for class LC n m a where gseq :: [n a] -> m [a] gcom :: m [a] -> ([n a] -> m [a]) -> m [a] -- The law: gcom gsec === id -- The best way to understand them is through the following instance -- of m == n: instance Monad m => LC m m a where gseq = sequence gcom m f = m >>= f . map return -- We will be using the structure for the case when m /= n -- N itself is not a monad instance LC N (State DAG) Int where gseq xs = sequence (map unN xs) gcom m f = m >>= f . map (N . return) -- Recall the original sklansky {- sklansky :: (a -> a -> a) -> [a] -> [a] sklansky f [] = [] sklansky f [x] = [x] sklansky f xs = left' ++ [ f (last left') r | r <- right' ] where (left, right) = splitAt (length xs `div` 2) xs left' = sklansky f left right' = sklansky f right -} -- To indicate the explicit sharing, we have to change -- the code, of course. -- But we don't have to re-write in the full-blown -- monadic style -- For example, the key part, the comprehension -- left' ++ [ f (last left') r | r <- right' ] -- remained the same. We just inserted a `generalized let' sklansky' :: LC m n a => (m a -> m a -> m a) -> [m a] -> n [a] sklansky' f a@[] = gseq a sklansky' f [x] = gseq [x] sklansky' f xs = gcom left' (\left' -> gcom right' (\right' -> gseq $ left' ++ [ f (last left') r | r <- right' ])) where (left, right) = splitAt (length xs `div` 2) xs left' = sklansky' f left right' = sklansky' f right test_sklansky' n = runState sk (DAG empty) where sk = sklansky' add xs xs = (map (variable . show) [1..n]) :: [N Int] t4 = test_sklansky' 4 -- modulo 6<->4 {- ([0,2,6,7],DAG BiMap[(0,NVar "1"),(1,NVar "2"), (2,NAdd 0 1),(3,NVar "3"),(4,NVar "4"), (5,NAdd 3 4),(6,NAdd 2 3),(7,NAdd 2 5)]) -} {- *ExpLetList> test_sklansky' 8 ([0,2,6,7,16,17,18,19], DAG BiMap[(0,NVar "1"),(1,NVar "2"), (2,NAdd 0 1),(3,NVar "3"),(4,NVar "4"), (5,NAdd 3 4),(6,NAdd 2 3),(7,NAdd 2 5), (8,NVar "5"),(9,NVar "6"),(10,NAdd 8 9), (11,NVar "7"),(12,NVar "8"), (13,NAdd 11 12),(14,NAdd 10 11),(15,NAdd 10 13), (16,NAdd 7 8),(17,NAdd 7 10),(18,NAdd 7 14),(19,NAdd 7 15)]) -} -- Not so bad... The implicit sharing detection is quite fast bench_skl' n = do_bench $ test_sklansky' n {- -- in ExpF.hs: using implicit sharing only *ExpF> bench_skl 128 576 (0.50 secs, 10,412,660 bytes) *ExpF> bench_skl 256 1280 (3.09 secs, 38,912,932 bytes) -} -- Now, with the explicit sharing -- It takes significantly less time, and significantly less space {- *ExpLetList> bench_skl' 128 576 (0.09 secs, 4,858,480 bytes) *ExpLetList> bench_skl' 256 1280 (0.21 secs, 4,170,644 bytes) -}