I’m getting used to Haskell’s higher-order functions. Usually I can replace explicit patterns of recursion with functions like map, fold, and scan. However, I often run into the following recursion pattern which I don’t understand how to express using higher-order functions:
f (x:[]) = k x
f (x:xs) = g x (f xs)
For instance, suppose I am representing analytic tableaux. Then I create a data type such as:
data Tableau = N Expr | S Expr (Tableau) | B Expr (Tableau) (Tableau)
If I want to convert a list of Exprs into a tableau structure, I want a function part of which might resemble:
f (x:[]) = N x
f (x:xs) = S x (f xs)
Now, I see three options: (1) create a function which decides, given a tableau and a list, whether the next branch in the tableau should be an S or N (or B, but we’ll ignore that case); (2) use a higher-order function to encapsulate the recursion pattern of f; (3) use a function like f.
What would the best option be?
I’d most probably use the following:
It basically means that the end of the list is replaced by
k xwhen folding. Thanks to lazy evaluation present everywhere, it works even for infinite lists.There are two other solutions – adding empty case and using Maybe.
A) adding empty case:
It would be best if
f []was well-defined. Then, you could write the definition aswhich is
f = foldr g c. For example, if you changeto
then you can represent single-element tableaux as
S expr N, and the function is defined as one-linerIt’s the best solution as long the empty case makes sense.
B) use Maybe:
On the other hand, if
f []cannot be sensibly defined, it’s worse.Partial functions are often considered ugly. To make it total, you can use
Maybe. DefineIt is a total function – that’s better.
But now you can rewrite the function into:
which is a right fold:
In general, folding is idiomatic and should be used when you fit the recursion pattern. Otherwise use explicit recursion or try to reuse existing combinators. If there’s a new pattern, make a combinator, but only if you’ll use the pattern a lot – otherwise it’s overkill. In this case, the pattern is fold for nonempty lists defined by:
data List a = End a | Cons a (List a).