7.7.0.3

13 — Poly Types

Tuesday, 18 February 2020

Presenters (1) Benjamin Herzberg & Nicolas Burniske (2) Jacob Chvatal & Thomas Morriss

Simple Types Limit Developers ...

Our type system has tons of weaknesses:
  • it has no types for your favorite data structure (dictionaries)

  • it has no types for alternatives (e.g., Boolean and similar user-defined types)

  • it forces us to copy code because the types differ:

    Lectures/13/types.rkt

      #lang racket
       
      (struct int [] #:transparent)
      (struct ->  [domain range] #:transparent)
      ;; - - - - - - - - - - - - - - - - - - - - - - - - -
      (struct ref [content] #:transparent)
       
      ;; - - - - - - - - - - - - - - - - - - - - - - - - -
      #; {Type is one of:
               -- (int) ;; represents the set of integers
               -- (-> Type Type) ;; .. the set of functions 
               -- (ref Type)} ;; the set of cells 
       
      ;; - - - - - - - - - - - - - - - - - - - - - - - - -
       
      (define type=? equal?)
       
      (define (type? t)
        (or (int? t) (->? t) (ref? t)))
       
      (provide
       (struct-out int) (struct-out ->) (struct-out ref)
       
       #; {Any -> Boolean : Type}
       type?
       
       #; {Type Type -> Boolean}
       type=?)
       

    Figure 59: Cell Types

    • a cell-content swapper for integer cells (int) is identical to a swapper for integer-to-integer cells (-> (int) (int))

      if we had the somewhat obvious types for cells (see figure 59)

    • a sort function of list of integers would be identical except for sort of list of pairs of integers

      if we had list types, but we don’t even have those

  • never mind: it doesn’t allow programmers to define a type of lists or trees

  • and we can’t apply an increment function for ints to nats (if we had nats in our language) or a length function for lists to the type of non-empty lists (if we had such things in our language to avoid first(empty))

... So We Need More Types

basically available in many typed languages:
  • products (tuples)

  • sums (alternatives)

  • labeled records (dictionary)

  • encapuslation (existential)

  • polymorphism (universal)

  • recursive types

  • structural subtypes (nat < int, (d -> r) < (d’ -> r’))

The Types of Cells

The copying code part is particularly disturbing and today’s topic. Indeed, because of the weakness of the type system, it’s all so bad, we can’t even get @, !, and = right.

Let’s say we want to play the recursion game and set up factorial’s recursion via a ref cell. So we add types to the initial environment so that our type checker can deal with cells. See figure 60.

Lectures/13/type-env0.rkt

  #lang racket
   
  (require "types.rkt")
  (require "../6/environment.rkt")
   
  ;; - - - - - - - - - - - - - - - - - - - - - - - - -
   
  (define int2int (-> (int) (int)))
   
  (define population
    (list
     (list "+" (-> (int) (-> (int) (int))))
     (list "*" (-> (int) (-> (int) (int))))
     (list "^" (-> (int) (-> (int) (int))))
   
     (list "@" (-> int2int (ref int2int)))
     (list "!" (-> (ref int2int) int2int))
     (list "=" (-> (ref int2int) (-> int2int int2int)))))
   
  (define TYPE-ENV0
    (for/fold ([env empty]) ([name-type population])
      (add (first name-type) (second name-type) env)))
  ;; - - - - - - - - - - - - - - - - - - - - - - - - -
   
  (provide TYPE-ENV0)
   

Figure 60: Simple Types for (at), (tt !), and (tt =)

Now we can run a “poem” style example where we first set up a simple identity function in a cell and then assign the function we want. Recall that = is returns the old value in the cell. So there, take a look at figure 61,

Lectures/13/ex0.rkt

  #lang racket
   
  (require "cheap-poly-tc.rkt")
  (require "ast.rkt")
  (require "types.rkt")
  (require "../define-names.rkt")
   
  (define-names f x @ ! = cell1 cell2 swap tmp)
   
  ;; - - - - - - - - - - - - - - - - - - - - - - - - - - - 
   
  (type-check
   (tdecl swap (-> (ref (-> (int) (int)))
                   (-> (ref (-> (int) (int)))
                       (-> (int) (int))))
          [tfun* cell1 (ref (-> (int) (int)))
                 [tfun* cell2 (ref (-> (int) (int)))
                        [tcall [tcall = cell2]
                               [tcall (tcall = cell1)
                                      (tcall ! cell2)]]]]
          swap))
   
  ;; - - - - - - - - - - - - - - - - - - - - - - - - - - -
   
  (type-check
   (tdecl swap (-> (ref (int))
                   (-> (ref (int))
                       (int)))
          [tfun* cell1 (ref (int))
                 [tfun* cell2 (ref (int))
                        [tcall [tcall = cell2]
                               [tcall (tcall = cell1)
                                      (tcall ! cell2)]]]]
          swap))
   

Figure 61: Two Examples Using Cells

With the base type environment as set up, we can only get the first example to type check not the second one.

Polymorphic Types, Done Right

; A Ty is one of:
;  ...
;  t                  : String (type variable)
;  (All (t) (tech "Ty"))       : a polymorphic type
; A TypedAST is one of:
; 
;  [FUN* Var TypedAST]  : type abstraction /\
;  [CALL TypedAst Ty]   : type application [ .. ]

  

   TEnv + (x,TYPE) |- bdy : s

  -------------------------------------------

   TEnv |- (FUN* x bdy) : (All (x) s)

  

    TEnv + (x,TYPE) |- f : (All (x) t),  t : Type

  ----------------------------------------------------------------

   TEnv |- (CALL f t) : (All (x) s)

  

Technically, we need to define the judgment t : Type and make sure that all free variables in t are bound via some All.

Here are some examples. We start with the polymorphic identity function:

id = [FUN* t [fun* [x : t] x]]

fint = [CALL id (int)]
fint2int = [CALL id (-> (int) (int))]

@ = /\t.\c:t.. allocate store location, place c in there ..

[tcall [tapp (at)  int] 0]

! = /\t.\c:cell t.. retrieve store location l from c, value from l in store

[tcall [tapp ! int] [tcall [tapp (at)  int] 0]]

sort = /\t.\ a:array[t], <: t x t ->Boolean. sort a with <

[tcall [tapp sort int] [1,0,1]]

Polymorphic Types, Done Cheaply

Lectures/13/cheap-poly-tc.rkt

  #lang racket
   
  (require "ast.rkt")
  ;; monotypes for @ etc. 
  ; (require "type-env0.rkt")
  ;; type generators for @ etc. 
  (require "type-env1.rkt")
  (require "types.rkt")
  (require "../6/environment.rkt")
   
  ;; - - - - - - - - - - - - - - - - - - - - - - - - -
   
  (define UNDECLARED "undeclared variable")
  (define FUNCTION   "function type expected")
  (define DOMAIN     "domain type doesn't match arg type")
  (define DECL       "type of a declaration doesn't match")
  (define IF-TEST    "int expected in if test position")
  (define IF-BRANCH  "same types expected in branches of if")
  (define INSTANTIATE "types not sufficiently instantiated")
   
  #; {Type Type -> Type}
  (define (function-type-match tf ta)
    (match tf
      [(-> tdom trng) (if (type=? tdom ta) trng (error 'tc DOMAIN))]
      [(? procedure?) ;; using "dirt cheap local inference"
       (define instantiated (tf ta))
       (->-range instantiated)]
      [else (error 'tc FUNCTION)]))
   
  #; {TypedISL -> Type}
  (define (type-check isl0)
   
    #; {TypedISL TEnv -> Type}
    (define (tc/accu isl env)
      (match isl
        
        [(tcall f a)
         (define tf (tc/accu f env))
         (define ta (tc/accu a env))
         (function-type-match tf ta)]
        
        ;; - - - - - - - - - - - - - - - - - - - - - - - -
        ;; old 
        [(? string?)   (if (defined? isl env)
                           (lookup isl env)
                           (error 'tc UNDECLARED))]
        [(? integer?)  (int)]
        [(tfun* x t b) (define env+ (add x t env))
                       (define tbdy (tc/accu b env+))
                       (-> t tbdy)]
        [(tdecl f type rhs body)
         (define env+ (add f type env))
         (define trhs (tc/accu rhs env+))
         (if (type=? trhs type)
             (tc/accu body env+)
             (error 'tc DECL))]
        [(tif-0 tst thn els)
         (define ttst (tc/accu tst env))
         (cond
           [(not (int? ttst)) (error 'tc IF-TEST)]
           [else (define tthn (tc/accu thn env))
                 (define tels (tc/accu els env))
                 (if (type=? tthn tels)
                     tthn
                     (error 'tc IF-BRANCH))])]))
   
    (define result (tc/accu isl0 TYPE-ENV0))
    (if (type? result) result (error 'tc INSTANTIATE)))
   
  ;; - - - - - - - - - - - - - - - - - - - - - - - - -
  (provide type-check)
   

Figure 62: Cheap Type Checker

Lectures/13/type-env1.rkt

  #lang racket
   
  (require "types.rkt")
  (require "../6/environment.rkt")
   
  ;; - - - - - - - - - - - - - - - - - - - - - - - - -
   
  (define int2int (-> (int) (int)))
   
  (define population
    (list
     ;; - - - - - - - - - - - - - - - - - - - - - - - -
     [list "@"
           (λ (type)
             (if (type? type)
                 (-> type (ref type))
                 (error 'tc POLY)))]
     [list "!"
           (λ (ctype)
             (match ctype
               [(ref type) (-> ctype type)]
               [_ (error 'tc CALL)]))]
     [list "="
           (λ (ctype)
               (match ctype
                 [(ref type)
                  (-> ctype
                      (λ (val-type)
                        (if (type=? type val-type)
                            (-> type type)
                            (raise CALL))))]
                 [_ (error 'tc "= ...")]))]
   
     ;; - - - - - - - - - - - - - - - - - - - - - - - -
     ;; old
     
     (list "+" (-> (int) (-> (int) (int))))
     (list "*" (-> (int) (-> (int) (int))))
     (list "^" (-> (int) (-> (int) (int))))))
   
  (define POLY "@ is polymorphic")
  (define CALL "domain doesn't match")
   
  (define TYPE-ENV0
    (for/fold ([env empty]) ([name-type population])
      (add (first name-type) (second name-type) env)))
  ;; - - - - - - - - - - - - - - - - - - - - - - - - -
   
  (provide TYPE-ENV0)
   

Figure 63: Cheap Poly Types for (at), (tt !), and (tt =)