Note: If you haven't stumbled upon asynchronous circuits yet, reading my two other Stack Overflow posts (besides John Hughes' article where he wrote them down) on them could come in quite handy: "An ArrowCircuit instance for stream processors which could block", "Hughes' Fibonacci stream".
John Hughes has come up with the following type for asynchronous circuits in his well-known "Generalising Monads to Arrows":
data StreamProcessor a b = Get (a -> StreamProcessor a b) |
Put b (StreamProcessor a b) |
Halt
instance Category StreamProcessor where
id = Get (\ x -> Put x id)
Put c bc . ab = Put c (bc . ab) {- 1 -}
Get bbc . Put b ab = (bbc b) . ab {- 2 -}
Get bbc . Get aab = Get $ \ a -> (Get bbc) . (aab a) {- 3 -}
Get bbc . Halt = Halt {- 4 -}
Halt . ab = Halt {- 5 -}
bypass :: [b] -> [d] -> StreamProcessor b c -> StreamProcessor (b, d) (c, d)
bypass [] ds (Get f) = Get $ \ ~(b, d) -> bypass [] (ds ++ [d]) (f b)
bypass (b : bs) [] (Get f) = bypass bs [] (f b)
bypass [] (d : ds) (Put c sp) = Put (c, d) $ bypass [] ds sp
bypass bs [] (Put c sp) =
Get $ \ ~(b, d) -> Put (c, d) (bypass (bs ++ [b]) [] sp)
bypass bs ds Halt = Halt
instance Arrow StreamProcessor where
arr f = Get $ \ a -> Put (f a) (arr f)
first = bypass [] []
instance ArrowChoice StreamProcessor where
left (Put b ab) = Put (Left b) (left ab)
left (Get aab) = Get $ \ a -> case a of
Left a' -> (left . aab) a'
Right d -> Put (Right d) (left $ Get aab)
left Halt = Halt
In his other paper ("Programming with arrows") he wrote down a combinator which works for arrows (well, ArrowChoice
s) like mapM
for Monads
:
biteOff :: [a] -> Either [a] (a, [a])
biteOff [] = Left []
biteOff (x : xs) = Right (x, xs)
mapA :: ArrowChoice k => k a b -> k [a] [b]
mapA k = let
go :: ArrowChoice k => k a b -> k (a, [a]) [b]
go k = k *** mapA k >>^ uncurry (:)
in arr biteOff >>> (arr (const []) ||| go k)
Long story short, it does not work on StreamProcessor
s:
GHCi> Put x sp = Put [1, 2, 3] Halt >>> mapA id
GHCi> x
*** Exception: stack overflow
GHCi> Put x sp = Put [] Halt >>> mapA id
GHCi> x
*** Exception: stack overflow
Interestingly enough, it is not just the value that can't decide, but also the constructor itself:
GHCi> Get f = Put [1, 2, 3] Halt >>> mapA id
GHCi> Put x sp = f ()
GHCi> x
*** Exception: stack overflow
So, here is how I understand whatever's happening:
StreamProcessor
has pretty cumbersome rules for composition. To compose two arrows, we often have to know both constructors. Therefore, when we stumble into an infinite series with composition, we just have to hope that the {- 1 -}
st and the {- 5 -}
th rule for (.)
only would be working. We aren't quite lucky:
mapA
=arr biteOff >>> (arr (const []) ||| go k)
.arr biteOff >>> (arr (const []) ||| go k)
=(arr (const []) ||| go k) . arr biteOff
.arr (const []) ||| go k
=left (arr (const [])) >>> arr mirror >>> left (go k) >>> arr mirror >>> arr untag
. See(|||)
,mirror
anduntag
here.>>>
isinfixr
. Hence:left (arr (const [])) >>> (arr mirror >>> (left (go k) >>> (arr mirror >>> arr untag)))
.arr mirror >>> arr untag
is aGet
, since both of them areGet
s (rule{- 3 -}
for(.)
).left (go k) >>> (arr mirror >>> arr untag)
=(arr mirror >>> arr untag) . left (go k)
=Get ... . left (go k)
. Rules from{- 2 -}
through{- 4 -}
work here. As one can see, we need to pattern-matchleft (go k)
now. A glance at theleft
tells us that we need to pattern-matchgo k
.go k
=k *** mapA k >>^ uncurry (:)
.k *** mapA k >>^ uncurry (:)
=first k >>> arr swap >>> first (mapA k) >>> arr swap >>> arr (uncurry (:))
.first k >>> arr swap >>> first (mapA k) >>> arr swap >>> arr (uncurry (:))
=... . (first (mapA k) . ...)
.first (mapA k)
turned out to be the first argument of some right-to-left composition (since.
isinfixr
), therefore it needs to be pattern-matched. A quick glance atfirst
shows that we need to pattern-matchmapA k
.Start over.
So, since this didn't quite work out, I thought about writing down some mapSP
for StreamProcessor
s only. Yet, it doesn't seem to be a great idea now:
Say, we applied mapSP
to some sp
. A list [a1, a2]
is input. sp
blocks on a1
and goes on with a2
. I see no way to deal with this.
So, is my understanding as to why generic mapA
does not work for StreamProcessor
s right? Is there some mapSP :: StreamProcessor a b -> StreamProcessor [a] [b]
which would add up, not just type check?