RU beehive logo ITEC dept promo banner
ITEC 380
2009fall
ibarland

homeinfolecturesexamshwsarchive

lect09a
union types in Java
and representing parse trees

(Recursive) union types in Java: We'll remind ourselves of scheme list-data-definition and an example function; then the same approach used for (singly-linked) lists in Java:

; A List is:
;   - empty, or
;   - (cons [any] [List])
;
(define (length some-list)
  (cond [(empty? some-list) 0]
        [(cons?  some-list) (+ 1 (length (rest some-list)))
/* The analagous thing in Java:  'singly-linked lists' */

abstract class SList { 
  abstract int length();
  }

class Empty extends SList {
  int length() { return 0; }
 }

class Cons extends SList {
  Object first;
  SList rest;

  int length() { return 1+rest.length(); }
 }
Note the one-to-one correspondence between code… except, where did the cond go? The run-time method dispatching does that for us (yay!).

Let's see another example of union-types in Java (starting by reminding ourselves of the scheme version:)

;;==== class anc-tree
; An anc-tree is
;   - 'unknown
;   - (make-child [string] [number] [symbol] [anc-tree] [anc-tree])
;
(define-struct/contract child ([name string?] 
                               [yob integer?] 
                               [eye symbol?]
                               [ma anc-tree?]  
                               [pa anc-tree?])

(define (anc-tree? any)
  (or (and (symbol? any) (symbol=? any 'unknown))
      (child? any)))


; Return the names of all baby-boomers.
(define (bbs fam)
  (cond [(symbol? fam) empty]
        [(child? fam)
         (append (if (<= 1956 (child-yob fam) 1965)
                     (list (child-name fam)) 
                     empty)
                 (bbs (child-ma fam))
                 (bbs (child-pa fam)))]))
The same thing in Java:
abstract class AncTree { 
  abstract List<String> bbs();
  }

class Unknown extends AncTree {   // A class with no fields (?!)
  List<String> bbs() {
    return new ArrayList<String>();  // 'return empty'
    }
  }

class Child extends AncTree {
  String name;
  int yob;
  String eye;
  AncTree ma;
  AncTree pa;

  List<String> bbs() {
    /* Direct translation of the scheme version: */
    return ((1956 <= this.yob) && (this.yob <= 1965) 
             ? new LinkedList<String>().add(this.name)
             : new LinkedList<String>())
           .append( this.ma.bbs() ).append( this.pa.bbs() );
    /* Ack!
     * [doesn't quite work because 'add' mutates]
     * and it doesn't translate well; the ternary '.. ? .. : ..'
     * feels much weirder than scheme's 'if'.
     * Here is equivalent code in more idiomatic Java:
     */
    List<String> selfList = new LinkedList<String>();
    if ((1956 <= this.yob) && (this.yob <= 1965))
      selfList.add( this.name );

    List<String> masBBs = this.ma.bbs();
    List<String> pasBBs = this.pa.bbs();
    return selfList.append( masBBs ).append( pasBBs );
    }
  }
Note that the design recipe still applies: When writing the Java method , we still pull out all the fields, think of their types, and (most likely) add the natural recursive calls.

Aside: the meaning(s) of null

Btw, compare this to the traditional approach to trees in CS2: in our approach we don't actually mention null, and we won't have any NullPointerExceptions. In traditional binary trees, null is used to represent data -- it represents the empty tree(Node). BUT: you can't call any methods on null (even though it makes sense to call methods on an empty tree). Even worse, the traditional type Declaration makes it look like

class Node {
  String name;
  Node left;   // well, a real Node *or* null
  Node right;  // well, a real Node *or* null
  }
We now are conflating Nodes (which contain data) with empty trees (which don't), and the fact that there is a supertype for both empty and non-empty trees. In our approach we just recurred on this.ma and this.pa. But here, we have to be checking for null before calling any (recursive) method. This is not only repeated code (ugly/redundant), it's also easy for programmers to forget.

The problem stems from the fact that the recursive data definition has three types (Node, emptyTree, and Tree), but the traditional approach program uses one-or-two (Node, null, and …places where we need to check for null-vs.-Node). Without getting the data-definition correct (the one mathematicians use for lists and trees), we suddenly are missing a methodology, and our code no longer can mirror the data.

Note that null certainly does have its uses as a sentinel: for example, a search function might return the object found or null. (In scheme, false is the idiomatic sentinel.) Since null type-checks as any type, we can still say our function returns a Node to appease the type-checker; in comments we say "this function returns a Node or null". The important thing to remember is that anybody who calls the function needs to treat the result as a union type, and do a cond or dispatch on it. In Java, the type system fails to remind us of this, and programmers easily forget.

representing N0

Recall our grammar:

  Expr      ::= Number | Var | ParenExpr | BinExpr | IfZExpr
  ParenExpr ::= ( Expr ) 
  BinExpr   ::= ( Expr BinOp Expr )
  IfZExpr   ::= if Expr is0 then Expr else Expr ;
  BinOp     ::= plus | minus | times
where Number is any numeric literal (as written in either Java or Scheme, your choice), and Var is string with no whitespace or punctuation (parentheses, “;”) and is not otherwise a reserved word in N0 (i.e. a terminal in the above grammar -- “if”, “plus”, “is0”, etc.). Whitespace is required between all terminals/non-terminals, with the exception of punctuation.

Our project will:

You can use either scheme or java for your project (but you'll be expected to be able to *understand* the code used either way). You'll be provided with a skeleton version to get you started.

How to represent Exprs: Let's start by representing (the parse tree for) “(3 plus 4)” and “((3 plus 4) minus 5)” :

(define-struct/contract 
    (bin-expr [left expr?] [op bin-op?] [right expr?)))

(make-bin-expr 3 'plus 4)
(make-bin-expr (make-bin-expr 3 'plus 4)
               'minus
               5)


; We need data def'ns for our union types;
; we'll make real code out of the union-type-def'n
; (so that we can use it in with 'define/contract', if you want):

(define (expr? any-val)
  (or (number? any-val)
      (string? any-val)
      (paren-expr? any-val)
      (bin-op-expr? any-val)
      (if-Z-expr? any-val)))1
2 

(define (bin-op? any-val)
  (member any-val (list 'plus 'minus 'times)))


1 Rather than calling each function on any-val, we could use map:

    (map (lambda (f) (f any-val))
         (list number? string? paren-expr? bin-op-expr? if-Z-expr?)))
This would give us a big list of booleans (in this case, mostly false with perhaps one true). Then, we could (nearly) use foldl take the or of the entire list:
  (foldl or
         false
         (map (lambda (f) (f any-val))
              (list number? string? paren-expr? bin-op-expr? if-Z-expr?)))
Actually, that doesn't quite work, since or is not a function (it short-circuits, so it's special syntax). But people want to do this all the time, so there is a function ormap:
  (ormap (map (lambda (f) (f any-val))
              (list number? string? paren-expr? bin-op-expr? if-Z-expr?)))
(ormap and andmap are built-in, but you could write them yourself, straight from the design recipe.) Thus a schemer wouldn't think twice about writing:
(define (is-expr? any-val)
  (ormap (map (lambda (f) (f any-val))
              (list number? string? paren-expr? bin-op-expr? if-Z-expr?)))
     

2 Well, if they were concerned about traversing the list twice (once with map, then again with ormap) they'd revert to list-processing, probably using foldl:

(define (is-expr? any-val)
  (foldl (lambda (f found-it) (or found-it (f any-val)))
         false
         (list number? string? paren-expr? bin-op-expr? if-Z-expr?)))
     

homeinfolecturesexamshwsarchive


©2009, Ian Barland, Radford University
Last modified 2009.Oct.26 (Mon)
Please mail any suggestions
(incl. typos, broken links)
to iba�rlandrad�ford.edu
Powered by PLT Scheme