© Alejandro Serrano Mena 2019
Alejandro Serrano MenaPractical Haskellhttps://doi.org/10.1007/978-1-4842-4480-7_14

14. Interpreting Offers with Attributes

Alejandro Serrano Mena1 
(1)
Utrecht, The Netherlands
 

This chapter continues the work of the previous one in creating a DSL for expressing offers for the Time Machine Store. As you may remember, deep embedding was chosen as the way to model the offers language in Haskell. A deeply embedded language is divided between its syntax, expressed as a set of Haskell data types, and its interpretation, which is responsible for assigning a meaning to each value in the language. In this example, the interpretation of an offer could be a function that, given a list of products and prices, applies the offers and discounts and returns the new price.

In principle, the interpretations could be defined as plain Haskell functions over the DSL values. However, almost every interpretation you can give follows a set of patterns. Thus, it’s interesting to consider those patterns and build a conceptual model that better defines the interpretations. This is what attribute grammars do for you. Then, you can use a tool to generate the Haskell code that corresponds to an attribute grammar, saving you from having to write a lot of boilerplate code. In particular, this chapter will devote most of its time to the topic of programming with the Utrecht University Attribute Grammar Compiler (UUAGC).

In addition to plain programming, in this chapter you will also consider the relations between attribute grammars and other Haskell concepts, such as monads. You will revisit the ideas of origami programming from Chapter 3 and will apply them to general data types (not only lists). Finally, I shall introduce the idea of data type-generic programming, which allows us to create algorithms which work on many different data types in a uniform manner.

Interpretations and Attribute Grammars

In this section, you’ll learn about attribute grammars. To help, a simple language is given an interpretation in Haskell. That language is then reworked using attributes.

A Simple Interpretation

Let’s consider a simple language that contains only three combinators for building its expressions. Say you have a basic AmountOf with a character as a parameter and then the addition and product of two expressions. Let’s express the language in a simple Haskell data type.
data Expr = Plus  Expr Expr
          | Times Expr Expr
          | AmountOf Char
The desired interpretation of this data type is parameterized by a string and gives a number as a result. When AmountOf is found, the number of characters of a given type is counted. The other two combinators just perform the sum or multiplication of the numbers in their subexpressions. The corresponding Haskell code for this interpretation is straightforward to write.
meaning :: Expr -> [Char] -> Int
meaning (Plus  l r)  p = meaning l p + meaning r p
meaning (Times l r)  p = meaning l p * meaning r p
meaning (AmountOf c) p = length $ filter (== c) p

Introducing Attribute Grammars

The previous section’s interpretation is quite simple. Still, it contains a lot of boilerplate code. The recursive nature of this definition is explicit by calling meaning on the subexpressions, and the p list must be explicitly threaded throughout the entire function. Attribute grammars provide a higher-level model for writing interpretations (and, as you will see later, many other kinds of functions), focusing on the data being used and produced, saving you from thinking about all the small details related to information flow. As a result, your code becomes much more maintainable.

In addition, attribute grammars contribute to the modularity of the code. You can separate your interpretation into several parts (and even in several files) and then ask the attribute grammar system to join them together to compute all the information you need from your data type in the smallest number of passes.

The main disadvantage of attribute grammars is that they have a different computational model than plain Haskell, even though the integration is tight. As you will see in this chapter, it’s easy to understand attribute grammars in Haskell terms, so this disadvantage shouldn’t block you from using them.

Let’s return to the expression example. If instead of plain Haskell you were using attribute grammars, you would be thinking about this interpretation in different terms. From the attribute grammar point of view, your data type is a tree whose nodes keep some attributes within them. In this example, the attributes would be the string that you’re exploring and the result of the interpretation.

When looking closer, you can see that those pieces of information are different. On one hand, the string to explore is passed from each expression to its subexpressions as a parameter. You say that this attribute is flowing top-down from the expression. On the other hand, the numeric results at each point in the expression are built considering the results of its subexpressions and combining them in some way. Thus, the flow is bottom-up in this case. Figure 14-1 shows the flow of information.
../images/316945_2_En_14_Chapter/316945_2_En_14_Fig1_HTML.jpg
Figure 14-1

Tree and attributes for AmountOf 'a' `Plus` (AmountOf 'b' `Times` AmountOf 'c')

In the attribute grammar jargon, the numeric result is a synthesized attribute, whereas the string to explore is an inherited attribute . The names roughly reflect the different flow direction, which is the important difference between both. To define this interpretation as an attribute grammar, you would need the following three pieces of information, as the example shows:
  1. 1.

    Which are the possible nodes in your tree. This is done via the definition of a set of data types in which every constructor represents a kind of node.

     
  2. 2.

    The attributes to be computed and whether they are inherited or synthesized.

     
  3. 3.

    How to pass the information from parent to children nodes in the case of the inherited attributes and how to compute the value of a synthesized attributes from their children.

     
The purpose of an attribute grammar system is to take all that information and generate the necessary code for computing the final value of the attributes at each node. Here you can see the main advantage of using an attribute grammar vs. writing the interpretation directly in Haskell, because the attribute grammar system would take care of the following automatically:
  • When an attribute needs to be transported in a more complex fashion than just top-down or bottom-up (e.g., between sibling nodes), the Haskell code can become quite complex. But with an attribute grammar, this behavior is expressed declaratively, and the system takes care of keeping track of it.

  • Sometimes the value of an attribute depends on another one. This means attributes must be ordered in some way for the whole computation to proceed, maybe doing more than one traversal of the same tree. Attribute grammar systems perform this ordering automatically (and in many cases, in an optimal way), so you don’t need to think about it.

Hackage includes several implementations of attribute grammars in its package repository. One option is to use Happy, the parser generator included in the Haskell Platform and that has support for attributes in its grammars. Happy is an external tool that preprocesses a grammar description and generates the Haskell code needed for parsing it.

AspectAG is a library for embedding attribute grammars inside Haskell. So, you don’t need an extra step of building prior to compilation. It’s an interesting option for working with attribute grammars inside Haskell but has the downside of needing a lot of type-level programming to make it work.

The tool that will be presented in this chapter is the Utrecht University Attribute Grammar Compiler (UUAGC). Like with Happy, it’s a preprocessor that generates Haskell code from the description of the grammar. In contrast to Happy, UUAGC is focused specifically on defining attribute grammars. Thus, the connection between the attribute grammar formalism and UUAGC code is much more explicit in this case, making it a better choice for learning about these grammars.

Installing UUAGC

UUAGC is available in Hackage , so you just need to execute the usual command to get it on your system.
$ cabal install uuagc

By following these instructions, you also install integration with Cabal, which is found in the uuagc-cabal package. You’ll be using uuagc-cabal to build the attribute grammars in the upcoming sections.

Your First Attribute Grammar

Now let’s create the attribute grammar corresponding to the simple numeric expressions introduced in the previous section. For the source file, you have two conventions to follow: UUAGC source files end with .ag, and files must be found inside a folder structure that follows the module name. The conventions follow Haskell’s. In this case, the module will be Chapter14.Simple, so the file should be found in src/Chapter14/Simple.ag.

Synthesizing the Result

In the previous section, the three pieces of information required to define an attribute grammar were presented. When programming with UUAGC, each of these pieces is declared separately. The first one is the declaration of the data type that attributes will refer to. The syntax is quite similar to Haskell’s data, but it has two differences.
  1. 1.

    No equal sign should appear after the name of the data type. Instead, each constructor declaration must begin with a vertical bar: |.

     
  2. 2.

    All the arguments to a constructor must be named, as happens with records in Haskell. However, no brackets should be used around the fields.

     
Furthermore, when using type variables or compound types, they must be placed inside { and }. This tells UUAGC to parse the content of the brackets as regular Haskell code instead of as an attribute grammar. Taking into account all these differences, the data type for expressions looks like the following:
data Expr
  | Plus  left :: Expr right :: Expr
  | Times left :: Expr right :: Expr
  | AmountOf c :: Char

Note

UUAGC supports two kinds of syntax. The one used in the examples of this chapter is dubbed “Haskell syntax” because of its resemblance to that language. The “classical syntax” is quite different from Haskell’s; you can spot it in UUAGC code because all the keywords are written in capitals.

The next step is to declare the attributes that each node will have. In UUAGC, all nodes representing constructors of the same data type contain the same attributes. The syntax for specifying this is the attr keyword, followed by the names of the data types you want to add the attributes to. Then, you must include a line per attribute, which starts with inh or syn, depending on whether you want to create an inherited or synthesized attribute, respectively, the name of the attribute and its type. The attributes in this example have a String flowing top-down and a numeric result being synthesized:
attr Expr
  inh string :: String
  syn result :: Int
Finally, you need to specify the way attributes are computed based on other attributes and on the parameters inside the constructors. This is called the semantics of the attributes and is specified via a sem block in the UUAGC source file. This block starts with the name of the data type and then a block per constructor to be described. Each of those blocks contains the code related to defining each of the attributes of that node. The right side for each constructor follows plain Haskell syntax, with three syntactical modifications.
  1. 1.

    The value of an attribute at a child node is accessed via the syntax node.attribute. The special name lhs is reserved for referring to the parent of the current node. The idea of a parent is not found in Haskell code, so let’s clarify it using an example. Say you have a value of the following form Plus (AmountOf 'a') (AmountOf 'b') , that is, a parent Plus node with two AmountOf children nodes. At each node both a string and a result attribute will be handled. From the point of view of AmountOf 'a', lhs.string refers to the attribute in the parent node, that is, the value of the string attribute in the Plus node.

     
  2. 2.

    At the right side of the declaration of each attribute, the references to other attributes or parameters of the constructor must be preceded by the @ sign.

     
  3. 3.

    UUAGC uses indentation to delimit blocks. However, you can ask the preprocessor to keep a piece of code exactly as it appears in the source file, wrapping it between { and }.

     
In particular, this convention implies that the declaration of a synthesized attribute must be similar to lhs.attribute = ... because you’re defining the value of such an attribute in the parent node. In this case, the meaning function can be translated into the following sem block :
sem Expr
  | Plus     lhs.result = @left.result + @right.result
  | Times    lhs.result = @left.result * @right.result
  | AmountOf lhs.result = { length $ filter (== @c) @lhs.string }

Executing the Attribute Grammar

Of course, writing and compiling the attribute grammar isn’t useful at all if you cannot call it in your own Haskell code. Depending on the options given to UUAGC,1 the preprocessor generates several functions and data types that you should use when taking advantage of attribute grammars.
  • The data declaration in a grammar is transformed to a data declaration in Haskell, but with the constructors prefixed with the name of the data type, in this case Expr. If you want constructors to appear as written in the grammar, don’t use the rename option in UUAGC. However, in that case, you have to take care that constructor names do not collide.

  • The data types Inh_Expr and Syn_Expr represent the inherited and synthesized attributes at each node of the Expr data type. Both are declared as records, with the field names including also the names of the attributes and the type of record they are in. In this example, the names boil down to string_Inh_Expr and result_Syn_Expr.

  • A function sem_Expr converts a value of the data type into a function that takes as parameters the initial inherited attributes and returns a tuple of synthesized attributes. However, the function that you get may have the attributes in any order, so it’s customary to use the wrap_Expr function , which takes attribute records as parameters.

For example, a function that would execute the computational actions of the attribute grammar to get the synthesized values in the root of the tree for the Expr data type would read as follows. Note that this function should be found in a different file from the attribute grammar definition itself, so you need to import the module generated by UUAGC.
import Chapter14.Simple
executeExpr :: Expr -> String -> Int
executeExpr e s =
  let syn = wrap_Expr (sem_Expr e) (Inh_Expr s)  -- returns Syn_Expr
   in result_Syn_Expr syn
And now you can use that function to test your attribute grammar.
*Chapter14.Simple> :{
*Chapter14.Simple| let e = Expr_Times (Expr_AmountOf 'e')
*Chapter14.Simple|                    (Expr_Plus (Expr_AmountOf 'a')
*Chapter14.Simple|                               (Expr_AmountOf 'o'))
*Chapter14.Simple| in executeExpr e "hello"
*Chapter14.Simple| :}
1

Integrating UUAGC in Your Package

As mentioned earlier, UUAGC is not a library but a preprocessor for Haskell code. This means the attribute grammars you write cannot be directly compiled by GHC or any other Haskell compiler; rather, they need to be converted into Haskell source by UUAGC. You can do the translation yourself each time your attribute grammar changes, but it’s better to tell Cabal that you’re using UUAGC and let the tool take care of preprocessing files that have changes while it takes care of which files need to be recompiled.

To start, create a new package with an executable stanza. Following the convention in the previous chapters, I’ll call the package chapter14, and the executable will be named uuagc-examples. The Cabal stanza corresponding to that executable reads as follows:
executable uuagc-examples
  hs-source-dirs:   src
  build-depends:    base >= 4
  ghc-options:      -Wall
  other-modules:    Chapter14.Simple
  main-is:          Main.hs
  default-language: Haskell2010
The next step is to tell Cabal to call the preprocessor before compilation. This is done by customizing the build script that Cabal follows to get the files compiled. This script is found in the Setup.hs file and should be changed to read as follows:
import Distribution.Simple
import Distribution.Simple.UUAGC (uuagcLibUserHook)
import UU.UUAGC (uuagc)
main = defaultMainWithHooks (uuagcLibUserHook uuagc)

Caution

Cabal does not support build dependencies, only package dependencies. In other words, Cabal is not aware that you need UUAGC to build the package; you need to ensure it beforehand. If you get an error about UU.UUAGC in Setup, double check that the tool has been correctly installed.

But this is not enough. You also have to change the field build-type in the Cabal file to instruct the build process to use your new Setup.hs file instead of the regular one. Open the chapter14.cabal file and change the line to the following:
build-type: Custom
The final step is to tell UUAGC which files must be handled by it and which compilation options it should apply. To do this, create a uuagc_options file in the root of your project (next to the .cabal and Setup.hs files) with the following contents:
file : "src/Chapter14/Simple.ag"
options : data, semfuns, catas, pretty, wrappers, rename,
          module "Chapter14.Simple", haskellsyntax, signatures

Notice that you need to include both the path to the attribute grammar (in the file field) and the name of the module that will be created (in this case Chapter14.Simple). Furthermore, if you want to use “Haskell syntax” as presented in the examples of this chapter, you should remember to always include the haskellsyntax option.

After all this preparation, you can run Cabal to build your project. The output will be a bit different than usual because you’re using a custom setup script and UUAGC is preprocessing your files. The following is a sample of the output, but it may be a bit different on your system depending on the versions installed:
$ cabal configure
Resolving dependencies...
[1 of 1] Compiling Main             ( Setup.hs, dist/setup/Main.o )
Linking ./dist/setup/setup ...
Configuring chapter14-0.1...
$ cabal build
Building chapter14-0.1...
Preprocessing executable 'uuagc-examples' for chapter14-0.1...
Building executable 'uuagc-examples' for chapter14-0.1...
...
Linking dist/build/uuagc-examples/uuagc-examples ...

For each new attribute grammar that you need to compile, you must include a new pair of lines defining it in the uuagc_options file. The advantage of this methodology is that Cabal takes care of rebuilding only the parts that have changed and doesn’t call the preprocessor if it’s not strictly needed.

UUAGC Code Generation

By default, UUAGC takes advantage of lazy evaluation for executing attribute grammars. But you can also instruct it to optimize the code. In that case, a specific sequence of visits through the tree will be scheduled, resulting in stricter code.

This chapter focuses on the use of UUAGC with Haskell. But the preprocessor can generate code for other functional languages, including OCaml and Clean. In those cases, the right sides should be written in the corresponding back-end language instead of in Haskell.

Expressions Interpretation

Let’s move now to a larger attribute grammar. In this case, the focus would be conditional expressions inside an offer. The aim is to obtain a function that, given an expression and a list of products, returns the result of applying the expression to that list.

Using an Attribute Grammar

You’ve seen in the example for simple numeric expressions how UUAGC separates the three pieces of information needed to define a whole attribute grammar. The first block defines the base data type into which attributes will be computed. The following code is a straightforward translation of the Expr data type found in the previous chapter. Notice that in order to keep the code a bit shorter, only one kind of Boolean comparison between numbers is included.
data Expr a
  | AmountOf product :: {a}
  | PriceOf  product :: {a}
  | TotalNumberOfProducts
  | TotalPrice
  | IVal val :: Int
  | FVal val :: Float
  | Plus  right :: (Expr {a})  left :: (Expr {a})
  | Times right :: (Expr {a})  left :: (Expr {a})
  | LessThan right :: (Expr {a})  left :: (Expr {a})
  | Or  right :: (Expr {a})  left :: (Expr {a})
  | And right :: (Expr {a})  left :: (Expr {a})
  | Not inner :: (Expr {a})

Note

To compile this attribute grammar, you need to enable the ScopedTypeVariables extension for GHC. The easiest way to do this is to add a line similar to extensions: ScopedTypeVariables to the Cabal stanza.

In this case, I’ll return to the simple modeling of expressions results. Three different attributes will hold the result of the expression as integer, float, or Boolean. At any point, only one of the components will hold a Just value, whereas the others will be Nothing. As you know from the previous chapter, you could refine the definition of Expr using GADTs to make this tupling unnecessary. However, the focus in this chapter is on attribute grammars, so let’s try to keep the types as simple as possible. The result of an expression depends on the subexpressions, so this attribute should be a synthesized one.

In addition to computing the result, you need to thread the list of products among subexpressions because the constructors AmountOf, PriceOf, TotalAmountOfProduct, and TotalPrice need that information. In an attribute grammar setting, that information would be represented via an inherited attribute. The declaration of the set of attributes for Expr is as follows:
attr Expr
  inh products  :: {[(a, Float)]}
  syn intValue  :: {Maybe Int}
  syn fltValue  :: {Maybe Float}
  syn boolValue :: {Maybe Bool}

Note

Remember that compound types such as [(a, Float)] or Maybe Bool must be written inside brackets to stop UUAGC from parsing them as special attribute grammar syntax.

Following the example in this section, let’s write the sem block for Expr. The initial declaration is a bit more complex in this case because you need to declare that the elements in the expression are instances of the Eq type class to be able to define the operations. Thus, the declaration reads as follows:
sem Eq {a} => Expr
One simple constructor is the TotalNumberOfProducts. In this case, the only attribute with a concrete value should be the integer one; the rest should be given a Nothing value. Since nodes of that kind don’t have any children, you don’t need to include any code for the inherited products attribute. The full code in this case is as follows:
  | TotalNumberOfProducts lhs.intValue  = Just $ length @lhs.products
                          lhs.fltValue  = Nothing
                          lhs.boolValue = Nothing
A more complex example is the Plus one. In this case, the integer and floating values should be updated, but each of them should take a Just value only if both subexpressions also take this value. You can achieve this concisely using the Applicative instance of Maybe. The following code defines the synthesized attributes of the Plus constructor. The code for inherited ones is also straightforward because it copies only the value of products from the node to their children.
  | Plus  lhs.intValue   = {(+) <$> @right.intValue <*> @left.intValue }
          lhs.fltValue   = {(+) <$> @right.fltValue <*> @left.fltValue }
          lhs.boolValue  = Nothing
          right.products = @lhs.products
          left.products  = @lhs.products

Given these examples, the rest of the cases should be easy to write. Exercise 14-1 asks you to do so.

Exercise 14-1. Full Expression Attribute Grammar

Complete the remaining cases of the sem block of Expr. Hint: use the previous examples as templates, either performing some computation over the products list or combining the value of subexpressions using Applicative syntax.

For example, a function that would execute the computational actions of the attribute grammar to get the synthesized values in the root of the tree for the Expr data type would read as follows:
executeExpr :: Ord a => Expr a -> [(a,Float)]
            -> (Maybe Int, Maybe Float, Maybe Bool)
executeExpr e products =
  let syn = wrap_Expr (sem_Expr e) (Inh_Expr products)  -- returns Syn_Expr
   in ( intValue_Syn_Expr  syn
      , fltValue_Syn_Expr  syn
      , boolValue_Syn_Expr syn )
And you can use that function to test your attribute grammar.
*Chapter14.Expr> :{
*Chapter14.Expr| let e = Expr_And
*Chapter14.Expr|           (Expr_AmountOf 'a' `Expr_LessThan` Expr_IVal 2)
*Chapter14.Expr|           (Expr_FVal 300.0 `Expr_LessThan` Expr_TotalPrice)
*Chapter14.Expr|     p = [('a',15.0), ('b',400.0)]
*Chapter14.Expr| in executeExpr e p
*Chapter14.Expr| :}
(Nothing,Nothing,Just True)

Precomputing Some Values

The attribute grammar for Expr works fine but could be enhanced in terms of performance. In the code above, each time a TotalNumberOfProducts or TotalPrice constructor is found, the full product list has to be traversed. Since it’s quite common to find those basic combinators in the expressions, it’s useful to cache the results to be reused each time they are needed. Let’s see how to implement that caching in the attribute grammar.

The most common way to cope with this situation is by adding a new data type with only one constructor that wraps the entire expression tree. This data type is usually called Root . The following is its definition plus an indication to derive a Show type class instance, since it will be interesting to inspect those trees:
data Root a
  | Root expr :: (Expr {a})
deriving Root Expr : Show

Note

Adding an extra Root data type is merely a convention used for initializing inherited attributes. In many cases it’s not needed, and of course, you can use a name different from Root.

The precomputed values will be stored in the numberOfProducts and totalPrice attributes. Those attributes must be passed top-down from the root of the tree. Thus, they must be inherited attributes of the Expr data type. Furthermore, if you want to get back the result of the computation from Root, you have to include the same synthesized attributes that Expr had. The attr part of the grammar should be changed to the following:
attr Root Expr
  inh products  :: {[(a, Float)]}
  syn intValue  :: {Maybe Int}
  syn fltValue  :: {Maybe Float}
  syn boolValue :: {Maybe Bool}
attr Expr
  inh numberOfProducts :: Int
  inh totalPrice       :: Float

Notice how UUAGC allows you to include in the same declaration several data types that share attributes. To use that functionality, just include all the data types after the attr keyword. In this example, the products, intValue, fltValue, and boolValue attributes are declared for both the Root and Expr data types.

Finally, you need to add the computation rules. The ones for computing the new inherited attributes of an Expr from a Root are straightforward given the grammar of the previous section.
sem Eq {a} => Root
  | Root expr.numberOfProducts = length @lhs.products
         expr.totalPrice       = foldr ((_,p) x -> p + x) 0.0 @lhs.products

Now you might expect extra declarations for threading the new inherited attributes inside Expr and taking the synthesized attributes from Expr to save it into Root. The good news is that they are not needed at all. UUAGC has some built-in rules that fire in case you haven’t specified how to compute some attribute. For inherited attributes, the computation proceeds by copying the value from the parent to its children. For synthesized attributes, the result is taken from a child. (If you need to combine information from several children, there are other kinds of rules; you’ll get in touch with them in the next section.) These rules are collectively known as copy rules because they take care of the simple case of attributes being copied. The flow of the inherited products attribute in the previous grammar didn’t have to be specified; this rule would take care of it.

One thing that should be changed is the computation of the synthesized attributes in those constructors that were to be enhanced. Instead of computing the result each time, they should use the precomputed values from the Root. For example, TotalNumberOfProducts should be changed to the following:
  | TotalNumberOfProducts lhs.intValue  = Just @lhs.numberOfProducts
                          lhs.fltValue  = Nothing
                          lhs.boolValue = Nothing

The core of the idea is that each time you need to initialize some values for the rest of the computation of the attributes, it’s useful to wrap the entire tree inside a data type with only one constructor, which includes that initialization in its sem block.

A Different (Monadic) View

When learning about a new concept, it’s useful to relate it to concepts you’re already familiar with. You’ve already seen how attribute grammars come into existence when trying to declaratively define the flow of information. In the first simple interpretation, the parameters to functions became inherited attributes, and the tuple of results correspond to the synthesized attributes. In Chapters 6 and 7, another different concept was used to thread information in that way: the Reader and Writer monads. Indeed, you can express the attribute grammar developed in this section using the monadic setting. I’ll show how to do that. Note that the code from this section is independent of the attribute grammar and should be written in a different file, which should contain the definition of the Expr data type from the previous chapter.

First, you need to overcome a little technicality: the type used in the Writer must be a monoid. However, the interest here lies only in the last value output by the function, so you can “fake” this behavior by providing a Monoid instance that does this, shown here:
import Data.Monoid
newtype Result = Result (Maybe Int, Maybe Float, Maybe Bool) deriving Show
instance Semigroup Result where
  _ <> r2 = r2
instance Monoid Result where
  _ `mappend` r2 = r2
  mempty         = Result (Nothing, Nothing, Nothing)

The Semigroup instance is only required in GHC version 8.4 or newer. The reason is that from that version on, Semigroup is a superclass of Monoid , and thus any instance of the latter must also be an instance of the former.

Caution

Although type correct, this instance doesn’t express a true monoid. The problem is that x `mappend` mempty = mempty, which is not what is necessary. A correct solution for this problem would be to save a list of output results and get the last one at the end of the attribute computation.

As mentioned, the behavior of inherited attributes can be seen as wrapped in a Reader monad and the synthesized attributes can be seen as wrapped in a Writer one. Thus, the type signature of the function to write is as follows:
sem :: Eq a => Expr a -> ReaderT [(a,Float)] (Writer Result) ()
The corresponding code that calls the function passing the initial values would be as follows:
import Control.Monad.Reader
import Control.Monad.Writer
executeExpr :: Eq a => Expr a -> [(a, Float)] -> Result
executeExpr e p = execWriter (runReaderT (sem e) p)
Let’s see how the computation of two of the constructors is expressed in this setting. First, you have TotalNumberProducts, which takes the inherited attribute (which is now the information in the Reader monad) and produces a new synthesized attribute with the length of the list.
sem TotalNumberProducts = do products <- ask
                             tell $ Result ( Just (length products)
                                           , Nothing, Nothing )
A second combinator to look at is the addition of two numbers. In this case, you need to compute the synthesized attributes locally and get those results to create the new synthesized attributes. The first part can be achieved using listen, the counterpart of Reader’s function local for the Writer monad.
sem (e1 :+: e2) = do (_, Result (i1, f1, _)) <- listen (sem e1)
                     (_, Result (i2, f2, _)) <- listen (sem e2)
                     tell $ Result ( (+) <$> i1 <*> i2
                                   , (+) <$> f1 <*> f2, Nothing )

As you can see, attribute grammars express the concepts behind the Reader and Writer monads in a new setting. The main advantage is that you can refer to attributes by name, instead of stacking several monad transformers and having to access each of them by a different amount of lift. The intuitive idea that Reader and Writer monads can be combined easily and in any order to yield a new monad is made explicit by the fact that you can combine attribute grammars just by merging their attributes.

Offer Interpretations

It’s time to move forward from conditional expressions to full values of type Offer, which represent the possible discounts in the Time Machine Store. As in the previous section, the examples will revolve around the initial definition of this data type, prior to adding extra type safety, in order to keep the code clean and concise.

Checking the Presents Rule

You’ve already seen several ways in which the Presents Rule of the offers (remember, the number of free presents in an offer may be limited) can be checked. Using strong typing, you looked at a way to count the maximum number of presents, but the implementation gives no clue about what presents are inside. The task in this section is to create a small attribute grammar that computes the largest possible set of presents that a single customer can get for free.

As in the previous section, the main data type is a straightforward translation from Haskell into the UUAGC syntax. The data declaration that will be used, and that contains the names given to the fields of each constructor, reads as follows:
data Offer a
  | Present  present :: {a}
  | PercentDiscount  discount :: Float
  | AbsoluteDiscount discount :: Float
  | Restrict products :: {[a]} inner :: (Offer {a})
  | From   from  :: Int inner :: (Offer {a})
  | Until  until :: Int inner :: (Offer {a})
  | Extend times :: Int inner :: (Offer {a})
  | Both     left :: (Offer {a}) right :: (Offer {a})
  | BetterOf left :: (Offer {a}) right :: (Offer {a})
  | If cond :: (Root {a}) then :: (Offer {a}) else :: (Offer {a})
deriving Offer : Show
The list of presents is something that is computed bottom-up, so it should be a synthesized attribute. In the code example, the attribute will be called presents. Here’s the attr part of the grammar:
attr Offer
  syn presents :: {[a]}
The next step is to write the sem block that tells the preprocessor how to compute the value of that attribute. In most cases of the data type, presents are just accumulated from all the suboffers. However, there are two special cases to consider.
  1. 1.

    When you find a Present constructor , you know that the number of presents returned by that offer is a singleton list including the value in its parameter.

     
  2. 2.

    When a Restrict constructor is found, you must delete from the present list all those products that are not inside the restriction list.

     
In terms of code, this corresponds to the following sem block. Remember that any code written inside brackets will be copied unchanged into the generated Haskell code. The case of imports is treated specially by UUAGC; you should write imports before the block to ensure that the imports are treated according to Haskell rules. In this example, we bring the module Data.List into scope.
imports
{
import Data.List
}
sem Eq {a} => Offer
  | Present  lhs.presents = [@present]
  | Restrict lhs.presents = { @products `intersect` @inner.presents }
Now it is time to write the computation for the rest of the cases, which is the concatenation of the lists for those constructors with suboffers and the empty list for other basic combinators such as discounts. Luckily, UUAGC acknowledges that this is a common pattern and provides special syntax for that case, extending the built-in copy rule mechanism for synthesized attributes. With this syntax, you need to provide a function that merges the attribute from the children and a base case for those nodes with no children at all. In this case, the attr part of the grammar can be decorated with (++) as the function to merge and [] as the initial value, so it reads as follows:
attr Offer
  syn presents use {++} {[]} :: {[a]}

If you don’t specify how to compute the value of the presents attribute, the rule that just copies it will no longer be applied. Instead, the preprocessor will use the operations in the use declaration.

If you look a bit closer at the operations specified, you’ll notice that they are indeed the Monoid instance for lists. Remember the previous discussion about the relation between attribute grammars and monads? When using a synthesized attribute with use, you can see it being a Writer, where the Monoid instance of the attribute type is exactly defined by the operations in use (instead of the operation that just forgets about the first argument of mappend).

The Duration Rule could also be checked using an attribute grammar. Exercise 14-2 asks you to do so.

Exercise 14-2. The Duration Rule

Compute the maximum duration of an offer using an attribute grammar. The rules for doing so should be the same as in Chapter 13. Decorate your attributes with use, as shown in this section, to specify the common behavior of joining offers.

Showing an HTML Description

The last example for attribute grammars will show a description of an offer as HTML. This is an interesting example of how a grammar can be used not only to consume information but also to show that information in a new way. Furthermore, this grammar will show some subtleties related to attribute handling.

The generated HTML should have three parts:
  1. 1.

    A list of all the presents that you may get via the offer. This is done by reusing the presents attribute from the previous section.

     
  2. 2.

    A small table of contents that should show a small description of the first and second levels of the offer tree, in which item is a link to the full description.

     
  3. 3.

    Finally, the full description of the offer as a series of nested lists.

     
The markup for the full description will be stored in an html attribute and will be encoded using the blaze-html library presented in Chapter 12. Thus, to follow this section, you need to add blaze-html as a dependency. To obtain the final markup that comprises the three parts, the code will use the trick of adding an extra root data type, which will be called HtmlRoot . In the following code block, the imports related to blaze-html are gathered here:
imports
{
import Data.List
import Data.String
import qualified Text.Blaze.Html5 as H
import qualified Text.Blaze.Html5.Attributes as A
}
data HtmlRoot a
  | HtmlRoot root :: (Offer {a})
In addition, each node will keep its title and the list of titles of the children immediately below. In that way, the uppermost node will have the information needed for the table of contents. The main issue with building the table is generating links to the full description. For that, you need to assign a unique identifier to each node. However, this identifier behaves both as an inherited attribute and a synthesized attribute. This has the effect of threading the attribute among siblings of the same node in addition to going up and down. Figure 14-2 shows the flow of information of such an identifier through a basic offer.
../images/316945_2_En_14_Chapter/316945_2_En_14_Fig2_HTML.jpg
Figure 14-2

Attribute c threaded through siblings of an And node

These kinds of attributes that are both inherited and synthesized are known in the field of attribute grammars as chained . If you want to relate the behavior of chained attributes to a monad, it would be the State one in this case. The attribute can be seen as a value that changes throughout the exploration of the whole tree.

The declaration of such an attribute inside an UUAGC source file can be done by writing the same attribute named both as inh and syn. But to stress that an attribute is chained, UUAGC uses chn as its identifier. Following the general pattern in computer science for naming a source of unique numbers, this attribute will be called counter . The definition of all these attributes reads as follows:
attr Offer
  chn counter   :: Int
  syn title     :: String
  syn subtitles :: {[(String,Int)]}
  syn presents use {++} {[]} :: {[a]}  -- from previous section
attr HtmlRoot Offer
  syn html        :: {H.Html}
Obviously, now the attribute grammar should be executed starting on an HtmlRoot value . The corresponding describeOffer function reads as follows:
describeOffer :: (Eq a, Show a) => Offer a -> H.Html
describeOffer o
  = html_Syn_HtmlRoot $ wrap_HtmlRoot (sem_HtmlRoot $ HtmlRoot_HtmlRoot o)
                                      Inh_HtmlRoot
Let’s first consider the computation of the counter attribute . At each node, the final value should be different from all their children and the value from its parent. You can achieve this by adding 1 to the value of the last child. For example, the value for basic combinators is just the one from the parent plus 1 and is expressed as follows:
sem Eq {a}, Show {a} => Offer
  | Present PercentDiscount AbsoluteDiscount  -- applies to all these
      lhs.counter = @lhs.counter + 1

Since counter is both an inherited attribute and a synthesized attribute, lhs.counter is referring to different attributes on each side. On the right side, @lhs.counter is the value that comes from the parent. On the left side, lhs.counter is the value that will be given back as an identifier for this node.

This code is not completely correct, though. If inside another attribute (e.g., when creating the HTML markup for that node) you refer to lhs.counter, you’ll be referring to the value from the parent. But this is not what you need. Two children of the same parent would get the same counter value since they share the same parent. The solution is to save the new value in a local attribute, which works as a local variable inside a node. In that way, each node would retain its ident value, notwithstanding any change in its parent. You don’t need to declare local attributes; use them only in a node, prefixing them by the loc keyword. In the example, I’m using a local attribute called ident to thread the value of the counter to other nodes while making a unique identifier available for other attributes:
  | Present PercentDiscount AbsoluteDiscount
      loc.ident   = @lhs.counter + 1
      lhs.counter = @loc.ident
For a constructor with more than one child, you should thread the attribute from one to the other. In this example, the Both and BetterOf constructors do so through their left and right children.
  | Both BetterOf
      left.counter  = @lhs.counter
      right.counter = @left.counter
      loc.ident     = @right.counter + 1
      lhs.counter   = @loc.ident

Think carefully about the rest of the threading for Exercise 14-3.

Exercise 14-3. Threading the Counter

Complete the computation of the counter and ident attributes for the Restrict, From, Until, Extend, and If constructors.

Using local attributes can help you write more concise code for computing the HTML markup. For example, you can divide the markup of each node into two parts: the HTML corresponding to the description of the constructor (htmlText) and the HTML corresponding to their children (htmlChild). Then, both attributes are joined in a single Html value, as shown here:
  | Present PercentDiscount AbsoluteDiscount Restrict
    From Until Extend Both BetterOf If
      lhs.html = { do H.a H.! A.name
                                (fromString ("elt" ++ show @loc.ident))
                            $ H.toHtml @loc.htmlText
                      @loc.htmlChild }

Note

You can write all the rules as they are shown, one below the other. UUAGC will take care of merging all the attribute computations for each constructor.

Now each of the constructors must give the value of those local attributes to build their HTML markup. This code is quite straightforward. As an example, the code that does so for the PercentDiscount and BetterOf constructors follows:
  | PercentDiscount
      loc.htmlText  = show @discount ++ "% discount"
      loc.htmlChild = H.toHtml ""
  | BetterOf
      loc.htmlText  = "Better of"
      loc.htmlChild = { H.ul $ do H.li @left.html
                                  H.li @right.html }
I mentioned that each of the nodes should save the title and subtitle attributes for the final table of contents. The first one will be a simple String value , whereas the other should collect the titles and identifiers of each of the immediate children. For the constructors that are being tackled, it reads as follows:
  | PercentDiscount
      lhs.title     = { "DISCOUNT: " ++ show @discount ++ "%" }
      lhs.subtitles = []
  | BetterOf
      lhs.title     = { "BETTER OF" }
      lhs.subtitles = [ (@left.title,  @left.counter)
                      , (@right.title, @right.counter) ]

The rest of the constructors are left as an exercise to the reader, specifically as Exercise 14-4.

Exercise 14-4. Building Descriptions

Complete the computation of the htmlText, htmlChild, title, and subtitle attributes for the Present, AbsoluteDiscount, Restrict, From, Until, Extend, Both, and If constructors.

The final touch is to take all that information and build the whole Html value representing the description. The following code takes advantage of the encoding as a monad of blaze-html, using mapM_ to generate lists of elements. For the rest, the code is just generating the markup from the inner elements.
sem Eq {a}, Show {a} => HtmlRoot
  | HtmlRoot root.counter = 1
             lhs.html = {
               do H.h1 $ H.toHtml "Description of an offer"
                  H.h2 $ H.toHtml "Possible presents:"
                  H.ul $ mapM_ (e -> H.li $ H.toHtml (show e)) @root.presents
                  H.h2 $ H.toHtml "Main offer"
                  H.a H.! A.href (fromString ("#elt" ++ show @root.counter))
                        $ H.toHtml @root.title
                  H.ul $ mapM_ ((s, e) -> H.li $ H.a H.! A.href
                                                   (fromString ("#elt" ++ show e))
                                                $ H.toHtml st)
                               @root.subtitles
                  H.h2 $ H.toHtml "Complete offer"
                  @root.html }

One important advantage of describing the generation of the markup as an attribute grammar is that you didn’t have to take care of ordering the computation. UUAGC is able to find a sorting of the attributes, in the way that each of them needs only the previous ones to be computed. If this cannot be done, the preprocessor will emit a warning telling you that your attribute grammar is circular.

Programming with Data Types

Attribute grammars provide us a common language to describe operations performed over any data type. Any Haskell value can be seen as a tree and can be decorated using attributes which flow either top-down or bottom-up. But this is not the only way to look at data types in a uniform manner: folds provide a similar generalization. In fact, Haskell has powerful features to describe operations based on the shape of a data type; this is what we call data type-generic programming.

Origami Programming Over Any Data Type

In Chapter 3 you saw how folds can be a powerful tool for understanding code on lists. The basic idea of a fold was to replace the (:) constructor with some function f and [] with some initial value i, as Figure 14-3 shows.
../images/316945_2_En_14_Chapter/316945_2_En_14_Fig3_HTML.jpg
Figure 14-3

Reminder of the behavior of foldr

Interestingly, the notion of a fold can be defined for every Haskell data type. For each constructor you supply a function that will “replace” it and then evaluate the resulting tree. In addition, the fold should call itself recursively in those places where the definition of the data type is also recursive. These generalized folds are also called catamorphisms .

For example, consider the simple language in the first section of this chapter, where the hard-coded Char parameter to AmountOf has been generalized to a type variable in the Expr type.
data Expr a = Plus  (Expr a) (Expr a)
            | Times (Expr a) (Expr a)
            | AmountOf a
In this case, a fold would have as parameters a function for Plus, other for Times, and finally another for AmountOf. As I stated before, the implementation should call each of those functions depending on the constructor of the value and have recursive calls in the places where the definition of Expr has Expr again as argument. With those ideas, the full code reads as follows:
foldExpr plusFn timesFn amountFn e =
  let f = foldExpr plusFn timesFn amountFn
  in case e of
       Plus  e1 e2 -> plusFn  (f e1) (f e2)
       Times e1 e2 -> timesFn (f e1) (f e2)
       AmountOf x  -> amountFn x
The interpretation shown in the first section can now be reworked as a fold like in the following code:
meaning :: Eq a => [a] -> Expr a -> Int
meaning s = foldExpr (+) (*) (x -> length $ filter (==x) s)
The types of the parameters can be recovered from the data type definition. A specific fold will produce an element of some type b for each value. Thus, for each place where you may get a value from a recursive place, b must appear in its type. You have also additional parameters for the information encoded in each constructor. With these guidelines, these types read as follows:
plusFn   :: b -> b -> b
timesFn  :: b -> b -> b
amountFn :: a -> b
When reading about folds or catamorphisms, you’ll sometimes find the term algebra . A D-algebra for a data type D is just a tuple that contains all the functions required to perform a fold over that data structure. You can create a newtype for an algebra for Expr.
newtype ExprAlgebra a b = ExprAlgebra (b -> b -> b, b -> b -> b, a -> b)
Thus, a fold over a data type D can be seen as a function taking a D-algebra and a value of D and returning the appropriate value. Let’s express the fold over Expr using algebra terms.
foldExpr' :: ExprAlgebra a b -> Expr a -> b
foldExpr' a@(ExprAlgebra (plusFn,timesFn,amountFn)) e =
  case e of
    Plus  e1 e2 -> plusFn  (foldExpr' a e1) (foldExpr' a e2)
    Times e1 e2 -> timesFn (foldExpr' a e1) (foldExpr' a e2)
    AmountOf x  -> amountFn x

You can practice those ideas by adding new constructors in Exercise 14-5.

Exercise 14-5. Larger Expressions

Extend the Expr data type with the constructors Minus and DividedBy and complete the corresponding fold functions for those new constructors.

Folds are closely related to attribute grammars. What you’re doing with an attribute grammar is defining a specific algebra for your data type, an algebra that computes the value of the attributes. If you look at the generated Haskell code by UUAGC, you’ll notice that there are some parts labeled as cata. These are the functions starting with sem_ that you used previously. What this function does is fold over the structure while expecting the initial inherited values you give with the function starting with wrap_.

Once you feel comfortable with these ideas, you can dive into other ways data types can be constructed and consumed in a generic fashion. For example, the notion of unfold (also called anamorphism ) can also be generalized, and combinations of folds and unfold give rise to other interesting functions. A good library to look at is recursion-schemes by Edward A. Kmett. Knowing these patterns will help you when reusing a lot of code that is already written and that follows the same structure on different data types.

In addition to libraries, there’s also a lot of information about how you can use this idea of folding over any data type to create more elegant code. “Origami programming” by Jeremy Gibbons was already mentioned in Chapter 3. “Functional Programming with Bananas, Lenses, Envelopes and Barbed Wire” by Erik Meijer, Maarten Fokkinga, and Ross Paterson discusses all these generalizations of folds and unfolds and shows how to use equational reasoning as you saw in Chapter 3 for lists, but over any data type.

Data Type-Generic Programming

You’ve already seen two kinds of polymorphism in Haskell. Parametric polymorphism treats values as black boxes, with no inspection of the content. Ad hoc polymorphism, on the other hand, allows you to treat each case separately.

But there’s a third kind of polymorphism available in GHC, namely, data type- generic programming (or simply generics). In this setting, your functions must work for any data type, but you may depend on the structure of your data declaration. There are several libraries of doing this kind of programming available in Hackage. GHC integrates its own in the compiler in the GHC.Generics module, and this is the one I am going to discuss.

Data type-generic programming works in Haskell because data types are only made of three different building blocks:
  • Fields , which contain one value of a certain type.

  • Products , which put together several fields into a constructor. We use the symbol (:*:) to express products.

  • Choice of constructors, for those data types which have more than one. In this case, we use the symbol (:+:).

Besides those, we need a way to describe constructors which have no data associated with them. One example of such a constructor is the empty list []. In fact, let’s look at the description of the type of lists of Booleans:
*> import GHC.Generics
* GHC.Generics> :kind! Rep [Bool]
Rep [Bool] :: * -> *
= D1 ('MetaData "[]" "GHC.Types" "ghc-prim" 'False)
        (C1 ('MetaCons "[]" 'PrefixI 'False) U1
     :+: C1 ('MetaCons ":" ('InfixI 'LeftAssociative 9) 'False)
            (    S1 ('MetaSel 'Nothing ...) (Rec0 Bool)
             :*: S1 ('MetaSel 'Nothing ...) (Rec0 [Bool])))
Apart from the building blocks mentioned above, this description – which has been automatically generated by GHC – also mentions metadata, like the name of the constructors. Those are visible with names D1, C1, and S1. If we strip those out, we obtain the core of the description of a list of Booleans, namely:
U1 :+: (Rec0 Bool) :*: (Rec0 [Bool])

You should read this as “you have two constructors, one with no fields, and one with two fields, the first one of type Bool and the second one of type [Bool]”. For most data type-generic programming tasks, the name of the data type and the constructors is not used at all.

In order to work with the generic version of a data type, you need to convert it to its description, which is nothing else that the Rep t type family discussed above. There is one type class, Generic, which takes care of this:
instance Generic [a] where
  type Rep [a] = -- shown above
  from []     = L1 U1
  from (x:xs) = R1 (K1 x :*: K1 xs)
  to (L1 U1)               = []
  to (R1 (K1 x :*: K1 xs)) = x:xs

Note that the appearance of a choice of constructors results in the use of L1 and R1 to choose which constructor to take. The rest of the building blocks, (:*:), U1, and Rec0, have only one constructor, whose name coincides with that of the type, except for Rec0 which uses K1.

The question is: how can we define a generic operation by taking advantage of this uniform description of data types? The procedure is always similar: we have to define two type classes. The first one represents the operation itself, whereas the second one is used to disassemble the building blocks and derive the implementation. Furthermore, in the first type class we specify how to derive a function automatically using the second one.

Because code is worth thousand words, let’s define one operation in a generic fashion. A call to getall with a given type should return the values of all the fields of that type in the data structure. For example:
* GHC.Generics> getall (Proxy :: Proxy Int) (Maybe 3 :: Maybe Int)
[3]

Note

A proxy is a dummy value whose only purpose is to fix a type for the compiler. They are defined in the Data.Proxy module.

The first step, as discussed above, is to introduce a type class for the operation and another for the building blocks:
{-# LANGUAGE KindSignatures #-}
{-# LANGUAGE MultiParamTypeClasses #-}
class GetAll t a where
  getall :: Proxy t -> a -> [t]
class GGetAll t (f :: * -> *) where
  ggetall :: Proxy t -> f x -> [t]

As you can see, they look fairly similar, except for the kind of the second argument. In the GHC.Generics module all the building blocks have a kind * -> *. As a result, the second argument to ggetall must be given an additional type argument, so it reads f x instead of simply f.

Now next step is to define how the ggetall function is implemented for each of the building blocks of a data type. In most cases, these instances call each other recursively, until they get to U1 or Rec0 , where we need to do the real work. In our case, choice of constructors simply recurs to the chosen branch, products concatenate the values from their components, and field-less constructors U1 add no values to the mix.
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE TypeOperators #-}
instance (GGetAll t f, GGetAll t g) => GGetAll t (f :+: g) where
  ggetall p (L1 x) = ggetall p x
  ggetall p (R1 y) = ggetall p y
instance (GGetAll t f, GGetAll t g) => GGetAll t (f :*: g) where
  ggetall p (x :*: y) = ggetall p x ++ ggetall p y
instance GGetAll t U1 where
  ggetall p U1 = []
The case of fields follows a similar pattern in all data type-generic constructors. We always call the nongeneric version of the type class, GetAll in this case, from the generic version applied to Rec0. This is not enough in this case, because without further information we would never reach any real field. For that reason, we add two instances for the nongeneric type class. The first one says that each type t contains exactly one value of type t. The other one says that if the types are different, by default there are no values of the chosen type. This instance is marked as overlappable, because it just works as a catch-all instance; in some cases like obtaining the Ints from a [Int], we shall override that default.
instance (GetAll t s) => GGetAll t (Rec0 s) where
  ggetall p (K1 x) = getall p x
instance {-# OVERLAPS #-} GetAll t t where
  getall p x = [x]
instance {-# OVERLAPPABLE #-} GetAll t s where
  getall p x = []
Since the representations also contain metadata about the names of the data type, constructors, and fields, we have one last case to handle. Metadata is represented uniformly via the M1 building block; we do not need three different instances. It is quite common to not do anything interesting in these instances, apart from calling the generic operation recursively.
instance (GGetAll t f) => GGetAll t (M1 v i f) where
  ggetall p (M1 x) = ggetall p x
The last step is to declare how to obtain the implementation of the nongeneric type class from the building blocks of the generic one. This is done via a default signature . These are like default implementations, but with a constraint over the way in which the code can be derived. The pattern is once again very similar for every generic operation: you always require a Generic instance, and an instance of the generic type class for the description of the type.
{-# LANGUAGE DefaultSignatures #-}
class GetAll t a where
  getall :: Proxy t -> a -> [t]
  default getall :: (Generic a, GGetAll t (Rep a)) => Proxy t -> a -> [t]
  getall p = ggetall p . from
If you have a type for which you want to implement getall, you don’t need to write the code any more. Just derive its Generic instance, which is done automatically by the compiler, and ask it to implement the GetAll type class:
{-# LANGUAGE DeriveGeneric #-}
data Tree a = Node a | Branch (Tree a) (Tree a)
            deriving (Show, Eq, Generic)
instance GetAll a (Tree a)  -- look ma, no code!

This was a very fast-paced introduction to data type-generic programming, with the aim for you to taste its flavor. If you want to dive into it, I recommend looking at the lecture notes called “Applying Type-Level andGeneric Programming in Haskell” by Andres Löh.

Summary

This chapter introduced attribute grammars as a way to give interpretations to a DSL encoded as a Haskell data type.
  • You delved into the general idea of attributing a tree whose nodes are the constructors of a specific Haskell data type.

  • Three kinds of attributes were distinguished: synthesized (bottom-up), inherited (top-down), and chained (threaded among siblings).

  • Monads are related to attribute grammars: Reader corresponds to inherited attributes, Writer to synthesized attributes, and State to chained attributes.

  • You learned how to configure and use the Utrecht University Attribute Grammar Compiler to preprocess your attribute grammars into Haskell code.

  • UUAGC provides many facilities for writing concise attribute grammars. In this chapter, you became familiar with use declarations and local attributes.

  • The idea of fold (or catamorphism) was generalized from lists to any possible Haskell data type.

  • Finally, you have seen how to define operations which depend on the structure of the data type using GHC.Generics.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.16.207.123