Solutions to exercises in [Programming with Arrows][1] === Since John Hughes wrote his paper, Arrows have become part of the Haskell standard library. The now-standard version differs incompatibly from the one described in the paper, in that Arrows have to be instances of the [Category](http://hackage.haskell.org/packages/archive/base/latest/doc/html/Control-Category.html#t:Category) typeclass, so these solutions have been updated to run under the modern, standard definition of Haskell Arrows. The program depends on a similarly updated `Sim` module, which can be found [here](Sim.lhs). The [updated Circuits module](Circuits.lhs) might also be of interest. The code has been written with explicit type signatures for top-level bindings and with an attempt to remove all compiler warnings (although GHC changes so rapidly that it may well complain about something new by the time you read this). [1]: http://www.cs.chalmers.se/~rjmh/afp-arrows.pdf Formalia --- The Arrows language pragma enables the pointed Arrow notation in GHC. > {-# LANGUAGE Arrows #-} > > import Control.Arrow > import Control.Category > import Control.Monad > import Control.Monad.Fix > import Data.Either Function composition and the identity operation are both defined more generally in `Control.Category` than in the Prelude, and we hide the names `and` and `or` so we can use them to name circuit components without getting compiler warnings. > import Prelude hiding ((.), id, and, or) We prefer the definition of `nor` from the `Sim` module. While `Circuits.nor` works with any Arrow, `Sim.nor` simulates gate delay, which is useful when we actually need to use `nor`. > import Circuits hiding (nor) > import Sim The SF arrow --- Copied from the paper, apart from factoring part of the Arrow instance out as the Category instance. > newtype SF a b = SF { runSF :: [a] -> [b] } > > instance Category SF where > id = SF id > SF f . SF g = SF (f . g) > > instance Arrow SF where > arr f = SF (map f) > first (SF f) = SF (unzip >>> first f >>> uncurry zip) > > instance ArrowChoice SF where > left (SF f) = SF $ \xs -> combine xs (f $ lefts xs) > where combine (Left _:xs) (z:zs) = Left z : combine xs zs > combine (Right y:xs) zs = Right y : combine xs zs > combine [] _ = [] > combine _ _ = error "Something impossible happened in ArrowChoice SF" > > instance ArrowLoop SF where > loop (SF f) = SF $ \as -> > let (bs,cs) = unzip (f (zip as (stream cs))) in bs > where stream ~(x:xs) = x:stream xs > > instance ArrowCircuit SF where > delay x = SF (init . (x:)) Exercise 2.5-1 --- See Exercise 3.5-3 below for a prettier version using pointed arrow notation. This implementation is merely a desugared version of that one. > filterA :: ArrowChoice arr => arr a Bool -> arr [a] [a] > filterA p = arr listcase >>> -- Check if list is empty. > (arr (const []) ||| -- Empty, so stop. > ((arr fst >>> p) &&& id -- Check if head of list fulfills predicate. > >>> arr check -- Convert tuple with bool to Left/Right. > >>> ((arr snd >>> filterA p) ||| -- Discard head. > (arr id *** filterA p >>> arr (uncurry (:)))))) -- Keep head. > where listcase [] = Left () > listcase (x:xs) = Right (x,xs) > check (False, v) = Left v > check (True, v) = Right v Exercise 2.5-2 --- > data SP a b = Put b (SP a b) | Get (a -> SP a b) > > runSP :: SP a b -> [a] -> [b] > runSP (Put b s) as = b:runSP s as > runSP (Get k) (a:as) = runSP (k a) as > runSP (Get _) [] = [] > > instance Category SP where > id = Get $ flip Put id > Put v f . g = Put v (f . g) > Get f . Put v g = f v . g > Get f . Get g = Get $ \x -> Get f . g x Defining first presents a problem, as consuming one element of input does not necessarily produce exactly one element of output. The synchronisation function I have implemented opts to never throw away and never duplicate data, but that implies that the lists of "delayed" I/O may grow with no upper bound. It is not hard to modify the function to follow some other strategy with constant upper bounds on buffering requirements. And of course, the use of lists here is highly inefficient, a proper deque (Data.Sequence, for example) should be used. > instance Arrow SP where > arr f = Get $ \x -> Put (f x) (arr f) > first f = sync f [] [] > > sync :: SP a c -> [a] -> [b] -> SP (a, b) (c, b) > sync (Put v f) xs (y:ys) = Put (v,y) (sync f xs ys) > sync (Put v f) xs [] = Get $ \(x,y) -> Put (v,y) (sync f (xs++[x]) []) > sync (Get f) (x:xs) ys = sync (f x) xs ys > sync (Get f) [] ys = Get $ \(x,y) -> sync (f x) [] (ys++[y]) > > instance ArrowChoice SP where > left (Put v f) = Put (Left v) (left f) > left (Get f) = Get g > where g (Left x) = left $ f x > g (Right x) = Put (Right x) (left $ Get f) We traverse down the stream function to look for an 'initial' state, then run the function with that state. This depends critically on lazy evaluation. > instance ArrowLoop SP where > loop f = let (f',s) = next f s in f' > where next (Put (v,s) f') _ = (Put v (fst $ next f' s), s) > next (Get f') s = (Get $ \x -> let (f'',s') = next (f' (x,s')) s > in f'', s) > > instance ArrowCircuit SP where > delay v = Put v id Exercise 3.5-1 --- > halfAdd :: Arrow arr => arr (Bool,Bool) (Bool, Bool) > halfAdd = arr $ \(x,y) -> (x&&y, x/=y) > > fullAdd :: Arrow arr => arr (Bool, Bool, Bool) (Bool, Bool) > fullAdd = proc (x,y,c) -> do > (c1,s1) <- halfAdd -< (x,y) > (c2,s2) <- halfAdd -< (s1, c) > returnA -< (c1||c2,s2) It is instructive to see the desugared version: > fullAddNoSugar :: Arrow arr => arr (Bool, Bool, Bool) (Bool, Bool) > fullAddNoSugar = arr (\(x,y,c) -> ((x,y), c)) > >>> first halfAdd > >>> arr (\((c1,s1),c) -> ((s1,c), c1)) > >>> first halfAdd > >>> arr (\((c2,s2),c1) -> (c1||c2,s2)) The *n*-bit adder takes an argument, `m`, which is the number of bits in the inputs, and the adder will fail if the input lists are not each of length `m`. The helper function `more` constructs the `m` adders, connecting the carry-out of one to the carry-in of the next. The carry-in of the first adder is always wired to `False`, and the carry-out of the last one is passed out of the entire adder. > adder :: Arrow arr => Int -> arr ([Bool], [Bool]) ([Bool],Bool) > adder m = proc (x, y) -> more m -< (x,y,False) > where more 0 = proc (_,_,cout) -> returnA -< ([],cout) > more n = proc (x':xs,y':ys,cin) -> do > (carry,b) <- fullAdd -< (x',y',cin) > (z,cout) <- more (n-1) -< (xs,ys,carry) > returnA -< (b:z, cout) Exercise 3.5-2 --- > bsadd :: ArrowCircuit arr => arr (Bool,Bool) Bool > bsadd = proc (x,y) -> do > rec (cout,b) <- fullAdd -< (x,y,cin) > cin <- delay False -< cout > returnA -< b Exercise 3.5-3 --- (a) --- > filterA' :: ArrowChoice arr => arr a Bool -> arr [a] [a] > filterA' p = proc xs -> > case xs of > [] -> returnA -< [] > x:xs' -> do c <- p -< x > l <- filterA' p -< xs' > if c then arr (uncurry (:)) -< (x,l) > else returnA -< l (b) --- I admit that I do not yet have much intuition for command combinators. > filterC :: ArrowChoice arr => arr (env,a) Bool -> arr (env,[a]) [a] > filterC p = proc (env, xs) -> > case xs of > [] -> returnA -< [] > x:xs' -> do c <- p -< (env,x) > l <- filterC p -< (env,xs') > if c then arr (uncurry (:)) -< (x,l) > else returnA -< l Exercise 3.5-4 --- (a) --- > rowC :: Arrow arr => > Int -> arr (env,(a,b)) (c,a) -> arr (env,(a,[b])) ([c],a) > rowC 0 _ = proc (_,(a,[])) -> returnA -< ([],a) > rowC n f = proc (env,(a,b:bs)) -> do > (c,a') <- f -< (env,(a,b)) > (cs,a'') <- rowC (n-1) f -< (env, (a',bs)) > returnA -< (c:cs,a'') (b) --- > counter1bit :: ArrowCircuit arr => arr Bool (Bool,Bool) > counter1bit = proc b -> do > rec sumin <- delay False -< sum > (carry,sum) <- halfAdd -< (sumin,b) > returnA -< (sum, carry) (c) --- > counternbit :: ArrowCircuit arr => Int -> arr Bool ([Bool],Bool) > counternbit n = proc bs -> > (|(rowC n) (\(b,_) -> counter1bit -< b)|) (bs, [0..n-1]) (d) --- > counter1bit' :: ArrowCircuit arr => arr (Bool,Bool) (Bool,Bool) > counter1bit' = proc (b,rst) -> do > rec sumin <- delay False -< sum > (carry,sum) <- halfAdd -< (not rst && sumin,not rst && b) > returnA -< (sum, carry) > > counternbit' :: ArrowCircuit arr => Int -> arr (Bool,Bool) ([Bool],Bool) > counternbit' n = proc (bs,rsts) -> > (|(rowC n) (\(b,rst) -> counter1bit' -< (b,rst))|) (bs, replicate n rsts) Exercise 5.6-1 --- Note that since Sim is an Arrow, the trivial solution to this exercise is to simple reuse the previous definitions. Instead, I will define "realistic" circuits using NOR logic. Since the `nor` gate simulates gate delay, this means all derived gates will also have proper delays. > and :: Monad m => Sim m (Bool,Bool) Bool > and = proc (a,b) -> do > a' <- nor -< (a,a) > b' <- nor -< (b,b) > nubA <<< nor -< (a',b') > > or :: Monad m => Sim m (Bool,Bool) Bool > or = proc (a,b) -> do > a' <- nor -< (a,b) > b' <- nor -< (a,b) > nubA <<< nor -< (a',b') > > lnot :: Monad m => Sim m Bool Bool > lnot = nubA <<< nor <<< id &&& id > > xor :: Monad m => Sim m (Bool, Bool) Bool > xor = proc (a,b) -> do > c <- and -< (a,b) > d <- nor -< (a,b) > nubA <<< nor -< (c,d) > > halfAddSim :: Monad m => Sim m (Bool,Bool) (Bool,Bool) > halfAddSim = proc (x,y) -> do > c <- and -< (x,y) > s <- xor -< (x,y) > nubA -< (c,s) From here on, the definitions are mostly trivial substitutions of the previous ones, as they are all ultimately built on top of half-adders. In fact, rowC is not changed at all. nubA has been used to remove glitches. Note that the circuits are now highly sensitive to timing issues and should be extended with a clock input to be useful for anything. > fullAdd' :: Monad m => Sim m (Bool,Bool,Bool) (Bool,Bool) > fullAdd' = proc (x,y,c) -> do > (c1,s1) <- halfAddSim -< (x,y) > (c2,s2) <- halfAddSim -< (s1,c) > c' <- or -< (c1,c2) > nubA -< (c',s2) > > bsadd' :: MonadFix m => Sim m (Bool,Bool) Bool > bsadd' = proc (x,y) -> do > (b,_) <- (|afix (\ ~(_,cin) -> do > cin' <- initially False nubA -< cin > (cout,b) <- fullAdd' -< (x,y,cin') > returnA -< (b,cout))|) > returnA -< b > > counter1bit'' :: Sim IO (Bool,Bool) (Bool,Bool) > counter1bit'' = proc (b,rst) -> > (|afix (\ ~(sumin,_) -> do > sumin' <- initially False nubA -< sumin > x <- and <<< first lnot <<< delay1 0.2 -< (rst,sumin') > y <- and <<< first lnot <<< nubA -< (rst,b) > (c,s) <- halfAddSim -< (x,y) > returnA -< (s,c))|) > > counternbit'' :: Int -> Sim IO (Bool,Bool) ([Bool],Bool) > counternbit'' n = proc (bs,rsts) -> > (|(rowC n) (\(b,rst) -> counter1bit'' -< (b,rst))|) (bs, replicate n rsts) Note that the following is an orphan instance, since it is defined outside the modules where Sim or ArrowChoice is defined. > instance Monad m => ArrowChoice (Sim m) where > left (Sim f) = sim left' > where left' (Left b) = do (c,s) <- f b > return (Left c,leftState b s) > left' (Right d) = return (Right d, untilLeft f) > > untilLeft :: Monad m => (b -> m (c, State m b c)) > -> (State m (Either b d) (Either c d)) > untilLeft f = Wait infinity undefined $ \e -> > case value e of > Right d -> Ready (Event (time e) (Right d)) (untilLeft f) > Left b -> Lift $ do > (c,s) <- f b > return $ Ready (Event (time e) (Left c)) (leftState b s) > > leftState :: Monad m => b -> State m b c -> State m (Either b d) (Either c d) > leftState b (Ready e s) = Ready (e {value = Left (value e)}) (leftState b s) > leftState b (Lift m) = Lift (liftM (leftState b) m) > leftState b (Wait t s k) = Wait t (leftState b s) $ \e -> > case value e of > Left b' -> leftState b' $ k e { value = b' } > Right d -> Ready (Event (time e) (Right d)) > (leftState b $ k e { value = b }) > > maybeneg :: Monad m => Sim m (Bool,Bool) Bool > maybeneg = proc (x,neg) -> if neg then returnA -< not x > else returnA -< x