The main program for Lambdascope > {-# LANGUAGE NoMonomorphismRestriction,DoRec,CPP #-} > module Main where > import INet > import Diagram hiding (S) > import Lambda > import Grid > import Graphics.Rendering.OpenGL (($=), GLfloat) > import qualified Graphics.Rendering.OpenGL as GL > import qualified Graphics.UI.GLFW as GLFW > import qualified Data.Map as Map > import Control.Monad.Fix > import Control.Monad.Task > import Graphics.UI.GLFW.Task > import Control.Monad.IO.Class > import Control.Monad.Trans > import Control.Monad.Trans.State > import Control.Monad.Identity > import Control.Monad > import Data.IORef > import qualified Data.Set as Set > import qualified Data.IntMap as IntMap > import Data.IntMap hiding (null, lookup, mapMaybe, map) > import Data.Maybe (mapMaybe, fromMaybe) > import Control.Arrow (first, second) > import Data.List (intercalate, sort) An interactive net is a mapping from node IDs to their connected (node ID, port No) pairs. > type Clicks = Map.Map String Int > data MainState > = MS { showHelp :: Bool -- whether to display help message > , inet :: INet -- current INet being displayed > , frame :: Frame -- single frame of display (for animation) > , hlset :: Maybe ([(Int, Int)], HLSet) -- wires to highlight > , backup :: [(INet, GridMap Direction, Clicks)] -- backups for undo/rollback > , clicks :: Clicks -- collection of statistics > , preset :: Int -- next preset to load > , title :: String -- title of current inet > } > initState net = MS False net emptyFrame Nothing [] Map.empty 0 "" > lift3 = lift . lift . lift > toggleShowHelp = lift3 $ modify $ \s -> s { showHelp = not (showHelp s) } > getShowHelp = lift3 $ get >>= return . showHelp > modifyTitle f = lift3 $ modify $ \s -> s { title = f (title s) } > getTitle = lift3 $ get >>= return . title > setTitle = modifyTitle . const > modifyINet f = lift3 $ modify $ \s -> s { inet = f (inet s) } > setINet = modifyINet . const > getINet = lift3 $ get >>= return . inet > modifyFrame f = lift3 $ modify $ \s -> s { frame = f (frame s) } > setFrame = modifyFrame . const > getFrame = lift3 $ get >>= return . frame > snapshot = do > grid <- lift $ getGrid > lift3 $ modify $ \s -> s { backup = take 10 $ (inet s, grid, clicks s) : backup s } > rollback = do > grid <- lift3 $ do > b <- fmap backup get > case b of > [] -> return Nothing > (i, g, c) : bs -> modify (\s -> s { inet = i, clicks = c, backup = bs }) >> return (Just g) > maybe (return False) (\g -> lift (setGrid g) >> return True) grid > click name f = lift3 $ modify $ \s -> s { clicks = click' (clicks s) } > where click' m = maybe (inc 0) inc $ Map.lookup name m > where inc x = Map.insert name (f x) m > resetClicks = lift3 $ modify $ \s -> s { clicks = Map.empty } > getClicks = do > m <- lift3 $ get >>= return . clicks > return $ intercalate ", " $ map (\(x, y) -> x ++ " " ++ show y) $ Map.toList m > renderINet = getINet >>= setDiagram . netToDiagram > loadPreset f = do > i <- lift3 $ get >>= return . preset > lift3 $ modify $ \s -> s { preset = f i, backup = [], clicks = Map.empty } > let (title, inet) = presetINet (f i) > setTitle title > setINet inet > lift clearGrid > setDirty Dirty > renderINet > autoAdjust Main Program #ifdef _EnableGUI_ > import EnableGUI > main = enableGUI >> do #else > main = do #endif > GLFW.initialize > dState <- initWindow w h > evalStateT (evalStateT (evalGridT_ (runTask start) margin) dState) (initState emptyINet) > GLFW.closeWindow > GLFW.terminate > where > -- prepare an initial diagram to load > -- net = nodeToNet $ mkNode . termToNode $ App cube (VInt 3) -- (termToNode t4)) -- (App cube (VInt 3)))) > w = 800 > h = 600 > start = do > loadPreset id > renderINet > waitForEvents <- liftIO registerTaskCallbacks > setupEvents $ (snapshot >>) . reduce > fork $ forever $ watch (onKey >=> isPress >=> isKey 'H') >> toggleShowHelp >> setDirty Lightly > fork $ forever $ watch (onKey >=> isPress >=> isKey 'L') >> snapshot >> lift clearGrid >> setDirty Dirty > fork $ forever $ watch (onKey >=> isPress >=> isKey '[') >> loadPreset (\x -> x - 1) > fork $ forever $ watch (onKey >=> isPress >=> isKey ']') >> loadPreset (+1) > fork $ forever $ watch (onKey >=> isPress >=> isKey GLFW.BACKSPACE) >> > rollback >> renderINet >> setDirty Dirty > mapM_ (\(c, f) -> fork $ forever $ watch (onKey >=> isPress >=> isKey c) >> snapshot >> getINet >>= f >>= setINet >> > renderINet >> setDirty Dirty) > [('R', reduceAll), > ('Y', resetAll), > ('X', crossAll), > ('B', betaAll), > ('E', eraseAll), > ('U', unwindAll), > ('S', scopeAll), > ('C', loopcutAll), > ('T', toTerm), > ('M', metaAll), > -- ('P', pruneAll), > ('O', outer), > ('Q', outerRed), > ('A', outerRedAll), > ('D', demoteDup) ] > forever $ do > waitForEvents > whenDirty renderAll > setDirty None > where > renderAll = do > title <- getTitle > font <- getFont > stats <- getClicks > renderS <- fmap (renderScene font) getFactor > renderH <- fmap (renderHelp font ("INet for (" ++ title ++ ") " ++ stats)) getShowHelp > oldFrame <- getFrame > newFrame <- getDiagram >>= renderDiagram > setFrame newFrame > d <- getDirty > if d < Dirty > then liftIO (clearAll >> renderS newFrame >> renderH) > else do > let f = betweenFrame oldFrame newFrame > t0 <- liftIO $ GL.get GLFW.time > let dur = 0.5 > animate t | t >= t0 + dur = return () > | otherwise = do > t <- liftIO $ GL.get GLFW.time > let p = realToFrac (t - t0) / dur > g = f (if p > 1 then 1 else p) > liftIO (clearAll >> renderS g >> renderH) > animate t > fork $ animate t0 > clearAll = GL.clear [GL.ColorBuffer] > renderScene font (cx, cy, s) f = GL.preservingMatrix (do > GL.translate (vector3 (realToFrac (cx / unit)) (realToFrac (cy / unit)) 0) > GL.scale (realToFrac s) (realToFrac s) (1 :: GL.GLfloat) > renderFrame font Set.empty f) > renderHelp font tagline help = do > GL.Size w h <- GL.get GLFW.windowSize > GL.preservingMatrix (do > GL.color $ color3 0.2 0.3 0.8 > GL.translate (vector3 (- fromIntegral w / unit' / 2) (fromIntegral h / unit' / 2 - 1.25) 0) > GL.scale 0.8 0.8 (1::GLfloat) > renderString font ("H toggles help, ESC quits. " ++ tagline) > GL.translate $ vector3 0 (-1.25) 0 > when help $ renderText font helpText) > GLFW.swapBuffers > helpText = unlines > [ -- "CTRL+ mouse pan" > -- , "ALT + mouse zoom" > "Left click apply any rule to node" > , "Drag mouse move node" > , "[ or ] load presets" > , "Space auto zoom" > , "BackSpace rollback one step (max 10)" > , "L auto layout all nodes" > , "R reduce to head normal form" > , "X repeat cross rule everywhere" > , "B repeat beta rule everywhere" > , "E repeat erase rule everywhere" > , "U apply unwind rule everywhere" > , "S apply scope rule everywhere" > , "C apply loopcut rule everywhere" > , "T print term from net (after loopcut)" > , "M repeat meta rule everywhere" > -- , "P prune all none root tree" -- temporarily disable prune since it is not implemented correctly > , "O outermost reduction once" > , "Q outermost reduction once (also goes under lambda)" > , "A repeat outermost reduction (also goes under lambda)" > , "D zap duplicator's value (become call-by-need)"] > > presetINet i = (title, node) > where > (title, node) = inets !! (i `mod` n) > n = length inets > inets > = [ ("test0", (testL0, 4)) > , ("trace0", cleanup $ nodeToNet $ mkNode $ termToNode (trace juggle)) > -- , ("ccaE", cleanup $ nodeToNet $ mkNode $ termToNode ccaE) > , ("map2", nodeToNet $ mkNode $ termToNode map2) > , ("exp", cleanup $ nodeToNet $ mkNode $ termToNode e) > , ("test1", (testL1, 4)) > , ("test2", (testL2, 4)) > , ("test3", (testL3, 4)) > , ("test4", (testL4, 4)) > ] ++ map (second (nodeToNet . mkNode)) > [ ("2", termToNode t2) > , ("2 2", termToNode t4) > , ("opt 1", termToNode (opt 1)) > , ("opt 2", termToNode (opt 2)) > , ("opt 3", termToNode (opt 3)) > , ("one", termToNode one) > -- , ("(\\x.x) x", termToNode (App (Abs Z) (VStr "x"))) > -- , ("cube 3", termToNode $ App cube (VInt 3)) > ] > cleanup = fst . runIdentity . repeatRule (applyRule (ruleM erase)) > demoteDup = return . first (IntMap.map demote) > where > demote a = case a of > (Duplicator _, cp, cv) -> (Duplicator (Sup False), cp, cv) > _ -> a > reLayout net d = (net, d) activates a local rule to a node, and apply it once. This will also revive dormant Cycle. > -- reduce :: Int -> IO ([Int], Diagram) > reduce i = do > -- traceShow ("NID", i) > net@(netMap, _) <- getINet > let a@(at, ap, av) = revive (netMap ! i) > (j, n) = head ap > b@(bt, bp, bv) = revive (netMap ! j) > when (ap == []) $ error "here!" -- delete i net > net' <- localAll (i, a) (j, b) net >>= return . fromMaybe net > -- traceShow ("reduced", net, net') > setINet net' > renderINet > where > revive (Duplicator (Cycle _), ap, v) = (Duplicator (Cycle True), ap, v) > revive x = x > localAll = meta_ ->- beta_ ->- cross_ ->- erase_ -- ->- share_ Note that in optimal reduction, the erase is a global rule rather than an outermost one because it'll otherwise results in redudant beta or meta reduction. > reduceAll = wrapRule (applyRule (ruleM resetCycle) +&+ > repeatRule (applyRule meta_) +&+ > repeatRule (applyRule beta_) +&+ > repeatRule (applyRule cross_) +&+ > repeatRule (applyRule erase_)) > resetAll = wrapRule (applyRule (ruleM resetCycle)) > crossAll = wrapRule (applyRule cross_) > betaAll = wrapRule (applyRule beta_) > eraseAll = wrapRule (applyRule erase_) > metaAll = wrapRule (applyRule meta_) > unwindAll = wrapRule (applyRule unwind_) > scopeAll = wrapRule (applyRule scope_) > loopcutAll = wrapRule (applyRule loopcut_) > outer = wrapRule (outermost localAll) > outerRed = wrapRule (outerReducible localAll) > outerRedAll = wrapRule (repeatRule (outerReducible localAll)) > pruneAll = wrapRule (return . prune) > readbackM = > applyRule unwind_ >=> return . fst >=> > applyRule scope_ >=> return . fst >=> > wrapRule (repeatRule (applyRule cross_)) >=> > applyRule loopcut_ >=> return . fst >=> > wrapRule (repeatRule (applyRule cross_)) > toTerm net = do > net' <- readbackM net > let t = netToTerm net' > liftIO $ putStrLn ("toTerm = " ++ show t) > return net' > wrapRule f net = fmap fst (f net) Rules with click counters. > cross_ = wrapClick "cross" cross > beta_ = wrapClick "beta" beta > meta_ = wrapClick "meta" meta > erase_ = wrapClick "erase" erase > unwind_ = wrapClick "unwind" unwind > scope_ = wrapClick "scope" scope > loopcut_ = wrapClick "loopcut" loopcut > -- share_ = wrapClick "share" share > wrapClick counter r a b c = do > n <- ruleM r a b c > click counter (maybe id (\_ -> (+1)) n) > return n Testing ======= We can compose INet nodes by wiring them > two s = do > rec a <- abstractor s b e > b <- abstractor a c m > c <- applicator d f b > d <- delimiter e c 0 > e <- duplicator a k d 0 > f <- applicator g l c > g <- delimiter k f 0 > h <- eraser m > i <- eraser l > j <- eraser k > k <- duplicator e g j 0 > l <- duplicator m i f 0 > m <- duplicator b l h 0 > return a > four = do > rec s <- initiator a > a <- applicator b b s > b <- duplicator t a a 0 > t <- two b > return s or we can write a Generalized Lambda term, and convert it to INet. > x = VStr "x" > f = Abs (VFunc 1 "f" Z) > g = Abs (VFunc 1 "g" Z) > t2 = church 2 > t2' = App (App (church 2) f) x --App (App (Abs (Abs (App (S Z) (App (S Z) Z)))) f) x > t4 = App t2 t2 > t4' = App (App t4 f) x > church n = Abs (Abs (app n (S Z) Z)) > app 0 f x = x > app n f x = App f (app (n - 1) f x) > double = Abs (App (VFunc 2 "+" (App id Z)) Z) > where id = Abs Z > testDouble n = app n double (VInt 1) Test substitution > testSub = App f (VInt 7) > where > s = Abs (App (VFunc 2 "*" Z) Z) > f = Abs (App (Abs (App (VFunc 2 ":" Z) (App (VFunc 2 "*" Z) (S Z)))) > (App (VFunc 2 "*" Z) Z)) Test for meta level fuction with arity > d1 = App (Abs (App (VFunc 2 "g" Z) Z)) t2' Test for handling disconnected graph, rather than tree > test = fromList [ > (0, (Eraser, [(1, 0)], Nothing)), > (1, (Eraser, [(0, 0)], Nothing)), > (2, (Eraser, [(3, 0)], Nothing)), > (3, (Eraser, [(2, 0)], Nothing)) ] Test for tuples > p0 = App (Abs (Fst Z)) (Tup t0 t1) > t0 = Abs (Abs Z) > t1 = Abs (Abs (App (S Z) Z)) > ones = Y (Abs (Tup (VInt 1) Z)) > one = Fst ones Tests for cross rule with self-loop for annihilate: two duplicators wiring to each other on one side > testL0 = fromList [ > (0, (Eraser, [(2, 1)], Nothing)), > (1, (Eraser, [(3, 2)], Nothing)), > (2, (Delimiter, [(3, 1), (0, 0)], Just 0)), > (3, (Duplicator Dup, [(4, 0), (2, 0), (1, 0)], Just 0)), > (4, (Duplicator Dup, [(3, 0), (4, 2), (4, 1)], Just 0))] two duplicators wiring to each other on both sides > testL1 = fromList [ > (2, (Duplicator Dup, [(3, 0), (2, 2), (2, 1)], Just 0)), > (3, (Duplicator Dup, [(2, 0), (3, 2), (3, 1)], Just 0))] for commute: similar to testL0 > testL2 = fromList [ > (0, (Eraser, [(2, 1)], Nothing)), > (1, (Eraser, [(2, 2)], Nothing)), > (2, (Duplicator Dup, [(3, 0), (0, 0), (1, 0)], Just 1)), > (3, (Duplicator Dup, [(2, 0), (3, 2), (3, 1)], Just 0))] similar to testL1 > testL3 = fromList [ > (2, (Duplicator Dup, [(3, 0), (2, 2), (2, 1)], Just 1)), > (3, (Duplicator Dup, [(2, 0), (3, 2), (3, 1)], Just 0))] when a single duplicator loops its two ports > testL4 = fromList [ > (0, (Eraser, [(1, 1)], Nothing)), > (1, (Delimiter, [(2, 0), (0,0)], Just 0)), > (2, (Duplicator Dup, [(1, 0), (2, 2), (2, 1)], Just 0))] These are tests for optimality. With church numbers, (n 2 i x) takes exponential time in call-by-need, but only linear to n in optimal reduction. > i = Abs Z > opt n = App (App (App (church n) (church 2)) i) i Chart for opt 1 .. 7 Optimal cross, beta, size(min, max) 47 6 (2, 27) 84 9 (2, 40) 128 12 (2, 54) 179 15 (2, 69) 237 18 (2, 85) 302 21 (2, 102) 374 24 (2, 120) Lazy beta, size(min, max) 6 (2, 14) 11 (2, 18) 20 (2, 32) 37 (2, 52) 70 (2, 84) 135 (2, 152) 264 (2, 284) 392 Compare Lazy, Completely Lazy (M. J. Thyer's thesis: http://thyer.name/phd-thesis/) and Optimal using number of beta reduction, steps, interactions (excluding garbage collection) as metrics respectively. n Lazy C.Lazy Optimal -------------------------- 1 6 8 53 2 11 15 93 3 20 25 140 4 37 40 194 5 70 66 255 6 135 114 323 7 264 204 398 8 392 377 480 9 644 719 569 It's easy to tell that they are of O(n * 2^n), O(n^7) and O(n^2) respectively. It's also worth mentioning that if we only count the number of betas, optimal is O(n), and completely lazy is O(n^3), lazy is O(n^4). Test for integral function ========================== integral i x = (i, \dt -> integral (next dt i (fst x)) (snd x dt)) also make tuple construction involve beta reduction. > tup x y = App (App (Abs (Abs (Tup (S Z) Z))) x) y > next = VFunc 3 "next" > integral = > Y (Abs -- \integral -> > (Abs -- \i -> > (Abs -- \s -> > (tup (S Z) -- i, > (Abs -- \dt -> > (App (App (S (S (S Z))) -- integral > (App (App (next Z) -- next dt > (S (S Z))) -- i > (Fst (S Z)))) -- fst x > (App (Snd (S Z)) Z))))))) -- snd x dt > e = Y (App integral (VInt 1)) -- (1, \dt -> (next dt 1 1, snd e)) > unfold = Abs (App (Snd Z) (VStr "dt")) > expN n = Fst (app n unfold e) Chart of computing expN 1 to expN 7 Optimal cross, beta, meta (total, include projection), meta (arith), size (min, max) 199 11 6 3 (41, 104) 472 16 12 6 (44, 155) 820 21 18 9 (47, 211) 1254 26 24 12 (50, 272) 1818 31 30 15 (53, 338) 2570 36 36 18 (56, 409) 3580 41 42 21 (59, 485) Call-by-need 13 3 (41, 134) 28 9 (44, 277) 50 18 (47, 516) 79 30 (50, 843) 115 45 (53, 1267) 158 63 (56, 2800) 208 84 (59, 2454) It indeed shows that call-by-need incurs recomputation at the meta arithmetic level: cbn(n) = optimal(n) + cbn(n - 1) Tests for traversing circular structure using cursor. A cursor is a tuple Data Cursor a = (a, List a -> List a) List is nested tuple, a circular structure Data List a = (a, List a) > c1 = tup (VInt 1) (Abs Z) -- c1 = (1, \x -> (2, (3, x))) > c2 = tup (VInt 1) (Abs (tup (VInt 2) Z)) -- c2 = (1, \x -> (2, (3, x))) > c3 = tup (VInt 1) (Abs (tup (VInt 2) (tup (VInt 3) Z))) -- c3 = (1, \x -> (2, (3, x))) adv (Cursor u@(Elem i v) f) = let f' x = next (f (Cons u x)) u' = this (fix (f . Cons u)) in Cursor u' f' (u', f' > nextc = > Abs > (tup > (Fst (Y (Abs (App (Snd (S Z)) (tup (Fst (S Z)) Z))))) > (Abs (Snd (App (Snd (S Z)) (tup (Fst (S Z)) Z)))) > ) > c n = Fst (app n nextc c2) 3 0 (31, 50) 5 0 (53, 93) 7 0 (77,130) 11 0 (..) 13 15 19 9 3 15 5 21 8 29 12 35 14 41 17 49 21 9 3 15 6 23 10 29 13 37 17 43 20 51 24 9 3 15 6 25 10 34 15 47 21 59 28 75 36 Power Cube Example ================== > cons x xs = App (VFunc 2 ":" x) xs > cond c t f = App (App (VFunc 3 "cond" c) t) f > eq x y = App (VFunc 2 "==" x) y > times x y = App (VFunc 2 "*" x) y > minus x y = App (VFunc 2 "-" x) y > power = Y (Abs (Abs (Abs ( > cond (eq (S Z) (VInt 1)) > (VInt 1) > (times Z (App (S (S Z)) (minus (S Z) (VInt 1)))))))) > cube = App power (VInt 3) > powerCube = cons power (cons cube (App cube (VInt 5))) CCA Integral of Exp Example =========================== GHC cannot give the most optimized result for the following expression: trace (juggle . ((dup . snd) `cross` id) . (swap `cross` id) . juggle . ((((+1) `cross` id) . trace (juggle . (dup `cross` id) . swap . (acc `cross` id) . juggle)) `cross` id) . juggle . (swap `cross` id) . juggle) trace f x = let (y, z) = f (x, z) in y juggle ((x, y), z) = ((x, z), y) cross f g (x, y) = (f x, g y) acc (x, i) = i + dt * x assoc ((x,y),z) = (x,(y,z)) unassoc (x,(y,z)) = ((x,y),z) trace f x = let g z = snd (f (x, z)) in fst (f (x, (fix g))) > trace = App $ Abs $ Abs $ Fst $ App (S Z) (Tup Z (Y (Abs $ Snd $ App (S (S Z)) (Tup (S Z) Z)))) > plus x y = App (VFunc 2 "+" x) y > juggle = Abs $ Tup (Tup (Fst (Fst Z)) (Snd Z)) (Snd (Fst Z)) > across f g = Abs $ Tup (App f (Fst Z)) (App g (Snd Z)) > acc = Abs $ plus (Snd Z) (times (VStr "dt") (Fst Z)) > assoc = Abs $ Tup (Fst (Fst Z)) (Tup (Snd (Fst Z)) (Snd Z)) > unassoc = Abs $ Tup (Tup (Fst Z) (Fst (Snd Z))) (Snd (Snd Z)) > dup = Abs $ Tup Z Z > f `dot` g = App (App (Abs $ Abs $ Abs $ App (S (S Z)) (App (S Z) Z)) f) g > cca0 = assoc `dot` unassoc > ccaE = trace (juggle `dot` > ((dup `dot` snd) `across` id) `dot` > (swap `across` id) `dot` juggle `dot` > ((((Abs $ plus Z (VInt 1)) `across` id) `dot` > trace (juggle `dot` (dup `across` id) `dot` swap `dot` > (acc `across` id) `dot` juggle)) `across` id) `dot` > juggle `dot` (swap `across` id) `dot` juggle) > {- > ccaE = trace > (juggle `dot` (dup `across` id) `dot` swap `dot` > (acc `across` id) `dot` juggle) > -} > where > snd = Abs $ Snd Z > id = Abs Z > swap = Abs (Tup (Snd Z) (Fst Z)) toTerm = Abs (Tup (App (VFunc 2 "+" (Snd Z)) (VInt 1)) (App (VFunc 2 "+" (Snd Z)) (App (VFunc 1 "(* dt)" (VFunc 2 "+" (Snd Z))) (VInt 1)))) f (a, b) = (1 + b, dt * b + 1) f (a, b) = (1 + b, b + dt * (1 + b)) Map Fusion ========== map f (x,y) = (f x, map f y) > map' = Y $ Abs $ Abs $ Abs $ Tup (App (S Z) (Fst Z)) (App (App (S (S Z)) (S Z)) (Snd Z)) > map2 = App (Abs (App Z f `dot` App Z g)) map' > map2' = App map' (f `dot` g) (map f . map g) (x, y) = ((f . g) x, (map f ((map g) y))) (map f . map g) = Y F where F h (x, y) = ((f.g) x, h y)