Search code examples
haskellrecursionfoldcombinators

Since "fold" isn't powerful enough to write a tree pretty-printer with indentation, what high-order combinator is?


Given, for example, the following tree data type:

data Tree a = Node [Tree a] | Leaf a deriving Show
type Sexp = Tree String

How do I express a "pretty" function using an high-order combinator, that prints the tree with proper indentation? For example:

sexp = 
    Node [
        Leaf "aaa", 
        Leaf "bbb",
        Node [
            Leaf "ccc",
            Leaf "ddd",
            Node [
                Leaf "eee",
                Leaf "fff"],
            Leaf "ggg",
            Leaf "hhh"],
        Leaf "jjj",
        Leaf "kkk"]
pretty = ????
main = print $ pretty sexp

I want the result of that program to be:

(aaa 
   bbb 
   (ccc 
       ddd 
       (eee 
           fff) 
       ggg 
       hhh) 
   jjj 
   kkk) 

Here is an incomplete solution, using a "fold" as the combinator, that doesn't implement the indentation:

fold f g (Node children) = f (map (fold f g) children)
fold f g (Leaf terminal) = g terminal
pretty = fold (\ x -> "(" ++ (foldr1 ((++) . (++ " ")) x) ++ ")") show
main = putStrLn $ pretty sexp

It is obviously not possible to write the function I want using fold, since it forgets the tree structure. So, what is a proper high-order combinator that is generic enough to allow me to write the function I want, but less powerful than writing a direct recursive function?


Solution

  • fold is strong enough; the trick is that we'll need to instantiate r as a reader monad of the current indentation level.

    fold :: ([r] -> r) -> (a -> r) -> (Tree a -> r)
    fold node leaf (Node children) = node (map (fold node leaf) children)
    fold node leaf (Leaf terminal) = leaf terminal
    
    pretty :: forall a . Show a => Tree a -> String
    pretty tree = fold node leaf tree 0 where
    
      node :: [Int -> String] -> Int -> String
      node children level = 
        let childLines = map ($ level + 1) children
        in unlines ([indent level "Node ["] ++ childLines ++ [indent level "]"])
    
      leaf :: a -> Int -> String
      leaf a level = indent level (show a)
    
      indent :: Int -> String -> String -- two space indentation
      indent n s = replicate (2 * n) ' ' ++ s
    

    Take careful note that I pass an extra parameter to the call to fold. This is the initial state of indentation and it works because with this specialization of r, fold returns a function.