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?
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.
It's simply
onLast f xs = init xs ++ [f (last xs)]
pretty :: Sexp -> String
pretty = unlines . fold (node . concat) (:[]) where
node [] = [""]
node (x:xs) = ('(' : x) : map (" " ++) (onLast (++ ")") xs)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With