Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How does mapA work with a Stream Function Arrow in Haskell?

Tags:

haskell

arrows

Background

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.

My Understanding

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:

Diagram of Control Flow of mapA (delay 0)

Where the numbers label parts of the process:

  1. For any non-empty list x, listcase will emit Right(x, xs). For the empty list, listcase will emit Left(), the terminal case.
  2. Values tagged Right will be passed to the lower portion. Values tagged Left will be passed to const[], which essentially stops the iteration.
  3. With input (x, xs), x will be passed to (delay 0), while xs will be passed back through to listcase.
  4. The output of 3 will be (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

    1. Right ([1,2,3],[[4,5,6],[7,8,9]])
    2. ([1,2,3], [[4,5,6],[7,8,9]]) gets passed to the lower portion
    3. (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

    1. Right ([4,5,6], [[7,8,9]])
    2. ([4,5,6], [[7,8,9]]) gets passed to the lower portion
    3. (delay 0) is called on [4,5,6], resulting in [0,4,5]. [[7,8,9]] is passed back to listcase
  • Third pass

    1. Right ([7,8,9], [])
    2. ([7,8,9], []) gets passed to the lower portion
    3. (delay 0) is called on [7,8,9], resulting in [0,7,8]. [] is passed back to listcase.
  • Fourth pass

    1. 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]].

My Confusion

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]]?

like image 906
aftertommy Avatar asked Apr 13 '14 20:04

aftertommy


1 Answers

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

  1. Piping the input through the 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.
  2. So, we have that [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 [[], []].
  3. 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.

like image 176
sabauma Avatar answered Oct 26 '22 21:10

sabauma