Com S 641 meeting -*- Outline -*- * typing rules (Schmidt 1.2) ** why? Q: does the abstract syntax completely define legality? no, it allows things like 3 + (not(1=2)) which are nonsense also: this is widely used notation for modern papers... ** type attributes Q: What kind of grammar are we using for abstract syntax? Q: What kind of grammar do we need to exclude nonsense? context-sensitive actually don't really need it, but convenient to use another level, in same way we used another level of grammar in going from lexer to parser use a kind of context-sensitive formalism called attribute grammars idea: attach type attributes to phrases the obvious ones are int and bool, but those would only work for expressions how do we distinguish a location from an expression? what about a command? ------------------------------------------ TYPE ATTRIBUTES (following Reynolds) type attribute use int int loc int exp, bool exp comm ------------------------------------------ note: no boolean locations, although they are possible tree is well-formed if can attact type attributes for all nonterminals (n.b.) ------------------------------------------ KINDS OF SYNTAX TREES abstract syntax tree: C / \ L := E (Loc 1) | N 0 attributed syntax tree: C: terminology: type annotation, type attribute well formed, well typed attributed syntax tree ------------------------------------------ ... / \ L:intloc := E:intexp (Loc 1) | N:int 0 ** typing rules tell us how to attach type attributes to programs Q: how should we go about defining how to attach type attributes to a program? structural induction (based on grammar, of course)! ------------------------------------------ TYPING RULES What rule for N ::= n, if n \in Integer? Examples: What rule for E ::= N? Examples: ------------------------------------------ ... n:int, if n \in Integer the rule attributes the type to the nonterminal above! think of this as the lexer returning the token N (Numeral) and synthesizing the type (int) ... N: int N:int 5 4 Q: What would you do for L ::= loci ? ... notation (explain from bottom up): N:int _______ N:intexp again notice that it's actually the E, which the lower N is the rhs of, that's having it's type attribute made intexp on the bottom we show the form of expression, just like we showed the n instead of n before ... E: intexp E:intexp | | N: int N:int 5 4 Q: What would you do for E ::= @L ? Q: What would you do for E ::= E1 + E2 ? ------------------------------------------ MORE TYPING RULES What rule for C ::= L := E? Example: ------------------------------------------ ... since only have integer locations: L:intloc E: intexp ___________________ (L := E): comm discuss this from the bottom up. Q: What would you do for C ::= if E then C1 else C2 fi ? show figure 1.4 ------------------------------------------ ATTACHING TYPE ATTRIBUTES while 0 = 0 do loc5 := @loc4 + 1 od ------------------------------------------ draw the abstract syntax tree first, then attach the atributes, with class help ** modeling type rules in Haskell > module CoreTyping where > import CoreLangParser ------------------------------------------ MODELING ATTRIBUTES IN HASKELL > data PrimitiveAttrib = > Aint | Abool | Acomm > | Aerr > | Aloc PrimitiveAttrib > | Aexp PrimitiveAttrib deriving Eq ------------------------------------------ show correspondences to Schmidt's notation int ~ Aint bool ~ Abool int loc ~ Aloc Aint int exp ~ Aexp Aint Aerr is used for type errors, to allow error messages... Q: Can you define an instance of the Haskell class Show for this type? ------------------------------------------ ATTRIBUTED SYNTAX TREES ARE PAIRS OF SYNTAX AND ATTRIBUTE TREES > data Tree a = Node a [Tree a] > deriving Eq > root :: Tree a -> a > root (Node r _) = r > data AttribTree syntax attrib = > syntax ::: attrib > type ATree syntax = > AttribTree syntax > (Tree PrimitiveAttrib) ------------------------------------------ The tree stuff is in a module Tree.hs The actual attributes used are trees of primitive type attributes. This allows the attributes of subtrees to be recovered if needed. Attributed syntax trees are a pair of a piece of syntax and an attribute. The ::: is the constructor for such pairs. (Loc 3) ::: Node (Aloc Aint) [] :: ATree Location (Deref (Loc 3)) ::: Node (Aexp Aint) [Node (Aloc Aint) []] :: ATree Expression We could try to model attributed trees, where all the attributes are recorded at each interior leaf (nonterminal), but that's messy and doesn't allow helping functions to be parameteric. So we use the above The function "annotate" is overloaded on each syntactic domain in the abstract syntax. Hence it is method in the following type class. ------------------------------------------ ANNOTATION > class Annotatable a where > annotate :: a -> ATree a EXAMPLES ? annotate 7 :: ATree Numeral ? annotate (Loc 641) ? annotate (Deref (Loc 641)) ? annotate (Num 641) ? annotate ((Num 641) `Plus` (Num 342)) Plus (Num 641) (Num 342) ::: Node (Aexp Aint) [Node (Aexp Aint) [Node Aint []], Node (Aexp Aint) [Node Aint []]] ? annotate ((Num 3) `Equals` (Num 4)) Equals (Num 3) (Num 4) ::: Node (Aexp Abool) [Node (Aexp Aint) [Node Aint []], Node (Aexp Aint) [Node Aint []]] ? annotate ((Loc 1) `Assign` (Num 641)) Assign (Loc 1) (Num 641) ::: Node Acomm [Node (Aloc Aint) [], Node (Aexp Aint) [Node Aint []]] ? annotate ((Loc 3) `Assign` ((Num 2) `Equals` (Num 3))) Assign (Loc 3) (Equals (Num 2) (Num 3)) ::: Node Aerr [Node (Aloc Aint) [], Node Aerr [Node (Aexp Aint) [Node Aint []], Node (Aexp Aint) [Node Aint []]]] ------------------------------------------ ... 7 ::: Node Aint [] ... Loc 641 ::: Node (Aloc Aint) [] ... ADeref (Loc 641) ::: Node (Aexp Aint) [Node (Aloc Aint) []] ------------------------------------------ MODELING THE TYPE RULES instance Annotatable Command where annotate c@(l `Assign` e) = (case (annotate l, annotate e) of (_::: tl@(Node (Aloc Aint) _), _::: te@(Node (Aexp Aint) _)) -> c ::: Node Acomm [tl, te]) ------------------------------------------ We can either leave out the error case, as here, or write something like... _ -> c ::: Node Aerr [tl, te] Do some more, such as while, skip Q: Can we abstract some of this detail away? Q: Can we make these look more like rules? ------------------------------------------ ABSTRACTING OUT A RULE CHECKER What are the parts we really need? What would that look like in use? So what's the type of the abstraction? ------------------------------------------ ... a list of hypotheses (syntax and expected type) syntax for the whole thing, the type of the whole thing. ... annotate c@(l `Assign` e) = rule [Loct l ::: Aloc Aint, Expr e ::: Aexp Aint] _____________________ c Acomm ... Annotatable a => Hypotheses -> a -> PrimitiveAttrib -> ATree a ------------------------------------------ THE RULE ABSTRACTION > rule :: (Annotatable hsyn, > Annotatable csyn) => > [AttribTree hsyn PrimitiveAttrib] > -> csyn -> PrimitiveAttrib > -> ATree csyn > rule hs syn tau = > if not (Aerr `elem` (map root ts)) > then syn ::: Node tau ts > else syn ::: Node Aerr ts > where > ts = map check hs > check (s ::: expected) = > (case annotate s of > _::: t@(Node pta _) > | pta == expected -> t > _::: (Node _ ts) -> > Node Aerr ts) > axiom :: Annotatable csyn => > csyn -> PrimitiveAttrib > -> ATree csyn > axiom syn tau = syn ::: Node tau [] ------------------------------------------ explain the above This is in the module CoreTypeHelpers.lhs (Note: Because of the Haskell type system, we can't write axiom = rule [].) ------------------------------------------ AUXILIARY DEFINITIONS FOR RULE > data Syntax = > Expr Expression > | Comm Command > | Loct Location > | Numr Integer > instance Annotatable Syntax where > annotate (Expr e) = > case annotate e of > (e:::a) -> Expr e ::: a > annotate (Comm c) = > case annotate c of > (c:::a) -> Comm c ::: a > annotate (Loct l) = > case annotate l of > (c:::a) -> Loct l ::: a > annotate (Numr n) = > case annotate n of > (n:::a) -> Numr n ::: a ------------------------------------------ Q: why does Syntax have to be an instance of Annotatable? ------------------------------------------ TYPING RULES FOR COMMANDS > instance Annotatable Command where > annotate c@(l `Assign` e) = > rule [Loct l ::: Aloc Aint, > Expr e ::: Aexp Aint] > ---------------------- > c Acomm > annotate Skip = axiom Skip Acomm > annotate c@(c1 `Semi` c2) = > rule [Comm c1 ::: Acomm, > Comm c2 ::: Acomm] > ------------------- > c Acomm > annotate c@(If e c1 c2) = > rule [Expr e ::: Aexp Abool, > Comm c1 ::: Acomm, > Comm c2 ::: Acomm] > ------------------- > c Acomm ------------------------------------------ Q: Can you write the rule for while in this format? > annotate c@(While e body) = > rule [Expr e ::: Aexp Abool, > Comm body ::: Acomm] > ---------------------- > c Acomm ------------------------------------------ TYPING RULES FOR EXPRESSIONS > instance Annotatable Expression where > annotate e@(Deref l) = > rule [Loct l ::: (Aloc Aint)] > ------------------------ > e (Aexp Aint) > annotate e@(Num n) = > rule [Numr n ::: Aint] > ----------------- > e (Aexp Aint) > annotate e@(e1 `Plus` e2) = > rule [Expr e1 ::: Aexp Aint, > Expr e2 ::: Aexp Aint] > ----------------------- > e (Aexp Aint) > annotate e@(Not e1) = > rule [Expr e1 ::: Aexp Abool] > ------------------------ > e (Aexp Abool) ------------------------------------------ Q: does this work for E1 = E2? Q: What could we write? annotate e@(e1 `Equals` e2) = (case (annotate e1, annotate e2) of (_::: te1@(Node (Aexp at1) _), _::: te2@(Node (Aexp at2) _)) | at1 == at2 -> e::: Node (Aexp Abool) [te1, te2]) better to make a new abstraction for rules with side conditions take the side condition as a function argument. Example the following, ask them to write ruleIf? > annotate e@(e1 `Equals` e2) = > ruleIf [Expr e1, Expr e2] > (\[Aexp at1, Aexp at2] -> > at1 == at2) > ---------------------- > e (\_ -> (Aexp Abool)) > ruleIf :: (Annotatable hsyn, > Annotatable csyn) => > [hsyn] -> ([PrimitiveAttrib] -> Bool) > -> csyn > -> ([PrimitiveAttrib] > -> PrimitiveAttrib) > -> ATree csyn > ruleIf subparts side_cond syn tau_f = > if not (Aerr `elem` ts) > && side_cond ts > then syn ::: Node (tau_f ts) ats > else syn ::: Node Aerr ats > where > ts = (map root ats) > ats = map extract subparts > extract s = > let (_::: attr) = annotate s > in attr ------------------------------------------ REMAINING RULES > instance Annotatable Location where > annotate l@(Loc i) = > axiom l (Aloc Aint) > instance Annotatable Integer where > annotate n = axiom n Aint ------------------------------------------ We can't use ruleIf for location's side condition, because the condition concerns the syntax, not on the types. ** terms ------------------------------------------ TERMINOLOGY type assertion, type judgement U : t static typing, dynamic typing def: a language is *strongly typed* iff Questions: unicity of typing soundness ------------------------------------------ ... no well-typed program produces run-time incompatibility errors (some op that is nonsense). Is that really well-defined? Questions: (unicity) no tree well-typed in more than one way? would that be bad? why? (soundness) are the typing rules sensible in that they ensure strong typing? ** induction and recursion (1.3) skip this, but tell them to read it, expect they already know this... ** unicity of typing (1.4) Q: How would we prove that for every phrase P, if P:t holds, then t is unique? structural induction, because of the "for every phrase P" talk through it, or tell them to read it. the "if P:t holds" is important, as handles error cases significance of this: a linear representation, such as loc_i := 0 : comm can be used safely, since it represents at most one program. ** typing rules define the language (1.5) the typing rules constitute an inductive def of well-typed trees. ------------------------------------------ TYPING RULES DEFINE THE LANGUAGE (1.5) !--------------------------------------! ! strings of tokens ! ! !----------------------------------! ! ! ! abstract syntax trees ! ! ! ! !------------------------------! ! ! ! ! ! type annotated trees ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !------------------------------! ! ! ! !----------------------------------! ! !--------------------------------------! AXIOMS AND RULES DEFINE A LOGIC !--------------------------------------! ! strings of symbols ! ! !----------------------------------! ! ! ! well-formed formulas ! ! ! ! !------------------------------! ! ! ! ! ! provable formulas ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! ! !------------------------------! ! ! ! !----------------------------------! ! !--------------------------------------! ------------------------------------------ we only care about the pieces of the syntax that are typeable, everything else is to be ignored Q: What's this like in compilers? separation of static vs. dynamic semantics ------------------------------------------ TYPE RULES AS RULES OF LOGIC Axioms: n:int, if n \in Integer loc_i:intloc, if i > 0 Inference rules: N:int _______ N:intexp L:intloc E: intexp ___________________ (L := E): comm Proof tree: ------------------------------------------ ... 4:int | 4:intexp (Loc 1):intloc | \ / (Loc 1):= 4 : comm Note the similarities and differences to the attributed syntax tree Q: So what's the annotated syntax tree in terms of the logic? a proof tree, so a proof of what? that there is some element of that type Summary: the typing rules constitute an inductive def of well-typed trees. Q: Can there be more than one proof tree for a well-typed program in While0? no. ** non-unique typings and coherernce (skip) ------------------------------------------ NON UNIQUE TYPINGS Suppose we add these rules: E: intexp E1: realexp E2: realexp _________ ________________________ E: realexp E1+E2 : realexp What proof trees are there for 5 + 4 : realexp? def: a type system is *coherent* if each derivation of E:t has ------------------------------------------ ... draw them, either coercing before or after addtion ... the same meaning