Rewrite semantics for guarded recursion with universal quantification over clocks (Work in progress) Aleš Bizjak 1 Rasmus Møgelberg 2 1 Aarhus University 2 IT University of Copenhagen May 18, 2015
Overview Background Guarded recursion Topos of trees model Multiple clocks and codata New model of clock synchronization Towards rewrite semantics Ensuring termination Computational content of type isomorphisms Generating fresh names of clocks
Background
Productivity Two stream definitions zeros = 0::zeros xs = xs Combining with higher order functions mergef:(int int S(int) S(int)) S(int) S(int) S(int) mergef f x::xs y::ys = f x y (mergef f xs ys)
Capturing productivity in types Introduce modal operator S(int) = µx.int X hd: S(int) int tail: S(int) S(int) cons: int S(int) S(int) Fixed points fix: ( S(int) S(int)) S(int) zeros = fix(λxs.0::xs)
Capturing productivity in types is an applicative functor next: X X : (X Y) X Y Typing mergef mergef:(int int S(int) S(int)) S(int) S(int) S(int) mergef f = fix(λg.λ(x::xs)λ(y::ys).f x y (g xs ys)) where g: (S(int) S(int) S(int))
The topos of trees
The topos of trees (Set ωop ) Objects X (1) r 1 X (2) r 2 X (3)... Example: object of streams of integers S(int) Z π Z 2 π Z 3 π... Define X { } X (1) X (2)... Note that S(int) = Z S(int): Z 1 Z! Z Z Z π Z Z 2 Z π...
Coinductive types via guarded recursive types
A problem S(int) = µx.int X hd: S(int) int tail: S(int) S(int) cons: int S(int) S(int) Computing the second element snd = (λxs.next(hd) tail(xs)): S(int) int How to get rid of?
Guarded recursion vs coinduction in model Guarded recursion useful when constructing streams Would like to use coinductive streams when taking them apart Observation: Limit of guarded recursive streams is set of real streams! Z π Z 2 π Z 3 π...
Multiple clocks Idea due to Atkey and McBride (simply typed setting only) Møgelberg extended it to dependent types using topos of trees model Clock variable context = κ 1,..., κ n etc ; Γ A : Type κ ; Γ κ A : Type fix κ : ( κ X X ) X
Universal quantification over clocks, κ; Γ A : Type κ / fc(γ) ; Γ κ.a : Type, κ; Γ t : A κ / fc(γ) ; Γ Λκ.t : κ.a ; Γ t : κ.a κ ; Γ t[κ ] : A[κ /κ] Clock quantification is right adjoint to clock weakening
Clock synchronisation Previous version of rule for clock application κ, κ / fc(γ, κ.a), κ; Γ A : Type, κ ; Γ, Γ t : κ.a, κ ; Γ, Γ t[κ ] : A[κ /κ] Dissallowed clock synchronisation Clock synchronisation proved consistent using new model.
A new model (MFPS 2015) Type with 0 free clocks is a set Type with 1 free clock is an object in the topos of trees X (1) r 1 X (2) r 2 X (3)... Type with 2 free clocks modelled as diagram of form.... X (1, 3) X (2, 3) X (3, 3) X (3) X (1, 2) X (2, 2) X (3, 2) X (2) X (1, 1) X (2, 1) X (3, 1) X (1)
Type isomorphisms
Structure maps S κ (int) = int κ S κ (int) S(int) = κ.s κ (int) Structure map constructed using series of isomorphisms S(int) = κ.(int κ S κ (int)) = ( κ.int) ( κ. κ S κ (int)) = int ( κ.s κ (int)) = int S(int)
Type isomorphisms in model If κ / fc(a) then κ.a is limit of [[A]] id [[A]] id [[A]] id... So A = κ.a if κ / fc(a) Limit of [[A]](1) [[A]](2) [[A]](3)... Isomorphic to limit of 1 [[A]](1) [[A]](2) [[A]](3)... So κ.a = κ. κ A
Isomorphism κ.a = κ. κ A Left to right definable λ(x: κ.a).λκ.next κ (x[κ]) Unrestricted eliminator for κ unsound: ; Γ t : κ A ; Γ prev κ t : A Implies all types inhabited fix κ (λx : κ A.prev κ x) : A
Isomorphism κ.a = κ. κ A A sound, restricted eliminator for κ : ; Γ t : κ A κ / fc(γ) ; Γ prev κ t : A But type judgement not closed under substitution in Γ
Isomorphism κ.a = κ. κ A Could use explicit substitutions ; Γ t : κ A κ / fc(γ) ρ : Γ Γ ; Γ prev κ ρ.t : A With Γ bound in prev κ ρ.t. We choose the alternative ; Γ t : κ A κ / fc(γ) ; Γ prev κ t : κ.a Binding κ
Isomorphism κ.a = A (for κ / fc(a)) Compare to System F type iso α : Type.int = int In this setting need to add a construct ; Γ t : κ.a κ / fc(a) ; Γ af t : A Pronounced apply at fresh
Rewrite semantics
Rewrite semantics Goals: normalisation and canonical forms Need restriction to prevent infinite unfolding of fix Strategy: Never reduce under next Will not have general confluence (λx.next κ (x))m (λx.next κ (x))n next κ (M) Model ensures that normal forms of type int are unique
Beta reductions (λx.t) s β t[s/x] π 1 s, t β s π 2 s, t β t (Λκ.t) [ κ ] β t[κ /κ] prev κ next κ t β Λκ.t fix κ (t) β t(next κ (fix κ (t))) af Λκ.t β t if κ fc(t)
Example Recall two types of streams S κ (int) = int κ S κ (int) S(int) = κ.s κ (int) Structure maps hd = λxs. af (Λκ.π 1 (xs[κ])) tl = λxs.prev κ (π 2 (xs[κ])) : S(int) int : S(int) S(int) A stream of zeros zeros κ = fix κ (λxs. 0, xs ) : S κ (int) zeros = Λκ.zeros κ : S(int)
Example hd = λxs. af (Λκ.π 1 (xs[κ])) tl = λxs.prev κ (π 2 (xs[κ])) : S(int) int zeros κ = fix κ (λxs. 0, xs ) : S κ (int) zeros = Λκ.zeros κ : S(int) : S(int) S(int) Reducing hd(zeros) β af (Λκ.π 1 (zeros[κ])) β af (Λκ.π 1 (zeros κ )) β af (Λκ.π 1 0, next κ (zeros κ ) ) β af (Λκ.0) β 0
Example hd = λxs. af (Λκ.π 1 (xs[κ])) tl = λxs.prev κ (π 2 (xs[κ])) : S(int) int zeros κ = fix κ (λxs. 0, xs ) : S κ (int) zeros = Λκ.zeros κ : S(int) : S(int) S(int) Reducing tl(zeros) β prev κ (π 2 (zeros[κ])) β prev κ (π 2 (zeros κ )) β prev κ (π 2 ( 0, next κ (zeros κ ) )) β prev κ (next κ (zeros κ )) β Λκ.zeros κ = zeros
Example hd = λxs. af (Λκ.π 1 (xs[κ])) tl = λxs.prev κ (π 2 (xs[κ])) : S(int) int zeros κ = fix κ (λxs. 0, xs ) : S κ (int) zeros = Λκ.zeros κ : S(int) : S(int) S(int) Putting these together hd(tl(zeros)) β hd(zeros) β 0
Commuting conversions
Commuting conversions af Λκ.next κ t c next κ (af Λκ.t) if κ κ af Λκ. t, s c af Λκ.t, af Λκ.s af Λκ.λx.t c λx. af Λκ.t af Λκ.Λκ.t c Λκ. af Λκ.t if κ κ next κ t κ next κ s c next κ (t s) Recall af Λκ.t β t if κ fc(t) Similar to Odersky-style local names Similar to restriction operation in nominal sets
Commuting conversions Needed to ensure progress ( ( ( af prev κ af Λκ.next κ (prev κ next κ 0) [ κ ]))) β af (prev κ next κ( af Λκ.(prev κ next κ 0) [ κ ])) β af Λκ. (af Λκ.(prev κ next κ 0) [ κ ]) β af Λκ. ( af Λκ.(Λκ.0) [ κ ]) β af Λκ. ( af Λκ.0 ) β af Λκ.0 β 0 : int
Conclusions Guarded recursive types in combination with clock quantification useful for programming with codata New model shows clock synchronisation is safe Towards computational content Apply at fresh: ( κ.int) int Commuting conversions: Odersky style local names Partial eliminator for κ Never evaluate under next Future work: theorems about rewrite semantics