UP | HOME

Type checking II
Lecture 8

Table of Contents

Review

Questions about the last class?

Quiz

Is printf part of the C language?

Quiz Discussion

Is printf part of C?

  • Library methods vs. keywords
  • Keywords in syntax vs. reserved identifiers
  • Growing a Language
  • Language as methods of abstraction, adding new symbols
  • -nostdinc flag

Performing type checking

  • Type checker proves that input an program follows the typing rules

SimpleC's types

  • int, bool, string, function

int

  • Set of integers
  • What operations are there for integers?

Definition of int type vs. machine representation.

C long vs. Java BigInt

How do we check an operation?

  • Examples: addition +
    • Takes two integers
  • How do we know 1 + 2 is legal?
    • + takes two integers
    • How do we know 1 and 2's types?

Remember: the map is not the territory! The ASCII symbols for 1, 2, +, are just numeric encoding conventions that our systems use to draw glyphs on screen. The type-checker needs to interpret the meaning of these symbols.

While we can easily observe the type ourselves, we need to create an program on a computer that can mechanically determine the meaning without a human to tell it our interpretation the meaning.

Types of symbols in the language

  • Integer literals (defined in syntax)
    • Always have type "int" (as an axiom)
  • What about variables?

Declarations give axioms/assumptions to the type-checker so it can prove

Type-checking 1 + 2

  • We know + takes two integer
  • We know 1 and 2 are integers
  • Therefore we know 1 + 2 is type safe. QED!

What about + in 1 + 2 * 3?

  • We know + takes two integers?
  • 1 is an integer?
  • How do we know the type of 2 * 3?

The type of functions

  • + is a function
  • We define it to take two integer
  • And we define it to produce an integer
  • Using formal notation: + : (int, int) -> int

The symbol + is a function -> which takes two integer (int, int) and returns an integer -> int

The typing rules for functions and type operations are the same

  • + is a built-in function with special syntax (infix instead of prefix)

Back to 1 + 2 * 3

  • To type-checking + we need to type-check 2 * 3
  • To type-check *, we check its type
    • * : (int, int) -> int)
  • Type-checking a function both
    • ensures its parameter types match, and
    • deduces the return value's type

Let's sketch an algorithm for type-checking arithmetic expressions

  • How can we handle these nested constructs?

The typing rules implemented by the type checker are designed to match the actual runtime behavior of the program. Programming language researchers develop proofs that the type system reflects the runtime behavior and also ensures safety.

Recursively type-check expression

  • Basically an evaluator, like Calc.java
  • But instead of integer values, we use a type as the (abstract) value
  • Recall that a type is a set of legal values (and operators)
    • Type-checking gives symbols the type name itself instead of one of the values

Algorithm pseudo-code

typecheck(expr):
  if expr is NUM:
    return INT
  elif expr equals either "true" or "false":
    return BOOL
  elif expr.op is one of + - * /
    assert typecheck(expr.left) == INT
    assert typecheck(expr.right) == INT
    return INT
  elif expr.op is one of < <= > >= == !=
    assert typecheck(expr.left) == INT
    assert typecheck(expr.right) == INT
    return BOOL
  elif expr.op is one of && || !
    assert typecheck(expr.left) == BOOL
    assert typecheck(expr.right) == BOOL
    return BOOL

Compare to an evaluator

  • Same structure
  • Different operations
eval(expr):
  if expr is NUM:
    return toInteger(expr)
  elif expr equals either "true" or "false":
    return expr == "false" ? 0 : 1
  elif expr.op is one of + - * /
    left = eval(expr.left)
    right = eval(expr.right)
    return do_op(left, expr.op, right)
  elif expr.op is one of < <= > >= == !=
    left = eval(expr.left)
    right = eval(expr.right)
    return do_op(left, expr.op, right)
  elif expr.op is one of && || !
    left = eval(expr.left)
    right = eval(expr.right)
    return do_op(left, expr.op, right)

Type checkers as theorem provers

  • Type-checker is equivalent to logical proofs
    • Curry-howard correspondence
    • Coq is a proof writing assistant based on

Function's return type is the theorem

  • Type-checking proves that it returns the type
  • Leaves of the tree are axioms
    • Defined by language designer or by developer

Typing judgments

  • Based on "proof rules"
    • Systematic notation for deriving proofs from logical rules
  • Popular notation in academia
    • Sort of an esoteric description of an evaluator
  • Described in Type Systems
  • Example

Handling declarations

  • User may add new symbols to the language
  • Type-checking needs to proof type safety of these as well
  • Many language require declarations

Growing a Language

Some languages can infer types from operators used.

Type-checker records type information about each symbol

  • Maps symbol name to type
  • Declarations populate the symbol table
  • Relevant to scoping rules
    • Same name, different memory location
    • Disambiguate by scope

Symbol tables

symbol-table.gif

(Source)

Conditional statements

x = setx()
if (x > 10)
y = 1
else
y = "hello"

What if the type-unsafe branch is not reachable?

x = setx()
if (x > 10)
y = 1
else
y = "hello"

Design decisions

  • No type system: any symbol, any type
  • Type-safety: values in all uses fit symbol's type
    • Inflexible in C, e.g., no function overloading

Polymorphism

  • Designs that permit multiple types for the same symbol, e.g.,
    • Subclasses
    • Operator/function overloading
    • Generics

Type checking loops

What if we don't know how long the loop runs?

int y int x

while x > 0 if (x > 10) y = 1 else y = "hello" x = updatex(x)

What if the loop doesn't terminate?

Author: Paul Gazzillo

Created: 2023-04-13 Thu 14:59