Solutions to exercises in Programming with Arrows
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
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.
The updated Circuits module 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).
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
= SF (map f)
arr f SF f) = SF (unzip >>> first f >>> uncurry zip)
first (
instance ArrowChoice SF where
SF f) = SF $ \xs -> combine xs (f $ lefts xs)
left (where combine (Left _:xs) (z:zs) = Left z : combine xs zs
Right y:xs) zs = Right y : combine xs zs
combine (= []
combine [] _ = error "Something impossible happened in ArrowChoice SF"
combine _ _
instance ArrowLoop SF where
SF f) = SF $ \as ->
loop (let (bs,cs) = unzip (f (zip as (stream cs))) in bs
where stream ~(x:xs) = x:stream xs
instance ArrowCircuit SF where
= SF (init . (x:)) delay 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]
= arr listcase >>> -- Check if list is empty.
filterA p const []) ||| -- Empty, so stop.
(arr (fst >>> p) &&& id -- Check if head of list fulfills predicate.
((arr >>> arr check -- Convert tuple with bool to Left/Right.
>>> ((arr snd >>> filterA p) ||| -- Discard head.
id *** filterA p >>> arr (uncurry (:)))))) -- Keep head.
(arr where listcase [] = Left ()
:xs) = Right (x,xs)
listcase (xFalse, v) = Left v
check (True, v) = Right v check (
Exercise 2.5-2
data SP a b = Put b (SP a b) | Get (a -> SP a b)
runSP :: SP a b -> [a] -> [b]
Put b s) as = b:runSP s as
runSP (Get k) (a:as) = runSP (k a) as
runSP (Get _) [] = []
runSP (
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
= Get $ \x -> Put (f x) (arr f)
arr f = sync f [] []
first f
sync :: SP a c -> [a] -> [b] -> SP (a, b) (c, b)
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])
sync (
instance ArrowChoice SP where
Put v f) = Put (Left v) (left f)
left (Get f) = Get g
left (where g (Left x) = left $ f x
Right x) = Put (Right x) (left $ Get f) g (
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
= let (f',s) = next f s in f'
loop f where next (Put (v,s) f') _ = (Put v (fst $ next f' s), s)
Get f') s = (Get $ \x -> let (f'',s') = next (f' (x,s')) s
next (in f'', s)
instance ArrowCircuit SP where
= Put v id delay v
Exercise 3.5-1
halfAdd :: Arrow arr => arr (Bool,Bool) (Bool, Bool)
= arr $ \(x,y) -> (x&&y, x/=y)
halfAdd
fullAdd :: Arrow arr => arr (Bool, Bool, Bool) (Bool, Bool)
= proc (x,y,c) -> do
fullAdd <- halfAdd -< (x,y)
(c1,s1) <- halfAdd -< (s1, c)
(c2,s2) -< (c1||c2,s2) returnA
It is instructive to see the desugared version:
fullAddNoSugar :: Arrow arr => arr (Bool, Bool, Bool) (Bool, Bool)
= arr (\(x,y,c) -> ((x,y), c))
fullAddNoSugar >>> 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)
= proc (x, y) -> more m -< (x,y,False)
adder m where more 0 = proc (_,_,cout) -> returnA -< ([],cout)
= proc (x':xs,y':ys,cin) -> do
more n <- fullAdd -< (x',y',cin)
(carry,b) <- more (n-1) -< (xs,ys,carry)
(z,cout) -< (b:z, cout) returnA
Exercise 3.5-2
bsadd :: ArrowCircuit arr => arr (Bool,Bool) Bool
= proc (x,y) -> do
bsadd <- fullAdd -< (x,y,cin)
rec (cout,b) <- delay False -< cout
cin -< b returnA
Exercise 3.5-3
(a)
filterA' :: ArrowChoice arr => arr a Bool -> arr [a] [a]
= proc xs ->
filterA' p case xs of
-> returnA -< []
[] :xs' -> do c <- p -< x
x<- filterA' p -< xs'
l 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]
= proc (env, xs) ->
filterC p case xs of
-> returnA -< []
[] :xs' -> do c <- p -< (env,x)
x<- filterC p -< (env,xs')
l 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)
0 _ = proc (_,(a,[])) -> returnA -< ([],a)
rowC = proc (env,(a,b:bs)) -> do
rowC n f <- f -< (env,(a,b))
(c,a') <- rowC (n-1) f -< (env, (a',bs))
(cs,a'') -< (c:cs,a'') returnA
(b)
counter1bit :: ArrowCircuit arr => arr Bool (Bool,Bool)
= proc b -> do
counter1bit <- delay False -< sum
rec sumin sum) <- halfAdd -< (sumin,b)
(carry,-< (sum, carry) returnA
(c)
counternbit :: ArrowCircuit arr => Int -> arr Bool ([Bool],Bool)
= proc bs ->
counternbit n |(rowC n) (\(b,_) -> counter1bit -< b)|) (bs, [0..n-1]) (
(d)
counter1bit' :: ArrowCircuit arr => arr (Bool,Bool) (Bool,Bool)
= proc (b,rst) -> do
counter1bit' <- delay False -< sum
rec sumin sum) <- halfAdd -< (not rst && sumin,not rst && b)
(carry,-< (sum, carry)
returnA
counternbit' :: ArrowCircuit arr => Int -> arr (Bool,Bool) ([Bool],Bool)
= proc (bs,rsts) ->
counternbit' n |(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
<- nor -< (a,a)
a' <- nor -< (b,b)
b' <<< nor -< (a',b')
nubA
or :: Monad m => Sim m (Bool,Bool) Bool
or = proc (a,b) -> do
<- nor -< (a,b)
a' <- nor -< (a,b)
b' <<< nor -< (a',b')
nubA
lnot :: Monad m => Sim m Bool Bool
= nubA <<< nor <<< id &&& id
lnot
xor :: Monad m => Sim m (Bool, Bool) Bool
= proc (a,b) -> do
xor <- and -< (a,b)
c <- nor -< (a,b)
d <<< nor -< (c,d)
nubA
halfAddSim :: Monad m => Sim m (Bool,Bool) (Bool,Bool)
= proc (x,y) -> do
halfAddSim <- and -< (x,y)
c <- xor -< (x,y)
s -< (c,s) nubA
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)
= proc (x,y,c) -> do
fullAdd' <- halfAddSim -< (x,y)
(c1,s1) <- halfAddSim -< (s1,c)
(c2,s2) <- or -< (c1,c2)
c' -< (c',s2)
nubA
bsadd' :: MonadFix m => Sim m (Bool,Bool) Bool
= proc (x,y) -> do
bsadd' <- (|afix (\ ~(_,cin) -> do
(b,_) <- initially False nubA -< cin
cin' <- fullAdd' -< (x,y,cin')
(cout,b) -< (b,cout))|)
returnA -< b
returnA
counter1bit'' :: Sim IO (Bool,Bool) (Bool,Bool)
= proc (b,rst) ->
counter1bit'' |afix (\ ~(sumin,_) -> do
(<- initially False nubA -< sumin
sumin' <- and <<< first lnot <<< delay1 0.2 -< (rst,sumin')
x <- and <<< first lnot <<< nubA -< (rst,b)
y <- halfAddSim -< (x,y)
(c,s) -< (s,c))|)
returnA
counternbit'' :: Int -> Sim IO (Bool,Bool) ([Bool],Bool)
= proc (bs,rsts) ->
counternbit'' n |(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
Sim f) = sim left'
left (where left' (Left b) = do (c,s) <- f b
return (Left c,leftState b s)
Right d) = return (Right d, untilLeft f)
left' (
untilLeft :: Monad m => (b -> m (c, State m b c))
-> (State m (Either b d) (Either c d))
= Wait infinity undefined $ \e ->
untilLeft f case value e of
Right d -> Ready (Event (time e) (Right d)) (untilLeft f)
Left b -> Lift $ do
<- f b
(c,s) 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)
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 ->
leftState b (case value e of
Left b' -> leftState b' $ k e { value = b' }
Right d -> Ready (Event (time e) (Right d))
$ k e { value = b })
(leftState b
maybeneg :: Monad m => Sim m (Bool,Bool) Bool
= proc (x,neg) -> if neg then returnA -< not x
maybeneg else returnA -< x