Semantic type soundness and language interop


Re

τ := bool
   | τ₁ → τ₂

---

V⟦τ⟧: Values of type τ which don't get stuck

V⟦bool⟧ = {true, false}
V⟦τ₁ → τ₂⟧ =
  {λx:τ₁. e
   | ∀v ∈ V[τ₁],
     e[v/x] ∈ E[τ₂]}

---

E⟦τ⟧: Expressions which will end up being of type τ

E⟦τ⟧ =
  {e | ∀e',
       e ↦* e' ∧ irred(e') ∧ e' ∈ V[τ]}

---

G: Substitution for assignments
G⟦⋅⟧ = {∅}

Γ

γ


D1

STLC

Logical relations

Observational equiv. Output program of the compiler is related to the input program.


STLC that we consider:

τ := bool | τ1 → τ2
e := x
   | true | false
   | if e then e1 else e2
   | λx:τ. e
   | e1 e2
v := true | false
   | λx:τ. e

Remark: λ comes in e and v. e is specified, then we say what are the vals. and λ is a val. Choice of values are important when designing lang. Eg: (e1, e2) itself could be considered val. But we could also have said only (v1,v2) can be a value.

Value lambda is an e suspended computations. Cannot be run till applied.

Operational semantics (call-by-value, left-to-right evaluation):

Evaluation context.

E := [⋅]
   | if E then e1 else e2
   | E e2      (Evaluate left first)
   | v1 E      (Evaluate right only after left)

Reduction rules:

# e ↦ e'

if true then e1 else e2 ↦ e1

if false then e1 else e2 ↦ e2

(λx:τ. e) v  ↦  e[v/x]

  e ↦ e'
────────────
E[e] ↦ E[e']

Context Γ:

Γ := ⋅         (empty)
   | Γ, x:τ    (cons)

Shape of typing judgement ??

# Γ ⊢ e: τ

Γ(x) = τ
─────────
 Γ ⊢ x:τ


──────────────    ───────────────
Γ ⊢ true: bool    Γ ⊢ false: bool



   Γ, x:τ1 ⊢ e:τ2          Γ ⊢ e1:τ1→τ2    Γ ⊢ e2:τ1    
─────────────────────      ─────────────────────────        
Γ ⊢ (λx:τ1. e): τ1→τ2          Γ ⊢ (e1 e2): τ2              



 Γ ⊢ e:bool   Γ ⊢ e1:τ    Γ ⊢ e2:τ
───────────────────────────────────
   Γ ⊢ (if e then e1 else e2): τ

Features defining a language:


How to prove type soundness.

Type soundness

Milner's slogan.

Type soundness: (à la Milner's slogan):

∀e', e ↦* e' then either
 val(e') or       (ie, already a value)
 ∃e'', e' ↦ e''   (ie, one more step possible)

What if e'' is a stuck term? That scenario is ruled out because the base condition says it should end up being a value.

DBT: For subtypes the above two need to be generalized.

For the type soundness proof, we can ping-pong between these two lemmas.

This is a syntactic way of proving type soundness.

Progress and preservation is just one technique, although the most popular one, to prove type soundness.

This lecture series will show how to prove type soundness semantically. Using logical relations.

DBT: From 80s till mid 2000s everyone used progress and preservation for type soundness proofs. What happened then?

In the 1980s, people used to prove type soundness using denotational semantics.

1990s, Andrew Wright and Matthias Felleisen[1]. Beginning of a proof method to prove soundness without using denotational semantics.

Denosem => give a mathematical interpreation to program and make sure everything is consistent.

80s: deno sem. math interp to progs 94: Andrew wright.

This is synatically proving type correctenss. This lecture series about proving this semantically. Using logrels.

Logical relations

Setting up relations that let us prove some property of interest.

We will be looking at type soundness of STLC. That means showing absence of stuck terms.

Pₜ(e)

Set of programs indexed by the different types of the language.

STLC that we consider => types are just bool and functions. Unary relations. More types => more relations

For binary relations, it would be like: Rₜ(e1, e2)

Unary relation:

Binary relation:

Our type soundness theorem for STLC:

⋅⊢e: τ then safe(e)

ie, if e has a type τ, then e is type safe

where safe is defined as

safe(e) ≝ ∀e', if e↦*e' then val(e') or ∃e'', e'↦e''

Now to define the relations.

We could define relations on expressions. But let's try a more common style.

DBT: Why is the other style more common?

2 parts:

— Value relation: What are all the sets of values v such that v behaves like something of type τ and doesn't get stuck.

V⟦τ⟧: set of all values which can be of type τ and don't get stuck
 = {v | .. }

V⟦bool⟧ = {true, false}

V⟦τ1→τ2⟧ = {λx.τ1. e | ... }
  ∀v ∈ V⟦τ1⟧
  e[v/x] ∈ E⟦τ2⟧

This has to behave like a τ1→τ2 even when we put it in a different program. So just checking if it's a value isn't enough.

Amal gave this example: λx:bool. x true

Also, λ is already a value. Could've stopped here, but that would mean getting stuck.

Logical relations give a stronger induction principle.

We got to eliminate stuff like true false which isn't even a proper function application??.

irreducible: cannot take another redn.

Note that V⟦τ1→τ2⟧ doesn't ask for a typing derivation. It only asks whether it behaves as expected.

Expression type

E[τ] is a relation indicating the family of expressions which behave like a τ.

E[τ] = {e | ∀e', e ↦* e' ∧ irred(e') ∧ e' ∈ V⟦τ⟧}

where

irred(e) ≝ ∄e', e ↦* e'

irred(e) means e cannot take another step. Which means it's either stuck or is a value.

Question: what if e diverges?

Omega combinator: The famous divergent term

Question: Example of a term which is not syntactically well-typed but still belongs to the relation?

Semantic type sounndess using logrel stuff method doesn't need the program to finish running. Doesn't need a complete derivation tree ??

Can't limit ourselves to proofs about closed terms. We need open progs as well ∵ of the λ typing rule

Semantics type soundness theorem: ⋅⊢ e:τ then safe(e)

⊢ : syntactically well-typed ⊨ : semantically well-typed

For open progs, G = Substitution for assignments

G[⋅] = {∅} G[Γ,x:τ] = { γ[x↦v] | γ ∈ G[Γ] ∧ v ∈ V[τ]}

γ is value assignments to variables. Eg: γ = {x1↦v1, x2↦v2, …}

Γ ⊨ e:τ =def ∀γ∈G[Γ], γ(e) ∈ E[τ]

Logrel: If you give me input of desired relation, I'll give you output of desired relation

Fundamental property of logical relations

Adequacy of a logrel

A) ⋅↦e:τ => ⋅ ⊨ e:τ A) ⋅↦e:τ => e ∈ E[τ]

B) ⋅⊨

B should be trivial if the logrel was built properly

Case 1: ¬irred(e)

Then

FP Γ⊢e:τ => Γ⊨e:τ

Induction of typing derivation.

Case: Γ⊢true:bool To prove: Γ⊨true:bool

Suppose γ∈G[Γ]

TAPL: Logical realtions section in Normalization

D2

behaviour: what happens when we run it? compositionality building blocks

Granularity of type ??

Add new types -> we will have new typing rules (intro, elim), then got to show that those rules are sound too.

logrel comes into play after a type system is already there.

FP:

Γ,x:τ implicitly means x ∈¬ Γ (TAPL)

HW: Application case (lambda was done in class)

The lang as it is now is terminating.

Let's allow non-terminating progs => Add recursive types

unit type useful for stuff like repring nil of lists and leaf of tree

type tree = Leaf
          | Node of int * tree * tree
tree = 1 + (int * tree * tree)

type itself shows up in definition

many possible 'views' (ie, unrollings??)

Fixpoint: x is fixpoint of f if x = f(x)

F := λα:type. 1 + (int * α * α)

We need a fixpoint builder. General.

<skip>

fold and unfold of a recursive type.

Examples of recursive types:

tree = μα. 1 + (int * α * α)
intlist = μα. 1 + (int * α)

where α is the type argument.

τ := bool 
   | τ → τ
   | 1
   | α
   | μα. τ

View shifters.

Let's try to type check the Ω-combinator.

Ω = (λx. x x) (λx. x x)

Without rectypes, x could be τ1 → τ2 or τ.

self application part (x x): (unfold x) x rectype in neg pos!

We have type rules. Now need eval rules??

V := fold V
   | ...
   | ...

E := fold E
   | ...

Redex rule:

unfold (fold v) = v

V[μα. τ] = {fold v | }

smeantic breacket eval arrow big 1 , 0

Andrew Pitts ATAPL last exercise

mutable refs: still unsovled ??

Step-indexing Logrel indexed by a natural number

Now induction is done on step count instead of the structure of type.

gas is similar concept but for synactic-oriented stuff

Vₖ

Each redn step of the opsem = 1 step Kinda like possible worlds model. monotonicity

D3

Step indexing paper (proof carrying code): 2001 TOPLAS Appel + McCallester 2004: Ahmed PhD on mutable refs

iris: step indexing fold case of fundamental property from last day:

Reminder: Γ⊢e:τ => Γ⊨e:τ

Γ⊢e: τ[μα.τ/α]


HW: Same for step-indexed λ

monotonicity: if a val behaves like some type τ for k steps, then it has to behave like τ itself for <k steps too.

Got to prove monotonicity for all types

monotonicity and contravariance in funs

k steps = k steps to interact with it. ie, to use it.

syntactic correctness -> semantic correctness

memory location info not needed for prog-preservation approach for type safety, but not here ???


Mutable references.

STLC with allocation and stuff: type of reference/pointer to a value of τ read/write from refs

τ := 1 | bool | τ1→τ2
   | ref τ    (type of ref to some mem loc which has a value of type τ)

e := () | ...  
   | new e      (create a new ref)
   | !e         (dereference/read)
   | e1 := e2   (assignment. overwrite if needed)

v := true | false | () | λx. ?
   |  l         (location)

Opsem:

One of: s(tore), m(emory), h(eap)

S: Loc -> Closed val l ↦ v ∈ S

Old rules has S, but unchanging

(S,e) -> (S',e')
────────────────────────────────────────────── 
(S, new v) -> (S[l↦v], l) where l ∈¬ dom(S)


(S, !l) -> (S, v) where l↦v ∈ S

(S, l:=v) -> (S[l↦v], ())  ie, update S, return unit

Eval context: call by val

E := new E
   | !E
   | E := e2
   | v := E

f(x: ref bool, y: ref bool) { free x; !y }

Aliasing problem. 2 ptrs pointing to same location. Freeing one would cause problem for others. What can type system do to prevent this?

Reason why once allocated, type can't be changed.

rust handles aliasing nicely ??

This lang is non-terminating. Loops possible. Eg:

let x: (unit -> unit) ref := new (λz. z)
let f = λz:unit. (!x) z
x := f
f tt

Named Landin's knot.

Infinite loop.

ie, STLC + refs => inf loop possible.

f = λz.

Type preserving updates. aka strong updates. GC is like refreshing mem so that it can be used for other types afterwards.

Review of what seen so far:

Possible world semantics. Meaning with respect to current world.

Typing rules with refs:

Γ ⊢ e:τ
----------------
Γ ⊢ new e: ref τ


Γ ⊢ e: ref τ
----------------
Γ ⊢ !e: τ


Γ ⊢ e1: ref τ       Γ⊢e2:τ
---------------------------
Γ ⊢ e1:=e2: 1

ie, 2 elim rules for ref

ψ location to type mappings New locations can come in, but their type shouldn't change => type preserving updates

D4

ψ = mapping from locations to semantic types (not syntactic types) = {l1↦V[τ1], l2↦V[τ2], ..} = store typing

S = mapping from locations to vals

Type world circularity Stratification to avoid circularity

For functions where there is mutref.

accessibility relation

V(k,ψ)[τ1→τ2] = {

possible worlds model is same as kripke models ??

D5

if true then 5 else false

semantically well-defined E⟦int⟧

but not syntactically


Our logrel was unary??? Binary also possible.

Re

References

[1]: Wright, A.K. and Felleisen, M., 1994. A syntactic approach to type soundness. Information and computation, 115(1), pp.38-94.ʰ