<<

Concepts of Program Design Type safety and

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

value & type environments / generics type safety control stacks error/exception handling partial application/function closures semantic features

functional (algebraic) data types static & dynamic static & dynamic scoping typing language concepts

explicit & implicit procedural/imperative typing Types in programming languages

statically vs dynamically typed

type safe vs non type safe strongly vs weakly typed Static and Dynamic Semantics

• MinHs 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’

e1 : τ ↦ e2 : τ ↦ e3 : τ ↦ …

• 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 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 , 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#

- Haskell

- Python

- Rust Run-time Errors and Safety

• How can we deal with partial functions, for example ?

Γ ⊢ t1 : Int Γ ⊢ t2 : Int

Γ ⊢ Div t1 t2 : 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 any computation

Plus Error e ↦ Error

Plus e Error ↦ Error

If Error e1 e2 ↦ 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 in C#

• Language constructs to handle exceptions:

- try: execute a code block which may trigger an exception, has to be followed by at least one

- catch block, which contains the exception handling code, which may be followed by a code block preceded by

- finally, which is executed whether or not

- throw, which triggers an exception, was called in the try block

• Predefined exception classes contain:

- Message: human readable text that describes the exception

- StackTrace: lists all the called methods and line numbers

• Exception class can be extended by user defined exceptions Exceptions in C#

try { // code block which may trigger // an exception through … throw … } catch (ExceptionName e1 ) { // error handling code } catch (ExceptionName e2 ) { // error handling code } catch (ExceptionName eN ) { // error handling code } finally { // code block which will always be // executed } Exceptions in C#

namespace SaveDivision { class Division { int result;

Division() { result = 0; }

public void div(int num1, int num2) { try { result = num1 / num2; } catch (DivideByZeroException e) { Console.WriteLine("Exception caught: {0}", e.Message); Console.WriteLine(“: {0}", e.StackTrace); } finally { Console.WriteLine("Result: {0}", result); } } … } } Exceptions

• Exceptions in MinHs: (1) raising (or throwing) an exception: raise e ‣ e : information about handling of exception

(2) catching an exception: try e1 handle x => e2

‣ catch expression raised in e1

‣ exception handler is e2 ‣ access to information about handling exception via x Exceptions

• Abstract Syntax

‣ raise e Raise e

‣ try e1 handle x => e2 Try e1 (x.e2)

• Informal evaluation rules: on try e1 handle x => e2

‣ evaluate e1, and

‣ if (Raise v) is encountered during e1, bind x to v and then evaluate e2 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, Exception in C# Exceptions - Static Semantics

• Typing rules

Γ ⊢ e : τexc

Γ ⊢ Raise e: ∀ τ. τ

Γ ∪ {x : τexc} ⊢ e2 :τ Γ ⊢ e1: τ

Γ ⊢ Try e1, (x.e2): τ 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 e1(x.e2) ↦C (Try ☐ (x.e2) ▷ s)≻ e1 • Returning to a try block

(Try ☐ (x.e2) ▷ s ≻ v1 ↦C s ≺ v1 • Evaluating a raise expression

s ≻ Raise e ↦C (Raise ☐) ▷ s ≻ e

• Raising an exception

(Raise ☐) ▷s ≻ v ↦C s ⪻ Raise v

• Catching an exception

Try ☐ x.e2 ▷ s ⪻ Raise v ↦C s ≻ e2 [x:=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 e1 (x.e2) ↦C

(Handle k (x.e2) ▷ h,(Try ☐ )▷ k) ≻ e1 • Returning to a try block

(Handle k (x.e2) ▷ h,(Try ☐) ▷ k) ≺ v1 ↦C

(h, k) ≺ v1

• 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.e2))▷ h, k) ⪻ (Raise v) ↦C

(h, k’) ≻ e2 [x:=v]