I've been going through John Hughes' Programming with Arrows, and I felt that I had everything straight in my head until the following example of using mapA:
>runSF (mapA (delay 0)) [[1,2,3],[4,5,6],[7,8,9]]
[[0,0,0],[1,2,3],[4,5,6]]
Where runSF extracts the stream function from the StreamFunction arrow defined as:
newtype SF a b = SF {runSF :: [a]->[b]}
And delay is defined as:
delay x = SF (init . (x:))
SF is an instance of ArrowChoice (which declares mapA) and thus an instance of Arrow.
mapA :: arr a b -> arr [a] [b]
delay :: SF a b
such that delay
simply prepends its second argument with its first.
Thus, mapA (delay 0)
should return us an SF arrow that takes [[a]]
and returns [[b]]
mapA (delay 0) :: SF [[a]] [[b]]
I would expect that the "circuit" that this would result in is:
Where the numbers label parts of the process:
list x
, listcase
will emit Right(x, xs)
. For the empty list, listcase
will emit Left()
, the terminal case.Right
will be passed to the lower portion. Values tagged Left
will be passed to const[]
, which essentially stops the iteration.(x, xs)
, x
will be passed to (delay 0)
, while xs
will be passed back through to listcase
.(z, zs)
, which gets passed to uncurry (:)
, which joins the tuple back into the list.Here’s my understanding of the flow, with input [[1,2,3],[4,5,6],[7,8,9]]
:
First pass
Right ([1,2,3],[[4,5,6],[7,8,9]])
([1,2,3], [[4,5,6],[7,8,9]])
gets passed to the lower portion(delay 0)
is called on [1,2,3]
, resulting in [0,1,2]
. [[4,5,6],[7,8,9]]
is passed back to listcase
Second pass
Right ([4,5,6], [[7,8,9]])
([4,5,6], [[7,8,9]])
gets passed to the lower portion(delay 0)
is called on [4,5,6]
, resulting in [0,4,5]
. [[7,8,9]]
is passed back to listcase
Third pass
Right ([7,8,9], [])
([7,8,9], [])
gets passed to the lower portion(delay 0)
is called on [7,8,9]
, resulting in [0,7,8]
. []
is passed back to listcase
.Fourth pass
Left ()
, dropped on the floor.At this point, we get to part 4, which takes the output of 3 and concats it all together. We essentially build of an operation of:
[0,1,2] : [[0,4,5] : [[0,7,8] : []]]
Which would give us [[0,1,2],[0,4,5],[0,7,8]]
.
Clearly, my above flow is wrong.
How does calling runSF (mapA (delay 0)) [[1,2,3],[4,5,6],[7,8,9]]
result in [[0,0,0],[1,2,3],[4,5,6]]
?
I find these examples tricky to reason about. There are two lists in this example, the outer list is the stream that your arrow operates over, while the inner lists are what mapA maps over. Consider a simpler example so we can ignore the recursive case for now. Given
runSF (mapA (delay 0)) [[1], [2]]
We see that the first step
listcase
arrow gives us the output
[Right (1, []), Right (2, [])]
. The first elements of each pair
are fed to the delay 0
arrow, while the second elements are fed
back to mapA f
.[1, 2] => delay 0
and [[], []] => mapA f
.
Feeding [1,2]
into delay 0
gives the result [0, 1]
, and
feeding the empty lists to mapA f
yields more empty lists
[[], []]
.Those two results are fed to arr (uncurry (:))
, which acts like zipWith (:)
,
since these functions are all mapped over lists, so it joins the two input
lists element-wise.
[0, 1]
|
v
arr (uncurry (:)) => [ 0:[], 1:[] ] == [[0], [1]]
^
|
[[], []]
The key is to recognize that all the stuff constructed with arr
operates on
the inner set of lists, so running your initial input through arr listcase
does not produce Right ([1,2,3],[[4,5,6],[7,8,9]])
, but
[Right (1, [2, 3]), Right (4, [5,6]), Right (7, [8,9])]
.
This is my attempt to draw it out in a diagram.
[Right (1, [2, 3]), Right (4, [5,6]), Right (7, [8,9])]
=======================================================
| [1, 4, 7] +-----------+ [0, 1, 4]
+----------+ | +----=--------->| delay |-----=------|
| listcase |---=------>| +-----------+ | +-------------------+
+----------+ | +-->| arr (uncurry (:)) |---> [[0,0,0],[1,2,3],[4,5,6]]
| | +-------------------+
| +-----------+ |
+-------=------>| mapA f |------=-----|
| +-----------+ |
| |
[[2,3],[4,5],[6,7]] [[0,0], [2,3],[4,5]]
* what will be
returned if you
trace it through
Sorry that I can't draw this better. In effect, mapA
gives a transposed view
of the input list, so you can think of mapA (delay 0)
as operating like
transpose . map (init . (0:)) . transpose
, since init . (0:)
is the
definition of delay
.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With