0% found this document useful (0 votes)
4 views39 pages

Understanding Type Systems in Programming

The document discusses the principles of type systems in programming languages, focusing on the differences between strongly-typed languages like Lean 4 and weakly-typed languages like JavaScript. It outlines the importance of static type checking, typing rules, and properties such as progress, preservation, and soundness, which ensure that well-typed programs do not encounter runtime type errors. The lecture aims to define a type system for mixed arithmetic and boolean expressions and prove its desired properties.

Uploaded by

Tudor Romaniuc
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
4 views39 pages

Understanding Type Systems in Programming

The document discusses the principles of type systems in programming languages, focusing on the differences between strongly-typed languages like Lean 4 and weakly-typed languages like JavaScript. It outlines the importance of static type checking, typing rules, and properties such as progress, preservation, and soundness, which ensure that well-typed programs do not encounter runtime type errors. The lecture aims to define a type system for mixed arithmetic and boolean expressions and prove its desired properties.

Uploaded by

Tudor Romaniuc
Copyright
© All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd

Principles of Programming Languages

Type Systems

Andrei Arusoaie1
1 Department of Computer Science
Outline

Motivation
Outline

Motivation

Typing
Outline

Motivation

Typing

Typing rules
Outline

Motivation

Typing

Typing rules

Properties
Static type checking

▶ Type systems are essential in programming languages


▶ Most languages have values categorised in certain types
▶ Static type systems perform compile-time type checking
▶ Type checkers verify that operations are applied to values
of appropriate types
▶ Type safety: A well-typed program cannot produce type
errors at runtime
▶ Lean 4 is a dependently-typed proof assistant with a strong
static type system
Motivation

Type errors in Lean 4:

-- Lean 4 type error example


#check true + false
-- application type mismatch
-- true + false
-- argument
-- true
-- has type
-- Bool : Type
-- but is expected to have a numeric type
Motivation

... but in Javascript:

// Welcome to [Link] v18.16.0.


// Type ".help" for more information.
> true + false
1
> 2 + '4'
'24'
> '2' + 4
'24'
> 2 * '4'
8
> '2' * 4
8
Motivation

What is the result type of && in Javascript?


// Welcome to [Link] v18.16.0.
// Type ".help" for more information.
> 0 && true
0
> true && 0
0
> 0 && false
0
> false && 0
false
Motivation

Confusing outputs in Javascript:


// Welcome to [Link] v18.16.0.
// Type ".help" for more information.
> false == []
true
> false == ![]
true
> [] == ![]
true
Strongly vs. Weakly typed languages

▶ Lean 4 is an example of a strongly-typed language


▶ Javascript is an example of a weakly-typed language
Strongly vs. Weakly typed languages

▶ Lean 4 is an example of a strongly-typed language


▶ Javascript is an example of a weakly-typed language
▶ Strongly-typed languages: type safety, readability,
performance vs. less flexibility, steep learning curve
Strongly vs. Weakly typed languages

▶ Lean 4 is an example of a strongly-typed language


▶ Javascript is an example of a weakly-typed language
▶ Strongly-typed languages: type safety, readability,
performance vs. less flexibility, steep learning curve
▶ Weakly-typed languages: flexibility, easy to learn, less
verbose code vs. runtime errors, no type safety, possible
less performance, debugging troubles
Goal for this lecture

Our goal in this lecture is to understand how to define a type


system and what are its desired properties.
Steps:
▶ We first mix the arithmetic and boolean expressions
▶ We define the small-step SOS for such expressions
▶ We define a type system for these expressions
▶ We formulate and prove the following properties for our
type system: progress, preservation, soundness
Abstract Syntax

▶ We define a unified expression type combining arithmetic


and boolean expressions
▶ Inductive types define syntax compositionally through
constructors
▶ This allows mixing expressions of different types
(potentially causing type errors)
▶ Coercions can be used to make certain types implicitly
convertible
▶ Notation provides syntactic sugar for better readability
Mixing boolean and arithmetic expressions
inductive Exp where
| anum : Int -> Exp
| avar : String -> Exp
| aplus : Exp -> Exp -> Exp
| amult : Exp -> Exp -> Exp
| btrue : Exp
| bfalse : Exp
| bnot : Exp -> Exp
| band : Exp -> Exp -> Exp
| blessthan : Exp -> Exp -> Exp
deriving Repr

open Exp

instance : OfNat Exp n where


ofNat := anum n
instance : Coe String Exp where
coe := avar

infixl:50 " +' " => aplus


infixl:40 " *' " => amult
infixl:60 " <' " => blessthan
infixl:75 " &&' " => band
Remember: Small-step SOS

▶ Small-step semantics defines how expressions reduce one


step at a time
▶ An environment (Env) maps variables to their values
sigma
▶ Evaluation relation e1 −−−→ e2 means expression e1
reduces to e2 in environment sigma
▶ Evaluation proceeds by pattern matching on expression
structure
▶ Reduction rules specify how to evaluate each syntactic
construct
▶ Some expressions may get stuck if they are ill-typed
Small-Step SOS for mixed expressions

abbrev Env := String -> Nat

inductive eval : Exp -> Env -> Exp -> Prop where
| const : forall i sigma,
eval (anum i) sigma (anum i)
| lookup : forall x sigma,
eval (avar x) sigma (anum (sigma x))
| add_l : forall a1 a2 sigma a1',
eval a1 sigma a1' ->
eval (a1 +' a2) sigma (a1' +' a2)
| add_r : forall a1 a2 sigma a2',
eval a2 sigma a2' ->
eval (a1 +' a2) sigma (a1 +' a2')
| add : forall i1 i2 sigma n,
n = i1 + i2 ->
eval (anum i1 +' anum i2) sigma (anum n)
-- ... (similar rules for mult, lessthan, not, band)

open eval
notation:98 A " -[ " S " ]-> " B => eval A S B
Remember: reflexive-transitive closure

▶ The reflexive-transitive closure represents zero or more


evaluation steps
▶ Reflexivity: Every expression can take zero steps to itself
▶ Transitivity: If e1 → e2 and e2 →∗ e3 , then e1 →∗ e3
▶ This captures multi-step evaluation from an expression to
its final result
▶ Well-typed expressions evaluate to values
▶ Ill-typed expressions may get stuck during evaluation
Well-typed expressions

inductive eval_closure : Exp -> Env -> Exp -> Prop where
| refl : forall e sigma,
eval_closure e sigma e
| tran : forall e1 e2 e3 sigma,
eval e1 sigma e2 ->
eval_closure e2 sigma e3 ->
eval_closure e1 sigma e3
open eval_closure
notation:99 A " -[ " S " ]>* " B => eval_closure A S B

def Env0 : Env := fun _ => 0

example : (2 +' "n") -[ Env0 ]>* 2 := by


apply tran (e2 := 2 +' 0)
· apply add_r
apply lookup
· apply tran
· apply add
rfl
· apply refl

▶ Note: the expression 2 +' "n" is evaluated to a value.


Ill-typed expressions

▶ It is impossible to obtain a value for ill-typed expressions


using eval
▶ In such cases we say that 2 +’ btrue is stuck:
2 +’ btrue does not evaluate to a value and no
transitions via eval are possible from it
▶ Terms that are not values and cannot be reduced to values
are stuck
▶ How do we detect stuck terms? Define a type system to
exclude nonsensical terms
A type system for expressions

▶ Our expressions include both numbers and boolean values


▶ To exclude terms we don’t want to have meaning (e.g., 2 +’
btrue), we define a typing relation
▶ The typing relation relates terms to the types of their final
(evaluated) results
▶ Types classify expressions into categories (e.g., Nat, Bool)
▶ The typing relation e : T means expression e has type T
▶ Type checking verifies that expressions satisfy typing rules
Adding types

▶ Types: Bool and Nat


▶ Lean 4:
inductive Typ where
| Nat : Typ
| Bool : Typ
deriving Repr, DecidableEq
open Typ
▶ We define a relation :⊆ Exp × Typ using inference rules for
typing
▶ When e : Nat we say that the type of e is Nat
▶ Checking whether an expression has a certain type
reduces to finding a derivation (proof) using the typing
rules
Typing rules - I

n∈N
anum n : Nat TNUM

x ∈ Var
avar x : Nat TVAR

a1 : Nat a2 : Nat
TPLUS
a1 +′ a2 : Nat

a1 : Nat a2 : Nat
TMUL
a1 ∗′ a2 : Nat
Typing rules - example

2∈N x ∈ Var
TNUM
2 : Nat x : Nat TVAR
TPLUS
2 +′ x : Nat
Typing rules - II

·
TTRUE
btrue : Bool

·
TFALSE
bfalse : Bool

b : Bool
TNOT
bnot b : Bool

b1 : Bool b2 : Bool
TAND
band b1 b2 : Bool

a1 : Nat a2 : Nat
TLT
a1 <′ a2 : Bool
Typing rules - example

2∈N x ∈ Var
TNUM
2 : Nat x : Nat TVAR
′ TLEQ
2 < x : Bool
Typing derivations

▶ Typing derivations are proof trees showing that


expressions are well-typed
▶ Each node in the tree corresponds to a typing rule
application
▶ inductive in Lean 4 naturally encode typing rules
▶ Type derivation proofs demonstrate that an expression has
a particular type
▶ Typing rules are syntax-directed: each expression form
has a specific rule
▶ Typing derivations are different from evaluating
expressions!!
Typing rules - I - in Lean 4

inductive type_of : Exp -> Typ -> Prop where


| t_num : forall n,
type_of (anum n) Nat
| t_var : forall x,
type_of (avar x) Nat
| t_plus : forall a1 a2,
type_of a1 Nat ->
type_of a2 Nat ->
type_of (a1 +' a2) Nat
| t_mult : forall a1 a2,
type_of a1 Nat ->
type_of a2 Nat ->
type_of (a1 *' a2) Nat
-- ...
Typing rules - II - in Lean 4
inductive type_of : Exp -> Typ -> Prop where
-- ...
| t_true :
type_of btrue Bool
| t_false :
type_of bfalse Bool
| t_not : forall b,
type_of b Bool ->
type_of (bnot b) Bool
| t_and : forall b1 b2,
type_of b1 Bool ->
type_of b2 Bool ->
type_of (band b1 b2) Bool
| t_lessthan : forall a1 a2,
type_of a1 Nat ->
type_of a2 Nat ->
type_of (blessthan a1 a2) Bool
Values and canonical forms

▶ Terms that cannot be reduced anymore are said to be in a


normal form
▶ Values are fully evaluated expressions that cannot be
reduced further
▶ For natural numbers: the value is anum n for some n ∈ N
▶ For booleans: the values are btrue and bfalse
▶ Canonical forms lemma: If an expression is both a value
and well-typed, it must have a specific form
▶ If e is a value and e : Bool, then e is either btrue or
bfalse
▶ These lemmas are crucial for proving type safety
properties
Values

inductive nat_value : Exp -> Prop where


| n_val : forall n, nat_value (anum n)

inductive bool_value : Exp -> Prop where


| b_true : bool_value btrue
| b_false : bool_value bfalse

def value (e : Exp) : Prop :=


nat_value e \/ bool_value e
Canonical forms

theorem bool_canonical :
forall e,
type_of e Bool ->
value e ->
bool_value e := by ...

theorem nat_canonical :
forall e,
type_of e Nat ->
value e ->
nat_value e := by ...
Progress

▶ Progress captures the fact that well-typed expressions


make progress
▶ Progress: A well-typed expression is either a value or can
take a step
▶ Formally: If ⊢ e : T , then either e is a value or ∃e′ such that
e → e′
▶ Progress guarantees that well-typed programs never get
stuck
▶ This is half of type safety: well-typed programs don’t
encounter runtime type errors
▶ Proof typically proceeds by induction on the typing
derivation
▶ Each case corresponds to a typing rule
Progress theorem

theorem progress :
forall e T sigma,
type_of e T ->
(value e \/ exists e', e -[ sigma ]-> e') :=
by sorry
Type preservation

▶ Type preservation (Subject Reduction) is critical for type


safety
▶ It says: if a well-typed expression is reduced in one step,
the result is also well-typed
▶ Formally: If ⊢ e : T and e → e′ , then ⊢ e′ : T
▶ Preservation ensures that evaluation preserves types
▶ This is the other half of type safety
▶ Together with progress, preservation implies that
well-typed programs can only produce well-typed results
▶ Proof typically by induction on the typing derivation
Type preservation theorem

theorem preservation :
forall e e' T sigma,
type_of e T ->
(e -[ sigma ]-> e') ->
type_of e' T :=
by sorry
Type soundness

▶ Type Soundness: “Well-typed programs don’t go wrong”


▶ Combines progress and preservation for multi-step
evaluation
▶ If ⊢ e : T and e →∗ e′ , then either e′ is a value or ∃e′′ such
that e′ → e′′
▶ Soundness theorem is a corollary of progress and
preservation
▶ Proof by induction on the reflexive-transitive closure
▶ This is the fundamental safety guarantee of the type
system
▶ Well-typed programs never get stuck during execution
▶ A well-typed expression cannot reach a stuck state
Type soundness theorem

theorem soundness :
forall e e' T sigma,
type_of e T ->
(e -[ sigma ]>* e') ->
(value e' \/ exists e'', e' -[ sigma ]-> e'') :=

The implications of type soundness are very strong: if a


program can be proved to be well-typed, then it is guaranteed
not to produce erroneous results.

You might also like