4.2 The Ag Language
4.2.2 Semantics
Prior work gives us a good starting point in defining the semantics of this language. We can re-use an essentially off-the-shelf functional semantics for (higher-order) at- tribute grammars [72]. Many of the extensions to attribute grammars we have incor- porated (reference attributes [58], and forwarding [21]) give a whole-program transla- tion back to ordinary, unextended attribute grammars.
We emphasize whole-program because these extensions, while they can be trans- lated away when we work with a whole-program, do still give us new capabilities when we have broken our program up into modules, and thus we include them in Ag.
4.2. The Ag Language
That is, there is still a good reason for keeping them around; they are not merely syntactic sugar. Forwarding, for example, cannot be translated away until we know all attributes that occur on a nonterminal. Our whole purpose in introducing for- warding is that we wish to write modules that do not break each other because they are unaware of some attributes that another module introduces to a nonterminal. But when it comes time to actually execute a program, we are always dealing with a whole program.
The introduction of aspects and default equations poses no special difficulty from a whole-program point of view. Default equations can simply be copied into each (non-forwarding) production that lacks one. Aspects can simply be merged with their original production declarations. (We have aspects because these allow us to write equations in different modules, but when taking a whole-program perspective we can pretend all modules have merged into one.) The only special behavior to note in this transformation is the renaming of variables to make signatures match and to avoid name collisions for locals between aspects. But these are essentially standard program transformation issues.
Each of these translations can be applied without any unexpected interaction with each other. The only major features of Ag that taken care of by prior semantics work or the above simple transformations are: the type parameters of nonterminals, and the semantics of pattern matching. We will defer all discussion of pattern matching until section 4.4.
And so, for the remainder of this section, we wish to turn our attention to whether any special problems are introduced in the translation of higher-order attribute gram- mars to a functional language. In particular, because we have enriched the types on the attribute grammar side, we want to know whether we make any new demands on
4.2. The Ag Language
the type system of the target functional language. For example, do we need the ability to nest quantifiers within types (like System F), or is the usual type schema stratifi- cation sufficient? We answer below that it is sufficient, but perhaps useful extensions could be permitted (beyond Ag) if we allowed additional quantifiers. However, our formulation of Ag also requires the target functional language of this semantics to support existential types (of a limited sort.)
A typical [72] translation to a typed non-strict lambda calculus will correspond productions with a semantic function with roughly the following type:
αN : tuple type of all synthesized attributes on N
βN : tuple type of all inherited attributes on N
p : a production of Ag type (N ::= T ) semp : (βN, αT)→ (αN, βT)
That is, the semantic function corresponding to a production (semp) takes as
input the inherited attributes given to a node and the synthesized attributes produced by that node’s children. It then outputs that node’s synthesized attribute and the inherited attributes to give to each child. This tuple must be a non-strict one, or this runs into trouble: almost every interesting attribute grammar would simply be an infinite loop. This function’s input includes the output of the children, but its output is the inputs for the children.
These semantic functions can then be knit, giving a production type similar to: (N.B. We gloss over the details of this knit function’s implementation since they aren’t too relevant to us, but it can be found in [72].)
4.2. The Ag Language
That is, a production takes its arguments and gives back the translated nonterminal type. And that nonterminal type is now also a function, from its inherited attributes to synthesized attributes.
So now we consider the question: how does this story change as a result of intro- ducing type parameters?
We have already mentioned one design choice that considerably simplifies our task here. Whenever a type parameter appears in a production, it is treated as an ordinary value, regardless of whether it ultimately turns out to be instantiated as a nonterminal type. That is, the production cannot have equations that supply attributes to a child of abstract type (since it may not even turn out to be nonterminal type!) This check will appear explicitly in the type rules for productions in the next section. But as a result of this uniform treatment of parameters as values, there are no extra complications with the existing semantics incorporating type parameters on nonterminals, all that happens is quantifiers show up.
Given: p : ∀v (NhTni ::= T )
Let vn= f v(Tn) (i.e. the free variables)
and vp = v\ vn
We use vnto denote those variables in a production that are universally quantified
over. We take vp to be those variables that appear only on the right-hand side of
the production signature. These latter variables are universally quantified but in the negative position of a function type, and as a result essentially act as extensionally quantified variables. We then get:
semp :∀v. (βN, αT)→ (αN, βT)
4.2. The Ag Language
It is interesting to note there are other options for where type quantifiers could appear here. One possibility (that we may implement in the future) is to permit quantifiers to appear within the types of attributes individually. That is, the elements of the tuple types α or β could be polytypes. We currently forbid this (and this rule will show up explicitly in the type system in the next section) and require attributes have monotypes. This extension would not cause any special problems for the type system (i.e. its still a a system of equalities on monotypes), however we have avoided it so far because we believe it should come with additional syntax (to explicitly indicate the quantified variables in the attribute declaration).
A more invasive possibility would be to permit quantifiers at “decoration time.” That is, to permit the translated nonterminal type (which is currently a flat βN → αN)
to have a quantifier as well, this inserting an additional quantifier in the middle of production types. This would permit parameterized attributes to have types related to each other, without involving any type parameters on the nonterminal. For in- stance, in figure 4.6 we show an erroneous attempt to represent Bool values, and implement conditionals as inherited and synthesized attributes. The constraint that all three attributes should have the same type cannot be expressed without adding a type parameter to Bool. Doing so means our Bool value can only be involved in conditionals of a single type!
The drawback of this extension is that we would have a more difficult (and verbose) time indicating how these references should work. Unlike nonterminals, where we declare the full set of type parameters, the set of parameters for decoration could be extended by any module that introduces a new related set of attributes. Dealing with an open set of nominally addressed type parameters was far too complex of an
4.3. The Type System