290 likes | 304 Views
Explore the foundational concepts of module system designs and their impact on real language implementations. Dive into the unifying type theory, projectibility, purity evaluation, second-class modules, sealing mechanisms, and the importance of generativity in functors.
E N D
A Type System forHigher-Order Modules Derek Dreyer, Karl Crary, and Robert Harper Carnegie Mellon University POPL 2003
Type Theory for Module Systems • Lots of work on module system design • Theory has had impact on real language design: • Harper-Lillibridge 94, Leroy 94 ) SML ’97 • Leroy 95 ) Objective Caml • Russo 00 ) Moscow ML • No general semantic framework for understanding relationships between designs
A Unifying Type Theory • High-level semantic analysis ) Unifying type theory • Previous designs can be seen as subsystems • Key idea: Explain semantics of abstract data types in terms of purity and effects
Projectibility • When is a module expression M projectible? • When can we project out M’s type components? • “Non-projectible” module expression:if buttonIsPressed() then struct type t = int ... end else struct type t = bool ... end • “Projectible” module expression:struct type t = int; val x = 3 end
Projectibility • When is a module expression M projectible? • When can we project out M’s type components? • “Non-projectible” module expression:if buttonIsPressed() then struct type t = int ... end else struct type t = bool ... end • “Projectible” module expression:struct type t = int; val x = 3 end
Projectibility • When is a module expression M projectible? • When can we project out M’s type components? • “Non-projectible” module expression:if buttonIsPressed() then struct type t = int ... end else struct type t = bool ... end • “Projectible” module expression:struct type t = int; val x = 3 end
Projectibility • When is a module expression M projectible? • When can we project out M’s type components? • “Impure” module expression:if buttonIsPressed() then struct type t = int ... end else struct type t = bool ... end • “Pure” module expression:struct type t = int; val x = 3 end
Projectibility • When is a module expression M projectible? • When can we project out M’s type components? • “Impure” module expression:if buttonIsPressed() then struct type t = int ... end else struct type t = bool ... end • “Pure” module expression:struct type t = int; val x = ref 3 end
Purity • M is soundly projectible ,M is pure (w.r.t. type components), Type components of M are the same every time M is evaluated • M is impure ) Meaning of M.t not statically well-determined
Second-Class Modules • Second-class modules admit “phase separation” • Type components can’t depend on run-time conditions • SML and O’Caml modules are second-class because of syntactic restrictions • All second-class modules are pure • But should they all be projectible?
Sealing • Principal means of creating abstract data types • M :>s, aka “opaque signature ascription” • Treating sealed modules as projectible violates abstraction: • A = (M :>s) and B = (M :>s) • If (M :>s) is projectible, then A.t = (M :>s).t = B.t • But “A.t = B.t” not observable in s
You Can’t Handle the Truth! • In truth, sealing doesn’t affect a module’s purity • But sealing obstructs our knowledge about a module’s purity • Projectibility is a judgment of knowledge, not truth: • Sealed modules treated as impure/non-projectible
Total and Partial Functors • To track purity in the presence of functors: • Need to know whether applying a functor will unleash an effect or not • Distinguish types of total and partial functors: • F : Ptot s:s1.s2, body of F is known to be pure • F : Ppar s:s1.s2, body of F could be impure
Total , Applicative • F : Ptot s:s1.s2, M : s1, F and M are pure • structure Res1 = F(M) • structure Res2 = F(M) • F(M) known to be pure ) projectible Res1.t = F(M).t = Res2.t
Partial , Generative • F : Ppar s:s1.s2, M : s1, F and M are pure • structure Res1 = F(M) • structure Res2 = F(M) • F(M) possibly impure ) non-projectible Res1.t ¹ Res2.t
Functors with Sealing • If body of a functor contains sealing, then: • Body is impure • Functor is generative • Can be both a good and bad thing: • Gives correct semantics of abstraction for functors that use imperative features • Overly restrictive for purely functional functors
Importance of Generativity functor MakeSymbolTable() = (struct ... (* creates new mutable hash table *) end :> sig type symbol val string_to_symbol : string -> symbol val symbol_to_string : symbol -> string end) ) • Generativity ties the symbol type to the run-time state of the module defining it
Purely Functional Abstraction functor MakeSet (Elem : COMPARABLE) = (struct ... end :> sig type elem = Elem.elem type set val insert : elem * set -> set end) • What if a sealed module is purely functional? • Abstract types not tied to any run-time state • Only care about hiding type identity
Hoisting the Sealing • Instead of sealing the body of the functor: l s:s1. (M :>s2) • Seal the functor itself (with a total signature): (l s:s1. M) :>Ptot s:s1.s2 • Problem: Only works if M is pure • Not true if M contains sealed substructures,such as datatype definitions
Module Classifications Impure, non-projectible Pure, projectible Sealing M :>s
Static and Dynamic Effects • Split effects into two kinds: static and dynamic • Module with any kind of effects is impure • Dynamic effects occur “during execution” • Static effects occur “during typechecking”
Weak and Strong Sealing Statically and dynamically impure Impure, Non-projectible Statically impure, dynamically pure Weak Sealing M ::s Strong Sealing M :>s Pure, projectible
Set Functor Revisited functor MakeSet (Elem : COMPARABLE) = (struct ... end :: sig type elem = Elem.elem type set val insert : elem * set -> set end) • We expand totality to allow body of a total functor to contain static (but not dynamic) effects
A Unifying Framework • Standard ML • Only has strong sealing, all functors are partial/generative • Objective Caml / Leroy (1995) • Only has weak sealing, all functors are total/applicative • Shao (1999) • Distinguishes two kinds of functor signatures • Only tracks dynamic effects and strong sealing • Russo (2000) • Two languages, one like SML and one like O’Caml • Moscow ML combines them, but language is unsound
Modules as First-Class Values • Packaging modules as first-class values: • Add a new “package type” <s> • Coercions between modules and terms: • If M : s, then pack M as <s> : <s> • If e : <s>, then unpack e as s : s
Modules as First-Class Values structure X = struct type t = int ... end structure Y = struct type t = bool ... end M = (if buttonIsPressed() then X else Y) • Type components of M actually depend onrun-time conditions • Unpacking induces a truly dynamic effect
Modules as First-Class Values signature S = sig type t ... end structure X = struct type t = int ... end structure Y = struct type t = bool ... end M = unpack (if buttonIsPressed() then pack X as <S> else pack Y as <S>) as S • Type components of M actually depend onrun-time conditions • Unpacking induces a truly dynamic effect
The Rest of the Paper • Formalism: • Synthesis of previous work on dependent types and singleton kinds • Fully expressive higher-order functors • Via “static” module equivalence • Decidable typechecking algorithm • Avoidance problem • Restrict type theory (as little as possible) to avoid it • Unrestricted language definable by elaboration
Conclusion • Future Work: • Recursive modules • Using monads instead of total/partial • We’ve provided a framework for understanding: • Alternative module system designs • Semantics of abstraction, via a framework of module-level effects