Data Abstraction and Procedural representation
We looked at parse last time:
(load "records.ss")
(define-record lit (datum))
(define-record varref (var))
(define-record lambda (formal body))
(define-record app (rator rand))
(define parse
(lambda (datum)
(cond
[(number? datum) (make-lit datum)]
[(symbol? datum) (make-varref datum)]
[(pair? datum)
(if (eq? (car datum) 'lambda)
(make-lambda
(caadr datum)
(parse (caddr datum)))
(make-app
(parse (car datum))
(parse (cadr datum))))]
[else (error 'parse "Invalid concrete syntax")])))
This provides a translation from the concrete (BNF)
to the abstract (record-case usable) syntax
concrete -> abstract
Let us look at unparse that goes the other way.
abstract -> concrete
(load "record.ss")
(define-record lit (datum))
(define-record varref (var))
(define-record lambda (formal body))
(define-record app (rator rand))
(define unparse
(lambda (exp)
(variant-case exp
[lit (datum) datum]
[varref (var) var]
[lambda (formal body)
(list
'lambda
(list formal)
(unparse body))]
[app (rator rand)
(list (unparse rator) (unparse rand))]
[else (error 'unparse
"Invalid abstract syntax expression")])))
> (parse '(lambda (x) x))
#3(lambda x #2(varref x))
> (unparse '#3(lambda x #2(varref x)))
(lambda (x) x)
>
Here's a free-vars that takes parsed expressions:
(define free-vars
(lambda (exp)
(variant-case exp
[lit (datum) '()]
[varref (var) (list var)]
[lambda (formal body)
(remove formal (free-vars body))]
[app (rator rand)
(union (free-vars rator) (free-vars rand))]
[else (error
'free-vars
"Error in parsed expression")])))
Here's the old free-vars
(define free-vars
(lambda (exp)
(cond
[(null? exp) '()]
[(varref? exp) (list exp)]
[(lambda-exp? exp)
(remove
(formal exp)
(free-vars (body exp)))]
[else (union
(free-vars (procedure exp))
(free-vars (argument exp)))])))
Handling Kleene expressions:
Lambda Calculus with NUMBER
EXP ::== NUMBER
| VARREF
| (lambda (VAR) EXP)
| (EXP EXP)
Change to:
EXP ::== NUMBER
| VARREF
| (if EXP EXP EXP)
| (lambda ({VAR}*) EXP)
| (EXP {EXP}*)
The equivalent abstract syntax is:
lit (datum)
varref (var)
if (test-exp then-exp else-exp)
lambda (formals body)
app (rator rands)
formals is a LIST of formal parameters
rands is a LIST of operand expressions
Suggested exercise:
ex 3.4.6 pg 87
Implementing Records
We have not commited to a specific implementation
for records, they are still abstract. We do not
know how they are implemented nor do we care
This data abstraction is very important
If asked, we could implement them as lists
or vectors (already in scheme).
Here is a possible implementation using vectors:
> (parse '(lambda (x) (y x)))
#3(lambda x #3(app #2(varref y) #2(varref x)))
>
(define-record lit (datum))
(define-record varref (var))
(define-record lambda (formal body))
(define-record app (rator rand))
Assuming a vector representation, then
(define-record leaf (number))
would produce
(define make-leaf
(lambda (number)
(vector 'leaf number)))
(define leaf?
(lambda (obj)
(and (vector? obj)
(= (vector-length obj) 2)
(eq? (vector-ref obj 0) 'leaf))))
(define leaf->number
(lambda (obj)
(if (leaf? obj)
(vector-ref obj 1)
(error
'leaf->number
"Invalid record object"))))
Can you directly define the five procedures that
result from
(define-record interior
(symbol left-tree right-tree))
Data Abstraction --> ADT
Representational independance through
an interface specification (informal)
Transparent representation versus Opaque
> (interior? tree-1)
#t
> (vector? tree-1)
#t
> (vector-ref tree-1 1)
foo
> (vector-ref tree-1 0)
interior
>
Transparent.
Opaque is better for correctness but difficult to
debug.
Transparent representations can be corrupted but
easier to debug.
We will use transparent representations but only
access information in records through the provided
access procedures. This will guarantee
incorruptibility of information in records
Representational independance:
parse and unparse defined without knowing how
records are implemented.....
Then we provided one possible implementation
using vectors.
I will continue to use this strategy.
For any data type:
Start with a conceptually easy to understand
representation (procedures). Once we have the
code using this data type, understood and
debugged, move to a more efficient representation
for the data type
Finite Function (ff)
FFs associate a value with each element of a
finite set of symbols.
This is a useful data type, for example
1. in implementing an ENVIRONMENT from where we
can find out the value associated with a variable
reference.
2. in implementing a SYMBOL TABLE which associates
a variable with lexical address
FF ADT
Three procedures:
1. create-empty-ff
procedure: 0 arguments, that returns the empty FF
which makes no associations
2. extend-ff
procedure: 3 arguments: sym, val, ff
returns a new ff that
a. preserves the old associations in ff
b. adds new association of sym with val
c. any previous assoc of sym is ignored
3. apply-ff
procedure: 2 arguments: ff, sym
returns the value associated with sym
Example finite function
Let us call it dxy-ff
{(d, 6), (x, 7), (y, 8)}
> (define dxy-ff
(extend-ff 'y 8
(extend-ff 'x 7
(extend-ff 'd 6
(create-empty-ff)
))))
> (apply-ff dxy-ff 'x)
??
A finite function may be represented as a scheme
procedure that takes a symbol and returns the
associated value.
Here's our interface using this representation
(define create-empty-ff
(lambda ()
(lambda (sym)
(error
'empty-ff
"No association for symbol"))))
(define extend-ff
(lambda (sym val ff)
(lambda (symbol)
(if (eq? symbol sym)
val
(apply-ff ff symbol)))))
(define apply-ff
(lambda (ff symbol)
(ff symbol)))
Create:
finite-function
ffabc: == {(a,1), (b,2), (c,3)}
> (create-empty-ff)
#
> (define ffc
(extend-ff 'c 3 (create-empty-ff)))
> (ffc 'a)
Error in empty-ff: No association for symbol.
Type (debug) to enter the debugger.
> (ffc 'b)
Error in empty-ff: No association for symbol.
Type (debug) to enter the debugger.
> (ffc 'c)
3
> (define ffbc
(extend-ff 'b 2 ffc))
> (ffbc 'a)
Error in empty-ff: No association for symbol.
Type (debug) to enter the debugger.
> (ffbc 'b)
2
> (ffbc 'c)
3
> (define ffabc (extend-ff 'a 1 ffbc))
> (ffbc 'a)
Error in empty-ff: No association for symbol.
Type (debug) to enter the debugger.
> (ffabc 'a)
1
> (ffabc 'b)
2
> (ffabc 'c)
3
>
Procedural representation quite small and easy.
(define create-empty-ff
(lambda ()
(lambda (sym)
(error
'empty-ff
"No association for symbol"))))
(define extend-ff
(lambda (sym val ff)
(lambda (symbol)
(if (eq? symbol sym)
val
(apply-ff ff symbol)))))
(define apply-ff
(lambda (ff symbol)
(ff symbol)))
Consider using records to implement finite
functions after all, not many programming
languages have first class procedures.
(define-record empty-ff ())
(define-record extended-ff (sym val ff))
(define create-empty-ff
(lambda ()
(make-empty-ff)))
(define extend-ff
(lambda (sym val ff)
(make-extended-ff sym val ff)))
(define apply-ff
(lambda (finfun symbol)
(variant-case finfun
[empty-ff ()
(error 'empty-ff "No association found")]
[extended-ff (sym val ff)
(if (eq? sym symbol)
val
(apply-ff ff symbol))]
[else (error
'apply-ff "Invalid finite function")])))
> (create-empty-ff)
#1(empty-ff)
> (define ffc
(extend-ff 'c 3
(create-empty-ff)))
> (ffc 'c)
Error: attempt to apply non-procedure #4(extended-ff c 3 #1(empty-ff)).
Type (debug) to enter the debugger.
> (apply-ff ffc 'c)
3
> (apply-ff ffc 'b)
Error in empty-ff: No association found.
Type (debug) to enter the debugger.
> (define ffbc
(extend-ff 'b 2 ffc))
> (ffbc 'a)
Error: attempt to apply non-procedure #4(extended-ff b 2 #4(extended-ff c 3 #1(empty-ff))).
Type (debug) to enter the debugger.
> (apply-ff ffbc 'a)
Error in empty-ff: No association found.
Type (debug) to enter the debugger.
> (apply-ff ffbc 'b)
2
> (apply-ff ffbc 'c)
3
> (define ffabc
(extend-ff 'a 1 ffbc))
> (apply-ff ffabc 'a)
1
> (apply-ff ffabc 'b)
2
> (apply-ff ffabc 'c)
3
> (apply-ff ffabc 'x)
Error in empty-ff: No association found.
Type (debug) to enter the debugger.
>
General algorithm for going from procedural to
record representation:
Assume the existence of
apply-ff
Step 1. Identify lambda expressions whose
evaluation yields values of the type and create a
distinct record type for each of these lambda
expressions
Step 2. Identify the free variables in the lambda
expressions whose values are distinct for each
element and allocate a field of the corresponding
record type for each of these variables
Step 3. Define the apply procedure for the type
using variant-case expression with one case per
record type, where the variable list for each case
lists the free variables from step 2 and the
consequent expression of each case is the body of
the corresponding lambda expression
Sushil Louis
Last modified: Wed Sep 29 12:54:58 PDT 1999