I’m currently reading through the paper Programming with Arrows by John Hughes
and I’m already stumped on the first exercise, in section 2.5, on pg 20.
We have the Arrow and ArrowChoice typeclasses at our disposal, as well as instances for functions, stream functions [a] -> [b], and monadic functions a -> m b via the Kleisli type.
A mapA example was given:
mapA f = arr listcase >>>
arr (const []) ||| (f *** mapA >>> arr (uncurry (:)))
Here’s an attempt:
listcase :: [a] -> (Either () (a,[a]))
listcase [] = Left ()
listcase (x:xs) = Right (x,xs)
helper :: (Bool,a) -> [a] -> Either (a,[a]) [a]
helper (True,x) y = Left (x,y)
helper (False,x) y = Right y
test :: Arrow a => (b -> Bool) -> a (b,c) ((Bool,b), c)
test p = first (arr p &&& arr id)
filterA :: Arrow a => (b -> Bool) -> a [b] [c]
filterA p = f >>> (g ||| (h >>> (j ||| (filterA p))))
where f = arr listcase
g = arr (const [])
h = test p >>> (uncurry helper)
j = (arr id *** (filterA p)) >>> (arr (uncurry (:)))
The (bruteforce) rationale behind this futile attempt is as follows:
There are two choices to be made with filterA: the listcase like map, and the result of applying the predicate p. It starts off like map, checking the list and returning an Either value using listcase. In the case of an empty list g is applied, otherwise everything to the right of ||| is applied to a value of type (a,[a]), containing the head and tail respectively. The h function is applied first, which applies the predicate while keeping the head, returning a value of type ((Bool, head),tail). This is passed to (uncurry helper), which decides whether to keep the head or not depending on the Bool value. It returns the result as an Either value so that we may apply the choice method (|||) to it. This value is passed to the next choice: (j ||| (filterA p)) such that if the predicate held True then j is applied to a pair containing the head and tail. The head is filtered through using id while the filter p is applied to the tail. Both results are returned as a pair. This pair is then reconciled using arr (uncurry (:)) like map. Otherwise the tail is passed to filterA p alone.
I doubt it’s as difficult as I’m making it out to be, I assume I’m missing something quite obvious.
Sorry I’m not quite following your logic, but let’s see what the non-arrow code does. It
xyspis true on the head, then we appendxtoys.ysThe
listcasefunction [to implement the first 2 tasks] looks good, though remember you’re returning a list, so might as well return that instead ofunitand remapping throughconst [].You have the recursion code for the third bullet buried in the two last cases, whereas I expose it directly, but that’s okay.
For the last merging, you write it with a
|||, but since you don’t need to compose any other arrows in your target category, you might as well just lift a function to do all of the work. In my code below, that’srejoin.Certainly it takes time to express your ideas clearly with the likes of
***,&&&,|||,first, etc.A little critique.
p, then you might as well declarefilterA = arr filter. You really want to take a lifted arrowp. That said, the change would be to simply typepinstead ofarr p, so your code has the right idea.(uncurry helper)is not something in the arrow space, it’s just a raw function.When developing this stuff, I usually write a skeleton, and declare the types. That helps me figure out what’s going on. For example, I started with
When you add
fainto thefilterRestdeclaration however, you need to tell it thatarrforfilterRestis the same as the one forfilterA(type variable scoping), so use theforall arr a.as above.