350 likes | 424 Views
What is a recursive module? Crary, Harper, Puri. Module Systems, Fall 2002 Aleksey Kliger. CHP. Understand the type theory of recursive modules via a phase-splitting interpretation into a constructor and a term expression
E N D
What is a recursive module?Crary, Harper, Puri Module Systems, Fall 2002 Aleksey Kliger
CHP • Understand the type theory of recursive modules via a phase-splitting interpretation into a constructor and a term expression • Introduce recursively-dependent signatures to accurately reflect sharing of type information in recursive modules
Example • Recursive modules are useful for splitting a program into several independent pieces • Consider the abstract syntax of a fictional ML compiler • Separate types dec and exp for the declarations and expressions • Mutually recursive datatypes
Example cont'd datatype exp = … | LET of dec * exp | … and dec = … | VAL of identifier * exp | … … fun make_let_val (id, e1, body) = let val d = VAL (id, e1) in LET(d,body) end … • Suppose we now wish to separate the expressions and the declarations into separate modules
Example cont'd structure Expr = struct datatype exp = … | LET of Decl.dec * exp | … fun make_let_val (id, e1, body) = let val d = Decl.VAL(id, e1) in LET(d, body) end … end structure Decl = struct datatype dec = … | VAL of identifier * Expr.exp | … … End • Fails to typecheck: neither structure can be written after the other one
Example cont'd • What we would like is to write something like structure rec Expr = struct … End and Decl = struct … end
Fixpoint modules • By analogy to fixpoint at term level: fix(x:s.e), introduce a module-level fixpoint: fix(s:S.M) • The structure variable s stands for the module being defined • As with fixpoints at term level, need to ensure that the fixpoint exists and is unique. (Will return to this)
Opaque Recursive Modules • To typecheck recursive module fix(s:S.M) suppose module variable s has signature S, and check that module M does. • Opaque in the sense that when checking M the only thing we know about s is that it has signature S.
Opaque Recursive Modules Limitations • Problem: knowing only that s has signature S is often not enough: • The preceeding definition for List fails to typecheck because we do not know within the body of List that t and List.t are the same type, so cannot typecheck cons signature LIST = sig type t val nil : t val cons : int * t -> t end structure rec List :> LIST = struct datatype t = NIL | CONS of int * List.t val nil = NIL fun cons(n:int, l:t): t = CONS(n,l) end
Opaque Recursive Modules Limitations • CHP shows a way to program around this deficiency that sacrifices efficiency: fun cons(n:int, l:t): t = case l of NIL => CONS(n,List.NIL) | CONS(n', l') => CONS(n, List.cons(n', l')) • In general such a workaround not possible, instead must give List a more precise signature while typechecking the struct
Recursively-dependent signatures • CHP solution is to introduce a signature for List which captures the dependency of t on List.t: • The signature given to List depends on a structure. Incidentally, that structure is List itself structure rec List :> sig datatype t = NIL | CONS of int * List.t val nil : t val cons : int * t -> t end = struct (* as before *) end
Recursively-dependent signatures • A module M may be given the signature rs.S if M may be given signature S[M/s] • If module M can be given the rds rs.S then M also has the signature S[M/s] • Back to the List example…
Recursively-dependent signatures • We assume List has the rds, and check the struct has the same rds (this is our rule for checking fixpoints) • cons is now ok because the type of l (that is, t) is structurally equivalent to List.t structure rec List :> sig datatype t = NIL | CONS of int*List.t val nil : t val cons : int * t -> t end = struct datatype t = NIL | CONS of int*List.t val nil : t val cons (x:int, l:t):t = CONS(x,l) end
Transparency • Note that the preceding example typechecked because the datatype t was defined transparently in the rds and we appealed to structural equality • CHP formalize this as the formation rule for rds's. An rds rs.S is well-formed iff the type components of S are transparent and S is a well-formed sig, in the context where s:S • Appealing to structural equality means we're using "equi-recursive" interpretation of recursive constructors
CHP Core Calculus • Like HMM with singleton kinds and fixpoints at the constructor and term level kinds k ::= T | 1 | S(c) | Pa:k1.k2 | Sa:k1.k2 constructors c ::= a | ¤ | la:k.c | c1 c2 | hc1, c2i | pI(c) | 1 | c1! c2 | c1£ c2 | ma:k.c types s ::= c | s1!Tots2 | s1!s2 | s1£s2 | 8a:k.sterms e ::= x | ¤ | l x:s.e | e1 e2 | h e1,e2i | pI(e) | La:k.e | e[c] | fix(x:s.e)contexts G ::= e | G[a:k] |G[x:s] | G[a"k] | G[x"s]
Fixpoints • Contractiveness condition on formation of recursive constructors to ensure that fixpoints exist and are unique • Constructor ma:k.c is well-formed if it actually "goes somewhere", ie it unfolds to an infinite tree. • Formalized with judgmentc is contractive with kind k, provided that a has kind k and is not contractive
Fixpoints • Uniqueness of constructor fixpoint reflected by the bisimilarity rule:
Fixpoints • There is a value restriction on fixpoints at the term level (more than restricting fix to lambdas because of phase-splitting considerations) • Formalized by judgment which says that e is valuable under the assumption that x is not. • Lambda abstractions lx:s.e are always valuable, moreover if e is always valuable, the lambda is deemed total, and its application is always valuable, if its argument is
CHP Structure Calculus • Like HMM structure calculus plus new fixpoint modules and rds's constructors c ::= … | Fst s terms e ::= … | Snd s signatures S ::= [a:k, s] | rs.S modules M ::= [c,e] | fix(s:S.M) contexts G ::= … | G[s:S] | G[s"S]
Fixpoint formation • Fixpoint formation formalized to the judgment
Rds intro and elimination • A module M may be given the rds rs.S if • r s.S is well-formed (next slide), • and M : S[M/s] • rs.S is a dependent signature, and it depends on M • Elimination: if M has the rds rs.S, M also has S[M/s]
Rds formation • Constructor part must be transparent • Any module M that may be given this rds may also be given an opaque signature S with all the recursive references in the static part hidden, and all the recursive references at the term level redirected to the static part • The transparent static part must be contractive where S is [a:k,s[a/Fst s]]
Phase splitting interpretation • Like in HMM, we understand fixpoint modules via a simple structure obtained by splitting the fixpoint into a static and a dynamic component • To phase-split fix(s:[a:k.s].M), suppose that in the context where s has the given signature, M phase splits into [c(Fst s), e(Fst s, Snd s)], then the fixpoint splits into [a = ma:k.c(a), fix(x:s, e(a,x))] • Take the fixpoint of c, and redirect recursive references to the static part in e to the static part of the phase split module
Phase splitting rds's • To phase split an rds rs.S we require that S split into [a:S(c(Fst s):k), s(Fst s)] • (ie, S has transparent type components which may refer to types in s) • Then the rds splits into [a:S(mb:k.c(b) : k), s(a)] • Note that recursive dependency in the static part is handled using recursive types, but dependency in the dynamic part is not essentially recursive
Avoiding static-on-static dependency • We can get the Expr/Decl example to work without rds's using only fixpoint modules if we're willing to incur a function call overhead • Consider the following opaque signatures: signature DECL = sig type exp type dec val mk_val : identifier * exp -> dec end signature EXPR = sig type exp type dec val mk_let_val : identifier * exp * exp -> exp end
Avoiding static-on-static dependency (cont'd) Structure rec Expr :> EXPR = struct datatype exp = … | LET of Decl.dec * exp | … type dec = Decl.dec fun mk_let_val (id, e1 : exp, body : exp) : exp = let val d = Decl.mk_val(id,e1) in LET(d,body) end … End And Decl :> DECL = struct type exp = Expr.exp datatype dec = … | VAL of identifier * Expr.exp | … fun mk_val (id, e : exp) : dec = VAL(id, e) … end
Practical typechecking • To typecheck structure rec A : ASIG(A) = struct … endwe would like to • first check that rs.ASIG(s) is well-formed, • then check that the struct has ASIG(A), given that A does. • The second step is different from what we said before: to typecheck the fixpoint struct, see if it has type rs.ASIG(s), given that A has rs.ASIG(s) • Would like to show that the more direct typechecking strategy is equivalent to the type theoretic method
Practical typechecking cont'd • By the intro and elim rules for rds's, A : ASIG(A) iff A : rs. ASIG(s) • Would like to know that ASIG(s) and rs.ASIG(s) are eqiuvalent in the context where s has rs.ASIG(s)
Practical typechecking cont'd Given s : \rho s. ASIG(s) ASIG(s) = [a:S(c(Fst s):k), s(Fst s)] (by well-formedness of rs.ASIG(s)) = [a : S(c(mb:k.c(b)):k), s(mb:k.c(b))] (by phase-splitting s's sig) = [a : S(mb:k.c(b):k), s(mb:k.c(b))] (by roll up and singleton kinds) = [a : S(mb:k.c(b):k),s(a)] (by singleton kinds and structures) = rs.ASIG(s) (by phase splitting) (for appropriate c,k,s)
Practical typechecking cont'd • Typechecking still critically depends on equality of equi-recursive constructors at higher kind • Hard problem, maybe reducible to equivalence problem of DPDAs which is decidable but no practical (efficient) algorithm
Iso-recursive types • Type equality for equirecursive types is hard • Would rather use iso-recursive types • Compiling datatypes typically use iso-recursive types anyway • Seems like "most of the time" recursive modules have static-on-static dependencies are within datatypes
Iso-recursive types • Turns out need to adopt Shao's equation to compile recursive modules using iso-recursive types • Let m@b.c(b) be the iso-recursive type. Then Shao's equation says m@a.c(a) = m@a.c(m@a.c(a)))
Iso-recursive types • If restrict the type components of an rds to only being datatypes, then after phase-splitting, the static part of an rds will be of the form ma.m@b.c(a,b) • By invoking Shao's equation and bisimilarity, can show this is equivalent to m@b.c(b,b) • Ie, uses of equi-recursive types may be eliminated
Conclusion • Phase splitting interpretation of opaque fixpoint modules and transparent rds's • Rds's are a novel way of formalizing the type theory of recursive modules • Relies on equi-recursive types • Not clear if this is a practical language to typecheck
Other approaches Derek R. Dreyer, Robert Harper, and Karl Crary. Toward a Practical Type Theory for Recursive Modules. • There appear to be several different ways of typechecking fixpoint modules which admit more examples, by considering the use of recursive types in the phase splitting interpretation of fixpoint modules • I did not really understand this TR