1 / 35

What is a recursive module? Crary, Harper, Puri

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

orien
Télécharger la présentation

What is a recursive module? Crary, Harper, Puri

An Image/Link below is provided (as is) to download presentation Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author. Content is provided to you AS IS for your information and personal use only. Download presentation by click this link. While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server. During download, if you can't get a presentation, the file might be deleted by the publisher.

E N D

Presentation Transcript


  1. What is a recursive module?Crary, Harper, Puri Module Systems, Fall 2002 Aleksey Kliger

  2. 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

  3. 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

  4. 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

  5. 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

  6. Example cont'd • What we would like is to write something like structure rec Expr = struct … End and Decl = struct … end

  7. 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)

  8. 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.

  9. 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

  10. 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

  11. 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

  12. 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…

  13. 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

  14. 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

  15. 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]

  16. 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

  17. Fixpoints • Uniqueness of constructor fixpoint reflected by the bisimilarity rule:

  18. 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

  19. 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]

  20. Fixpoint formation • Fixpoint formation formalized to the judgment

  21. 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]

  22. 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]]

  23. 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

  24. 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

  25. 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

  26. 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

  27. 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

  28. 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)

  29. 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)

  30. 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

  31. 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

  32. 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)))

  33. 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

  34. 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

  35. 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

More Related