Logical relations are used to prove things about programs
- Unary equivalence
- Example: Prove termination of the STLC
- Example: Prove type soundness / safety (these lectures)
- Binary equivalence
- Example: are two programs going to behave the same when you put them in some different context? (*contextual equivalence*)
-
Representation independence
- Example: suppose u implement a stack and have 2 different implementations. Prove that it doesn't matter which implementation you use.
- Parametricity for existential types "there exists a stack such that these properties are true"
- Parametricity
- Security levels of data in a security programming language
- Establish that high-security data never flows to low-security observers (*non-interference*)
You can also relate source terms to target terms in different languages.
This can be used to prove compiler correctness.
== Type safety / soundness
Unary realizability relations for FFI. "all well-behaved terms from language A can be used in language B without causing problems with either language"
Unsafe code blocks are an example of a something that may not be _syntactically well-formed_, but are still represented by logical relations because they behave the same at the boundary.
The difference between semantic soundness with logical relations vs. progress and preservation is that you don't need to prove anything about intermediate states of running programs.
Can't just work with closed terms because of this rule:
*Definition (Fundamental property of logical relations).*
Theorem we are trying to prove
$
Gamma tack.r e : tau arrow.r.double Gamma tack.r.double e : tau
$
- "syntactically well typed means semantically well typed"
]
Focus on terms with open variables
Use $gamma$ as a substitution from variables to values.
#rect[$G db(Gamma)$]
- $G db(dot) = { emptyset }$
- $G #db[$Gamma , x : tau$] = { gamma[x mapsto v] | gamma in G db(Gamma) and v in V db(tau)}$
"sound inputs will give you sound outputs". This is the important case
Now define semantic soundness:
$
Gamma tack.r.double e : tau :equiv forall (gamma in G db(Gamma)) . gamma(e) in Epsilon db(tau)$
Prove:
#set enum(numbering: "a.")
1. $dot tack.r e : tau arrow.r.double e in Epsilon db(tau)$
- this cannot be proved by focusing on closed terms alone
- This is why the fundamental property must be used, in the case of $Gamma = dot$, then the substitution $gamma$ is empty and the result is $e in Epsilon db(tau)$
2. $e in Epsilon db(tau) arrow.r.double safe(e)$
#set enum(numbering: "1.")
To prove A:
_Proof._ By induction on typing derivations
-
Case example:
#tree(
axi[],
uni[$Gamma tack.r "true" : "bool"$]
)
Show $Gamma tack.r.double "true" : "bool"$. Suppose $gamma in G db(Gamma)$.
Show $gamma("true") in Epsilon db("bool") equiv "true" in Epsilon db("bool")$.
Suffices to show $"true" in V db("bool")$, which is true by definition.
False is proved similarly.
-
Case example:
#tree(
axi[$Gamma(x) = tau$],
uni[$Gamma tack.r x : tau$]
)
Show $Gamma tack.r.double x : tau$. #TODO
To prove B:
_Proof._ Suppose $e'$ s.t. $e mapstostar e'$ .
- Case : $not "irreducible" (e')$ then $exists e'' . e' mapsto e''$
- Case : $"irreducible" (e')$ then $isValue(e')$ trivially.