Concepts of Program Design Type safety and exception handling Gabriele Keller Ron Vanderfeesten
Overview higher & first-order syntax inference rules, induction tools to talk about languages abstract machines big step and small step operational semantics λ -calculus and de Bruijn indices value & type environments parametric polymorphism/ generics type safety control stacks error/exception handling semantic features partial application/function closures functional (algebraic) data types static & dynamic static & dynamic typing scoping language concepts explicit & implicit explicit & implicit procedural/imperative typing typing
Types in programming languages statically vs dynamically typed type safe vs non type safe strongly vs weakly typed
Static and Dynamic Semantics • MinHs (as discussed in the lecture) is a type-safe (or strongly typed) language • What exactly do we mean by this? - these terms are used by different authors to mean different things - in general, it refers to guarantees about the run-time behaviour derived from static properties of the program - Robin Milner: “ Well typed programs never go wrong ” ‣ do not exhibit undefined behaviour - we define type safety to be the following two properties: ‣ preservation ‣ progress - we look at both preservation and progress in turn
Preservation and Progress • Preservation: - Idea: evaluation does not change the type of an expression - Formally: If ⊢ e : τ and e ↦ e’ , then ⊢ e’ : τ • Progress: - Idea: a well-defined program can not get stuck - Formally: If e is well typed, then either e is a final state, or there exists e’ , with e ↦ e’ e 1 : τ ↦ e 2 : τ ↦ e 3 : τ ↦ … • Together is means that a program will either evaluate to a value of the promised type, or run forever
Type Safety • For any language to be type safe, the progress and preservation properties need to hold! • Strictly speaking, the term type safety only makes sense in the context of a formal static and dynamic semantics • This is one reason why formal methods in programming languages are essential • The more expressive a type system is, the more information and assertions the type checker can derive at compile type - type systems usually should be decidable - but there are exceptions • MinHs is type safe - we can show that progress and preservation hold! - but what if the language contains partial operations, like division?
Run-time Errors and Safety • Stuck states: in a type safe language language, stuck states correspond to ill- defined programs, e.g., - use (+) on values of function type, for example - treat an integer value as a pointer - use an integer as function let x = 1 in x 5 • Unsafe languages/operations do not get stuck - something happens, but its not predictable and/or portable: void boom () { void (f*)(void) = 0xdeadbeef; f (); }
Type safe languages • Which of these languages are type safe? - C - C++ - C# - Haskell - Python - Rust
Run-time Errors and Safety • How can we deal with partial functions, for example division by zero? Γ ⊢ t 1 : Int Γ ⊢ t 2 : Int Γ ⊢ Div t 1 t 2 : Int Problem: the expression 5/0 is well-typed, but does not evaluate to a value. • There are two alternatives: (1) Change static semantics: can we enhance the type system to check for division by zero? ‣ in general, such a type system would not be decidable ‣ there exist systems that approximate this (2) Change dynamic semantics: can we modify the semantics such that the devision by zero does not lead to a stuck state ‣ this approach is widely used for type safe languages
Run-time Errors and Safety • Application of a partial function can yield Error Div v (Num 0) ↦ Error • An Error interrupts any computation Plus Error e ↦ Error Plus e Error ↦ Error If Error e 1 e 2 ↦ Error and so on.....
Run-time errors and Safety • Typing the Error value : - a run-time error can have any type Γ ⊢ Error : ∀ τ. τ • What type of situations lead to checked run-time errors in Haskell?
Exceptions • Error handling so far: - The Error expression to handle run-time errors deterministically aborts the whole program - For many applications, this is not the appropriate behaviour - Exceptions permit a more fine grained response to run-time errors • Error: - result of a programming error (e.g., preconditions of a function not met), can be fixed by fixing the program • Exception: - result of expected, but irregular occurrence, can possibly be handled in the program
Exceptions • Exceptions in MinHs: (1) raising (or throwing) an exception: raise e ‣ e : information about handling of exception (2) catching an exception: try e 1 handle x => e 2 ‣ catch expression raised in e 1 ‣ exception handler is e 2 ‣ access to information about handling exception via x
Exceptions • Abstract Syntax ‣ raise e Raise e ‣ try e 1 handle x => e 2 Try e 1 ( x .e 2 ) • Informal evaluation rules: on try e 1 handle x => e 2 ‣ evaluate e 1 , and ‣ if (Raise v ) is encountered during e 1 , bind x to v and then evaluate e 2
Exceptions • Example: try if (y <= 0) then raise -1 else x/y handle err => .... • try expressions can be nested ‣ innermost try expression catches ‣ handler may re-raise an exception
Exceptions • Observations: - type of exception values (second argument of raise) • in many programming languages, this is a fixed type τ exc • may simply be a string or integer (exception code) • e.g., subclass Throwable in Java
Exceptions - Static Semantics • Typing rules Γ ⊢ e : τ exc Γ ⊢ Raise e: ∀ τ. τ Γ ∪ { x : τ exc } ⊢ e 2 : τ Γ ⊢ e 1 : τ τ Γ ⊢ Try e 1, ( x .e 2 ):
Exceptions - Dynamic Semantics • We introduce a new machines state s ⪻ (Raise v ) the machine raises an exception with the exception value v • First approach: on s ⪻ (Raise v ) ‣ propagate exception upwards in the control stack s ‣ use first handler encountered
Exceptions - Dynamic Semantics • Entering a try block s ≻ Try e 1 ( x . e 2 ) ↦ C (Try ☐ ( x . e 2 ) ▷ s ) ≻ e 1 • Returning to a try block ↦ C s ≺ v 1 (Try ☐ ( x . e 2 ) ▷ s ≻ v 1 • Evaluating a raise expression s ≻ Raise e ↦ C (Raise ☐ ) ▷ s ≻ e • Raising an exception (Raise ☐ ) ▷ s ≻ v ↦ C s ⪻ Raise v • Catching an exception ↦ C s ≻ e 2 [ x:=v] Try ☐ x . e 2 ▷ s ⪻ Raise v • Propagating an exception f ▷ s ⪻ Raise v ↦ C s ⪻ Raise v
Exceptions - Dynamic Semantics • What is the problem here? - efficiency: the frames are popped one by one when an exception is raised • Second approach - how can we jump directly to the appropriate handler? - we use an extra handler stack h - a handler frame contains ‣ a copy of the control stack ‣ the handler expression
Exceptions - Dynamic Semantics • Entering a try block ( h, k ) ≻ Try e 1 ( x . e 2 ) ↦ C (Handle k ( x . e 2 ) ▷ h ,(Try ☐ ) ▷ k ) ≻ e 1 • Returning to a try block (Handle k ( x . e 2 ) ▷ h,(Try ☐ ) ▷ k ) ≺ v 1 ↦ C ( h, k ) ≺ v 1 • Evaluating a raise expression ( h, k ) ≻ ( Raise e ) ↦ C ( h , ( Raise ☐ ) ▷ k) ≻ e • Raising an exception ( h , (Raise ☐ ) ▷ k) ≻ v ↦ C ( h, k ) ⪻ (Raise v ) • Catching an exception (handle k’ (x . e 2 )) ▷ h , k ) ⪻ (Raise v ) ↦ C ( h, k’ ) ≻ e 2 [ x:=v]
Recommend
More recommend