Programming Languages: Chapter 9: Type Systems and Data Abstraction



Aggregate data types

  • array (indexed with integers)
  • record (aka struct; indexed with field names) (e.g, in C:
    struct {
       int x;
       double y;
    };
    
    )
  • undiscriminated union (can only hold one of several types) (e.g., in C:
    /* compiler only allocates memory for the largest */
    union {
      int x;
      double y;
    };
    
    )
  • discriminated union contains a tag field which indicates which type the union currently holds (e.g., in C:
     
    /* C compiler does no checking or enforcement. */
    struct {
       enum tag {i, d} flag;
       union {
          int x;
          double y;
       } U;
    }
    
    )


Inductively defined data types
Inductive data types

can be represented as unions of record types, (i.e., union of structs)
  • called variant records
  • each record type is called a variant of the union type
  • example of a variant record for a binary tree in C:
    union bintree {
    
       struct {
          int number;
       } leaf;
    
       struct {
          int key;
          /* why are pointers necessary? */
          union bintree* left;
          union bintree* right;
       } interior_node;
    } 
    


(define-datatype ...) and (cases ...)

  • we need a tool for specifying ADTs
  • (define-datatype ...) makes variant records (it is not part of Scheme)
  • a constructor is created for each variant to create data values belonging to that variant
  • binary tree ADT example:
    • interface:
      • a 1-argument procedure leaf-node (to create a leaf node)
      • a 3-argument procedure interior-node (to create an interior node)
      • a 1-argument predicate bintree?
    • using the constructors:
      > (leaf-node 5)
      #(struct:leaf-node 5)
      
      > (define myleaf (leaf-node 5))
      
      > (interior-node 'a (leaf-node 5) (leaf-node 6))
      #(struct:interior-node a #(struct:leaf-node 5) #(struct:leaf-node 6))
      
      > (bintree? myleaf)
      #t
      
      > (bintree? (interior-node 'a (leaf-node 5) (leaf-node 6)))
      #t
      
  • data types can be mutually-recursive (e.g., recall grammar for S-expressions)
  • (cases ...) provides a convenient way to manipulate data types created with (define-datatype ...)
  • can think of (cases ...) as pattern matching (values bound to symbols)
  • make (define-datatype ...) and (cases ...) your friends


Abstract syntax

  • simple grammar for λ-calculus expression re-visited
  • concrete vs. abstract syntax (external vs. internal representation)
  • expression datatype
  • one-to-one mapping between production rules and constructors



    (regenerated from [EOPL2] p. 49)
  • use of the expression datatype
  • makes occurs-free? more readable; eliminates obscure and lengthy car-cdr chains
  • abstract syntax tree (AST): like a parse tree, except uses abstract syntax rather than concrete syntax
  • AST for ((lambda (x) (f x)) (g y)):


  • parsing is the process of converting a string of characters representing a program into an AST
    • performed by a program called a parser (or syntactic analyzer)
  • it is easier to parse list expressions than strings into abstract syntax
  • Scheme (read) facility
  • parse-expression: converts concrete syntax to abstract syntax (or S-expression to abstract data type)
  • unparse-expression: converts abstract syntax to concrete syntax (or abstract data type to S-expression)
  • use of abstract syntax makes data representing code easier to manipulate and a program which processes code (i.e., a program) more readable


Inductive Data Types and Abstract Syntax Summary

  • discriminated union
  • inductive data types (e.g., variant record: a union of structs)
  • define-datatype constructs inductive data types (specifically, variant records)
  • cases decomposes inductive data types
  • concrete syntax vs. abstract syntax


Type systems


Haskell type system


    Types

      Prelude> :t [1,2,3,4]
      [1,2,3,4] :: Num a => [a]
      Prelude> :t [1.1,2.2,3.3,4.4]
      [1.1,2.2,3.3,4.4] :: Fractional a => [a]
      Prelude> :t head
      head :: [a] -> a
      Prelude> :t isDigit
      isDigit :: Char -> Bool
      
    • type introduces a new name for an existing type:
      (* like a typedef/struct in C/C++ *)
      type id = int;
      
      type name = string;
      
      type age = int;
      
      type gender = char;
      
      type rate = real;
      
      type professor = (id * name * gender * age * rate);
      (* type professor = (id * name * gender * age * real); *)
      
      (*
      struct {
         int a;
         float b;
      }
      *)
      
      val tam = (1, "Tam Nguyen", #"M", 46, 45.56) : professor;
      
      val shen = (2, "Ju Shen", #"M", 64, 7.25) : professor;
      
      val yao = (5, "Zhongmei Yao", #"M", 31, 8.25) : professor;
      
      val phu = (1, "Phu Phung", #"M", 32, 3.25) : professor;
      
      val saverio = (2, "Saverio Perugini", #"M", 20, 1.23) : professor;
      
      val gowda = (3, "Raghava Gowda", #"M", 59, 13.25) : professor;
      
      val zargham = (4, "Mehdi Zargham", #"M", 55, 33.25) : professor;
      
      type department = professor list;
      
      val udcps = [tam,shen,phu,saverio,gowda,zargham,yao];
      
      type point = (real * real);
      
      type rectangle = (point * point * point * point);
      
      (* can be parameterized like a template in C++ *)
      type ('domain_type, 'range_type) mapping = ('domain_type * 'range_type) list;
      
      val floor1 = [(2.1,2), (2.2,2),(2.9,2)]: (real, int) mapping;
      
      val professor_mapping = [(1, "Zhongmei Yao"), (2, "Saverio Perugini")] : (int, string) mapping
      (* professor_mapping = [(1, "Zhongmei Yao"), (2, "Saverio Perugini")] :: (int, string) mapping *)
      
      val lookup = [(yao,1), (saverio,2)] : (professor, id) mapping;
      
      (* recursive types not permitted 
      type tree = (int * tree list) *)
      
    • data introduces a new type:
      -- a variant record or a union of structs
      -- comparable to define-datatype
      data Bool = True | False
      data Colors = Red | Green | Blue | Orange | Yellow
      
      --decorate :: Mapping Colors Int
      decorate = [("Red",1), ("Blue",2)]
      
      -- can be parameterized (like a template in C++)
      data Student a = New | Id a
      
      -- can be recursive
      data Natural = Zero | Succ Natural
      data IntTree = Leaf Int | Node IntTree Int IntTree
      
      -- can be parameterized and recursive
      data List a = Nil | Cons a (List a)
      


    Haskell's type system

      Haskell has a very powerful type system. A type system is a language support for creating new types.
      -- a variant record or a union of structs
      -- comparable to define-datatype
      -- like the define-datatype construct from [EOPL2] or ML's datatype
      data Colors = Red | Green | Blue | Orange | Yellow
      
      {--
      union {
         int a;
         float b;
      } --}
      
      data Daysofourlives = Sun | Mon | Tue | Wed | Thu | Fri | Sat
                            deriving (Show,Eq)
      
      onholiday :: Daysofourlives -> Bool
      onholiday Sun  = True
      onholiday day = (day == Sun) || (day == Sat)
      
      ans = onholiday Mon
      ans2 = onholiday Sat
      
      -- can be parameterized like a template in C++
      data Student a = New | Id a
      
      -- can be recursive
      data Natural = Zero | Succ Natural
      
      four = (Succ (Succ (Succ (Succ Zero))))
      
      data IntTree = Leaf Int | NodeIT IntTree Int IntTree
      
      data Bintreeofints = EmptyI | NodeI Bintreeofints Int Bintreeofints
                           deriving (Show,Eq)
      
      ourbintreeofints :: Bintreeofints
      ourbintreeofints = (NodeI
         (NodeI
            (NodeI EmptyI 1 EmptyI)
            7
            (NodeI EmptyI 2 EmptyI))
         6
         (NodeI
            (NodeI EmptyI 3 EmptyI)
            8
            (NodeI
               (NodeI EmptyI 5 EmptyI)
               4
               (NodeI EmptyI 10 EmptyI)
            )
         ))
      
      -- if inorder returns a sorted list,
      -- then its argument is a binary search tree
      -- inorder :: Bintreeofints -> [Int]
      inorderI EmptyI = []
      inorderI (NodeI left i right) =
         (inorderI left) ++ [i] ++ (inorderI right)
      
      preorderI EmptyI = []
      preorderI (NodeI left i right) =
         [i] ++ (preorderI left) ++ (preorderI right)
      
      postorderI EmptyI = []
      postorderI (NodeI left i right) =
         (postorderI left) ++ (postorderI right) ++ [i]
      
      ans3 = inorderI ourbintreeofints
      ans4 = preorderI ourbintreeofints
      ans5 = postorderI ourbintreeofints
      
      -- can be parameterized and recursive
      data List a = Nil | Kons a (List a)
                    deriving (Show,Eq)
      
      cpslistofprofs =
          (Kons phu
             (Kons saverio
                (Kons gowda
                   (Kons yao
                      (Kons zargham Nil)))))
      
      -- like typedef in C
      -- type and constructor names must begin with a capital letter
      type Id = Int
      
      type Name = [Char]
      
      type Age = Int
      
      type Gender = Char
      
      type Rate = Float
      
      --type Professor = (Id, Name, Gender, Age, Float)
      type Professor = (Id, Name, Gender, Age, Rate)
      
      tam :: Professor
      tam = (1, "Tam Nguyen", 'm', 46, 45.56)
      
      shen :: Professor
      shen = (2, "Ju Shen", 'm', 64, 7.25)
      
      phu :: Professor
      phu = (1, "Phu Phung", 'M', 32, 3.25)
      
      saverio :: Professor
      saverio = (2, "Saverio Perugini", 'M', 20, 1.23)
      
      gowda :: Professor
      gowda = (3, "Raghava Gowda", 'M', 59, 13.25)
      
      zargham :: Professor
      zargham = (4, "Mehdi Zargham", 'M', 55, 33.25)
      
      yao :: Professor
      yao = (5, "Zhongmei Yao", 'F', 31, 8.25)
      
      type Department = [Professor]
      
      udcps :: Department
      udcps = [tam,shen,phu,saverio,gowda,zargham,yao]
      
      sun = (8, "Yvonne Sun", 'F', 29, 4.5)
      mark = (9, "Mark Nielsen", 'M', 51, 5.7)
      
      udbio = [sun,mark]
      
      joe = (10, "Joe Mashburn", 'M', 52, 10.1)
      art = (11, "Art Busch", 'M', 33, 5.66)
      atif = (12, "Atif Abueida", 'M', 42, 6.67)
      driskell = (13, "Shannon Driskell", 'F', 35, 7.753)
      
      udmat = [joe,art,atif,driskell]
      
      type Univ = [Department]
      
      univ = [udcps,udbio,udmat]
      
      -- parameterized datatype 
      data Bintree a = Empty | Node (Bintree a) a (Bintree a)
                       deriving (Show,Eq)
      {--
      inorder Empty = []
      inorder (Node left i right) = (inorder left) ++ [i] ++ (inorder right)
      preorder Empty = []
      preorder (Node left i right) = [i] ++ (preorder left) ++ (preorder right)
      postorder Empty = []
      postorder (Node left i right) = (postorder left) ++ (postorder right) ++ [i]
      --}
      
      ourbintreeofints2 = (Node
            (Node
              (Node Empty 1 Empty)
              7
              (Node Empty 2 Empty))
            6
             (Node
              (Node Empty 3 Empty)
              8
              (Node
                 (Node Empty 5 Empty)
                 4
                 (Node Empty 10 Empty))))
      
      ourbintreeofstrs = (Node
            (Node
              (Node Empty "one" Empty)
              "seven"
              (Node Empty "two" Empty))
            "six"
             (Node
              (Node Empty "three" Empty)
              "eight"
              (Node
                 (Node Empty "five" Empty)
                 "four"
                 (Node Empty "ten" Empty))))
      
      ourbintreeofstrs2 = (Node
         (Node
            (Node Empty "the" Empty)
            "type"
            (Node Empty "is" Empty))
         "cat"
         (Node
            (Node Empty "called" Empty)
            "bintree"
            (Node Empty "and" Empty)))
      
      ourbintreeofEmployees = (Node
            (Node
              (Node Empty saverio Empty)
              phu
              (Node Empty gowda Empty))
              yao
             (Node
              (Node Empty phu Empty)
              zargham
              (Node
                 (Node Empty yao Empty)
                 gowda
                 (Node Empty saverio Empty))))
      
      ourbintreeofDepts = (Node
              (Node Empty udcps Empty)
              udmat
              (Node Empty udbio Empty))
      
      -- declaring the type of a function is not required, but
      -- can be used to resolve ambiguities or restrict the type
      -- of the function beyond what the Hindley-Milner type checking
      -- would infer' [PLPP] p. 514
      inorder :: Bintree a -> [a]
      inorder Empty = []
      inorder (Node left i right) = (inorder left) ++ [i] ++ (inorder right)
      
      preorder Empty = []
      preorder (Node left i right) = [i] ++ (preorder left) ++ (preorder right)
      
      postorder Empty = []
      postorder (Node left i right) = (postorder left) ++ (postorder right) ++ [i]
      
      ans6 = inorder ourbintreeofstrs
      ans7 = preorder ourbintreeofstrs
      ans8 = postorder ourbintreeofstrs
      
      ans9 = inorder ourbintreeofstrs2
      ans10 = preorder ourbintreeofstrs2
      ans11 = postorder ourbintreeofstrs2
      
      ans12 = inorder ourbintreeofEmployees
      ans13 = preorder ourbintreeofEmployees
      ans14 = postorder ourbintreeofEmployees
      


    Haskell type classes and instances

    • a collection of names and types of the functions which every instance must support
    • like a (Java) interface from object-oriented programming
    • some pre-defined Haskell classes: Eq, Show, Ord, Num, Real
    • the Haskell numeric type class hierarchy:



      (regenerated from [PLPP] Fig. 11.10, p. 519)


ML type system


Comparison of ML and Haskell

conceptMLHaskell
listshomogeneoushomogeneous
cons:::
append@++
renaming parameterslst as (x::xs)lst@(x:xs)
functional redefinitionpermittednot permitted
dynamic dispatch|
parameter passingcall-by-value
strict
applicative-order evaluation
call-by-need
non-strict
normal-order evaluation
functional compositiono.
infix → prefix(op +)(+)
curried formomit parenthesesomit parentheses
type declaration:::
datatype definitiondatatypedata
type variablesprefaced with '
written before the datatype name
not prefaced with '
written after the datatype name
function typeoptional, but if used,
embedded within function definition
optional, but if used,
precedes the function definition
type checkingHindley-MilnerHindley-Milner
function overloadingnot supportedsupported through qualified types and type classes


Representation strategies


Well defined abstract data types

The underlying implementation can change without disrupting the client code as long as the contractual signature of each function declaration in the interface remains unchanged.


Data abstraction


Example: non-negative integers


Advantages to an ADT


Choices of representation


Case Study: Environments


Closure representation


List of lists representation


Abstract syntax representation

    (self-study)
  1. take expressions in the form as
    (extend-environment symbols values
       ...
          (extend-environment symbols values
             (empty-environment)))
    
    and define them using EBNF (a concrete syntax)
    <environment> ::= ???
    <environment> ::= ???
    
  2. represent that concrete syntax as an abstract syntax
  3. define the data type
  4. define the environment implementation
  5. see § 2.3.3 (pp. 59-61) when done


References


Return Home