Passing an algebra to the parser
7.5 Passing an algebra to the parser
The previous section shows how to implement a parser with an implicit algebra. Since this approach fails when we want to define different parsers with the same result type, we make the algebras explicit. Thus we obtain the following definition of parens:
parens
:: ParenthesesAlgebra a -> Parser Char a
parens (match,empty) = par where
par = (\_ b _ d -> match b d) <>
open <> par <> close <> par <|> succeed empty
Note that it is now easy to define different parsers with the same result type:
nesting, breadth :: Parser Char Int nesting
= parens (\b d -> max (1+b) d,0)
breadth
= parens (\b d -> d+1,0)
Chapter 8
Programming with higher-order folds
introduction
In the previous chapters we have seen that algebras play an important role when describing the meaning of a recognised structure (a parse tree). For each recursive datatype T we have a function foldT, and for each constructor of the datatype we have a corresponding function as a component in the algebra. Chapter 6 introduces
a language in which local declarations are permitted. Evaluating expressions in this language can be done by choosing an appropriate algebra. The domain of that algebra is a higher order (data)type (a (data)type that contains functions). Unfortunately, the resulting code comes as a surprise to many. In this chapter we will illustrate a related formalism, which will make it easier to construct such involved algebras. This related formalism is the attribute grammar formalism. We will not formally define attribute grammars, but instead illustrate the formalism with some examples, and give an informal definition.
We start with developing a somewhat unconventional way of looking at functional programs, and especially those programs that use functions that recursively descend over datatypes a lot. In our case one may think about these datatypes as abstract syntax trees. When computing a property of such a recursive object (for example, a program) we define two sets of functions: one set that describes how to recursively visit the nodes of the tree, and one set of functions (an algebra) that describes what to compute at each node when visited.
One of the most important steps in this process is deciding what the carrier type of the algebras is going to be. Once this step has been taken, these types are
a guideline for further design steps. We will see that such carrier types may be functions themselves, and that deciding on the type of such functions may not always
be simple. In this chapter we will present a view on recursive computations that will enable us to “design” the carrier type in an incremental way. We will do so by constructing algebras out of other algebras. In this way we define the meaning of a language in a semantically compositional way.
We will start with the rep min example, which looks a bit artificial, and deals with
a non-interesting, highly specific problem. However, it has been chosen for its sim- plicity, and to not distract our attention to specific, programming language related, semantic issues. The second example of this chapter demonstrates the techniques on a larger example: a small compiler for part of a programming language.
Programming with higher-order folds
data Tree = Leaf Int
| Bin Tree Tree deriving Show
type TreeAlgebra a = (Int -> a, a -> a -> a) foldTree :: TreeAlgebra a -> Tree -> a
foldTree alg(leaf, _ ) (Leaf i)
= leaf i
foldTree alg(_
, bin) (Bin l r) = bin (foldTree alg l)
(foldTree alg r)
Listing 9: rm.start.hs
goals
In this chapter you will learn:
• how to write ‘circular’ functional programs, or ‘higher-order folds’; • how to combine algebras; • (informally) the concept of an attribute grammar.