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

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

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

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:.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 -> tendstructure 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 -> tend = struct (* as before *)end

Recursively-dependent signatures

• A module M may be given the signature s.S if M may be given signature S[M/s]

• If module M can be given the rds s.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 -> tend = 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 s.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 ::= T | 1 | S(c) | :1.2 | :1.2

constructors c ::= | ¤ | :.c | c1 c2 | hc1, c2i | I(c) | 1 | c1! c2 | c1£ c2 | :.ctypes ::= c | 1!Tot2 | 1!2 | 1£2 | 8:.terms e ::= x | ¤ | x:.e | e1 e2 | h e1,e2i | I(e) | :.e | e[c] | fix(x:.e)contexts ::= | [:] |[x:] | ["] | [x"]

Fixpoints

• Contractiveness condition on formation of recursive constructors to ensure that fixpoints exist and are unique

• Constructor :.c is well-formed if it actually "goes somewhere", ie it unfolds to an infinite tree.

• Formalized with judgment

c is contractive with kind , provided that has kind 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 x:.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 sterms e ::= … | Snd ssignatures S ::= [:, ] | s.Smodules M ::= [c,e] | fix(s:S.M)contexts ::= … | [s:S] | [s"S]

Fixpoint formation

• Fixpoint formation formalized to the judgment

Rds intro and elimination

• A module M may be given the rds s.S if– s.S is well-formed (next slide),– and M : S[M/s]

• s.S is a dependent signature, and it depends on M

• Elimination: if M has the rds s.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 [:,[/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:[:.].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

[ = :.c(), fix(x:, e(,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 s.S we require that S split into

[:S(c(Fst s):), (Fst s)]• (ie, S has transparent type components which may

refer to types in s)• Then the rds splits into

[:S(:.c() : ), ()]• 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 -> decendsignature EXPR = sig type exp type dec val mk_let_val : identifier * exp * exp -> expend

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 …EndAnd 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 s.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 s.ASIG(s), given that A has s.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 : s. ASIG(s)

• Would like to know that ASIG(s) and s.ASIG(s) are eqiuvalent in the context where s has s.ASIG(s)

Practical typechecking cont'dGiven s : \rho s. ASIG(s)

ASIG(s) = [:S(c(Fst s):), (Fst s)] (by well-formedness of s.ASIG(s)) = [ : S(c(:.c()):), (:.c())] (by phase-splitting s's sig) = [ : S(:.c():), (:.c())] (by roll up and singleton kinds) = [ : S(:.c():),()] (by singleton kinds and structures) = s.ASIG(s) (by phase splitting)

(for appropriate c,,

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 .c() be the iso-recursive type. Then Shao's equation says

.c() = )))

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

..c(,)• By invoking Shao's equation and bisimilarity,

can show this is equivalent to

.c(,)• 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