Recursive Module Playground
Question: does the elaboration of module expressions always produce a term with type of kind ? Question: can we use Derek’s double vision solution A Type System for Recursive Modules (two pass checking + internal/external view tagging)
A minimal example of double vision problem
module rec X : sig
module A : sig type t end
module B : sig
val g : X.A.t -> X.A.t
end
end = struct
module A = struct
type t = int
let f (x : t) = X.B.g (x + 1) (* Error 1 *)
let z = let y = X.B.g 42 in y + 5 (* Error 2 *)
end
module B = struct
let g (x : X.A.t) = x
end
endThe module typing (without identity tagging):
This is modified form of Derek’s method, as we try to avoid using the forward declaration signature
First pass, compute the recursive signature by static parts of the module expression
module rec X : sig
module A : sig type t end
module B : sig
val g : X.A.t -> X.A.t
end
end
[First pass] we get Approx is
\alpha. sig
module A : sig type t = alpha end
module B : sig end
end
[Second pass] we are checking in the context of:
alpha, X : sig
module A : sig type t = alpha end
module B : sig end
end |- ... :
\beta. sig
sig type t = beta end
sig val g: X.A.t -> X.A.t end
<--- problem: at this point X.A.t cannot be retrieved from C
<--- we don't have the M-type-typedef-path rule?
end
Assuming a typing path rule, we get:
alpha, X : ... |- ... :
\beta. sig
sig type t = beta end
sig val g: alpha -> alpha end
end
so the overall rec signature is:
exists (beta : * -> *).
forall (alpha : *). sig
sig type t = beta(alpha) end
sig val g = alpha -> alpha end
end
module rec X : sig
type u = X.t
type t = X.u
end
[First pass] we get Approx is
\alpha1 alpha2. sig type t = alpha1, u = alpha2 end
[Second pass] we are checking in the context of:
alpha1, alpha2,
X : sig type t = alpha1, u = alpha2 end
|- ... :
sig
sig type t = alpha2 end
sig type u = alpha1 end
end
?
mu (X : *). sig
type t = X.u
type u = X.t
end
and the second pass will be a similar formalization like applicative functors (but actually no, since we still need path information), by assuming the (approximated) recursive signature is known
- for the second premise we are not intending to use the path , should be refined?
To see: later when checking module expressions, how the skolemization will be treated, we need to finetune the rules here so that the modules and signature elaborations can coincide
What we do for elaborating rec module expressions?
A concern to soundness is: whether this strengthening? weakening? that: vs. (the actual, coinductive?)
- which requires , however, is more abstract than , so we should not drop type definitions as Leroy did? is sound and to what extent incomplete
So far, we haven’t solved the double vision problem, but we can follow the elaboration in A Type System for Recursive Modules to expose external and internal views?
mu (X : *) . {
A = {
t_t = ⟨⟨ Int ⟩⟩
v_f = \(x : ⟨⟨ Int ⟩⟩). X.B.g (x + 1)
}
B = sig val g : X.A.t -> ?
}
where ⟨⟨ Int ⟩⟩ : ⋆ = ∀(φ : ⋆ → ⋆). φ Int → φ Int
{ l_t = ⟨⟨ Int ⟩⟩
l_f = \(x : ⟨⟨ Int ⟩⟩).
}
where ⟨⟨ Int ⟩⟩ : ⋆ = ∀(φ : ⋆ → ⋆). φ Int → φ Int
Question: are existentials required to lift out of mu binders? (maybe due to the need of functor application?) (or, with equi-recursive types, they are just equal to existentials (in unfolded form))
References:
(from Retrofitting and strengthening the ML module system)
first class modules have to be generative functors?
avoidance can account for many mechanisms (e.g. shadowing)
zipML
-
core idea: if a field is avoided, you must find an alias … s.t.
-
no complex stuff
-
potential for scale? (with applicative functor)
-
best effort type checking:
module recshould subsumemodule- whether
Approx(type t = Int) = (type t), depends on “best effort” - constraint resolution
- whether