Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Avoid pattern matching in recursion

Consider this code which I used to solve Euler Problem 58:

diagNums = go skips 2
    where go (s:skips) x = let x' = x+s
                           in x':go skips (x'+1)

squareDiagDeltas = go diagNums
    where go xs = let (h,r) = splitAt 4 xs
                  in h:go r

I don't like the pattern matching in the second function. It looks more complicated than necessary! This is something that arises pretty frequently for me. Here, splitAt returns a tuple, so I have to destructure it first before I can recurse. The same pattern arises perhaps even more annoyingly when my recursion itself returns a tuple I want to modify. Consider:

f n = go [1..n]
    where go [] = (0,0)
          go (x:xs) = let (y,z) = go xs
                      in (y+x, z-x)

compared to the nice and simple recursion:

f n = go [1..n]
    where go [] = 0
          go (x:xs) = x+go xs

Of course the functions here are pure nonsense and could be written in a wholly different and better way. But my point is that the need for pattern matching arises every time I need to thread more than one value back through the recursion.

Are there any ways to avoid this, perhaps by using Applicative or anything similar? Or would you consider this style idiomatic?

like image 547
firefrorefiddle Avatar asked Dec 08 '22 15:12

firefrorefiddle


2 Answers

First of all, that style is actually rather idiomatic. Since you're doing two things to two different values, there is some irreducible complexity; the actual pattern match does not introduce much on its own. Besides, I personally find the explicit style very readable most of the time.

However, there is an alternative. Control.Arrow has a bunch of functions for working with tuples. Since the function arrow -> is an Arrow as well, all these work for normal functions.

So you could rewrite your second example using (***) to combine two functions to work over tuples. This operator has the following type:

(***) :: a b c -> a b' c' -> a (b, b') (c, c')

If we replace a with ->, we get:

(***) :: (b -> c) -> (b' -> c') -> ((b, b') -> (c, c'))

So you could combine (+ x) and (- x) into a single function with (+ x) *** (- x). This would be equivalent to:

\ (a, b) -> (a + x, b - x)

Then you could use it in your recursion. Unfortunately, the - operator is stupid and doesn't work in sections, so you would have to write it with a lambda:

(+ x) *** (\ a -> a - x) $ go xs 

You can obviously imagine using any other operator, all of which aren't quite as stupid :).

Honestly, I think this version is less readable than the original. However, in other cases, the *** version can be more readable, so it's useful to know about it. In particular, if you were passing (+ x) *** (- x) into a higher-order function instead of applying it immediately, I think the *** version would be better than an explicit lambda.

like image 89
Tikhon Jelvis Avatar answered Dec 11 '22 05:12

Tikhon Jelvis


I agree with Tikhon Jelvis that there is nothing wrong with your version. Like he said, using combinators from Control.Arrow can be useful with higher order functions. You can write f using a fold:

f n = foldr (\x -> (+ x) *** subtract x) (0,0) [1..n]

And if you really want to get rid of the let in squareDiagDeltas (I'm not sure I would), you can use second, because you are only modifying the second element of the tuple:

squareDiagDeltas = go diagNums
  where go = uncurry (:) . second go . splitAt 4
like image 44
raymonad Avatar answered Dec 11 '22 03:12

raymonad