Data Abstraction
Essentials of Programming Languages
Specifying Data via Interfaces
We do not want to be concerned with representations of data types
- manipulation files
- arithmetic operations...
We may also decide to change the representation of the data
Data abstraction divides a data type into:
- an interface: It tells us
- what the data of the type represents
- what the operations on the data are
- what properties these operations may be relied on to have
- an implementation: It provides
- a specific representation of the data
- code for the operations that make use of that data representation
Representation
𝑣 : The representation of data v
Data type of natural numbers:
-The interface is to consist of four procedures: zero, is-zero?, successor,
and predecessor
(zero) = 0
(is-zero? 𝑛 = #t n=0
#f n≠0
(successor 𝑛 ) = 𝑛 + 1 (n ≥ 0)
(predecessor 𝑛 + 1 ) = [n] (n ≥ 0)
Client Program
The client code manipulates the new data only through the operations
specified in the interface
-This code is representation-independent
A sample of client program, manipulating natural numbers:
(define plus
(lambda (x y)
(if (is-zero? x)
y
(successor (plus (predecessor x) y)))))
No matter what representation is in use
Interfaces
Most interfaces contain
- some constructors
-for building elements of the data type
- some observers
-for extract information from values of the data type
Zero, successor, and predecessor are constructors and is-zero? is observer.
Representation
Some representations for natural numbers:
1) Unary representation:
-natural number n is represented by a list of n #t’s
-0 is represented by (), 1 is represented by (#t), 2 is represented by (#t (#t)),
... 0 = ()
𝑛 + 1 = (#t . 𝑛 )
In this representation, we can satisfy the specification by writing
(define zero (lambda () '()))
(define is-zero? (lambda (n) (null? n)))
(define successor (lambda (n) (cons #t n)))
(define predecessor (lambda (n) (cdr n)))
Representation
Some representations for natural numbers:
2) Scheme number representation:
- we simply use Scheme's internal representation of numbers
(define zero (lambda () 0))
(define is-zero? (lambda (n) (zero? n)))
(define successor (lambda (n) ( + n 1)))
(define predecessor (lambda (n) (- n 1)))
Representation
3) Bignum representation:
-Numbers are represented in base N, for some large integer N
-The representation becomes a list consisting of numbers between 0 and N-1
- with least-significant bigit first
-This representation makes it easy to represent integers that are much larger
than can be represented in a machine word
𝑛 = () n=0
(r . 𝑞 ) n = qN + r , 0 ≤ 𝑟 < 𝑁
So if N = 16, then 33 = ( 1 2 ) and 258 = ( 2 0 1 ) , since
258= 2 × 160 + 0 × 161 + 1 × 162
Opaque vs. Transparent
Types can be divided into:
-Opaque types
-The representation of a type is hidden, so it cannot be exposed
by any operation (including printing)
-Transparent types
Scheme does not provide a standard mechanism for creating new
opaque types
-we define interfaces and rely on the writer of the client program
to be discreet and use only the procedures in the interfaces
Representation Strategies for Data Types
Consider a data type of environments.
An environment associates a value with each element of a finite set of
variables
An environment is a function whose domain is a finite set of variables,
and whose range is the set of all Scheme values
environment env = {(𝑣𝑎𝑟1 , 𝑣𝑎𝑙1 ),.... (𝑣𝑎𝑟𝑛 , 𝑣𝑎𝑙𝑛 )}
The interface to this data type has three procedures, specified as follows:
(empty-env) = 𝜙
(apply-env 𝑓 𝑣𝑎𝑟) = 𝑓(var)
(extend-env 𝑣𝑎𝑟 v 𝑓 = 𝑔
where g(𝑣𝑎𝑟1 ) = v if 𝑣𝑎𝑟1 = 𝑣𝑎𝑟
𝑓(𝑣𝑎𝑟1 ) otherwise
Environment
For example, the expression
> (define e
(extend-env ‘d 6
(extend-env ‘y 8
(extend-env ‘x 7
(extend-env ‘y 14
(empty-env))))))
defines an environment e such that e(d) = 6, e(x) = 7, e(y) = 8, and e is
undefined on any other variables.
In this example, empty-env and extend-env are the constructors, and
apply-env is the only observer
Environment
We can obtain a representation of environments by observing that
every environment can be built by starting with the empty environment
and applying extend-env n times, for some n≥ 0, e.g.,
(extend-env 𝑣𝑎𝑟𝑛 𝑣𝑎𝑙𝑛
...
(extend-env 𝑣𝑎𝑟1 𝑣𝑎𝑙1
(empty-env))...)
So every environment can be built by an expression in the following
grammar:
𝐸𝑛𝑣 − 𝑒𝑥𝑝::= (empty-env)
::= (extend-env 𝐼𝑑𝑒𝑛𝑡𝑖𝑓𝑖𝑒𝑟 𝑆𝑐ℎ𝑒𝑚𝑒 − 𝑣𝑎𝑙𝑢𝑒 𝐸𝑛𝑣 − 𝑒𝑥𝑝)
A data-structure representation of environments
𝐸𝑛𝑣 = (empty-env) | (extend-env 𝑉𝑎𝑟 𝑆𝑐ℎ𝑒𝑚𝑒𝑉𝑎𝑙 𝐸𝑛𝑣)
𝑉𝑎𝑟 = sym
empty-env: () → 𝐸𝑛𝑣
(define empty-env
(lambda () (list 'empty-env)))
extend-env: 𝑉𝑎r x 𝑆𝑐ℎ𝑒𝑚𝑒𝑉𝑎𝑙 × 𝐸𝑛𝑣 → 𝐸𝑛𝑣
(define extend-env
(lambda (var val env)
(list 'extend-env var val env)))
A data-structure representation of environments
apply-env 𝐸𝑛𝑣 × 𝑉𝑎r → 𝑆𝑐ℎ𝑒𝑚𝑒𝑉𝑎𝑙
(define apply-env
(lambda (env search-var) Env = (empty-env) | (extend-env 𝑉𝑎𝑟 𝑆𝑐ℎ𝑒𝑚𝑒𝑉𝑎𝑙 𝐸𝑛𝑣)
(cond
((equal? (car env) 'empty-env)
(report-no-binding-found search-var))
(if(equal? (car env) ‘extend-env)
(let ((saved-var (cadr env))
(saved-val (caddr env))
(saved-env (cadddr env)}}
(if (equal? search-var saved-var)
saved-val
(apply-env saved-env search-var))))
(else
(report-invalid-env env)))))
(define report-no-binding-found
(lambda (search-var)
(eopl: error 'apply-env “No binding for -s” search-var)))
(define report-invalid-env
(lambda (env)
(eopl:error 'apply-env "Bad environment: -s" env)))
Interfaces for Recursive Data Types
𝐿𝑐 − 𝑒𝑥𝑝 ∷= 𝐼𝑑𝑒𝑛𝑡𝑖𝑓𝑖𝑒𝑟
::= (lambda 𝐼𝑑𝑒𝑛𝑡𝑖𝑓𝑖𝑒𝑟 𝐿𝑐 − 𝑒𝑥𝑝)
::= (𝐿𝑐 − 𝑒𝑥𝑝 𝐿𝑐 − 𝑒𝑥𝑝 )
The definition of occurs-free? in section 1.2.4 is not as readable as
it might be
Our interface will have constructors and two kinds of observers:
predicates and extractors
An Interface for Lambda calculus expressions
The constructors are:
𝐿𝑐 − 𝑒𝑥𝑝 ∷= 𝐼𝑑𝑒𝑛𝑡𝑖𝑓𝑖𝑒𝑟
var-exp : Var → Lc-exp ::= (lambda 𝐼𝑑𝑒𝑛𝑡𝑖𝑓𝑖𝑒𝑟 𝐿𝑐 − 𝑒𝑥𝑝)
lambda-exp : Var × Lc-exp → Lc-exp ::= (𝐿𝑐 − 𝑒𝑥𝑝 𝐿𝑐 − 𝑒𝑥𝑝 )
app-exp : Lc-exp × Lc-exp → Lc-exp
The predicates are:
var-exp? : Lc-exp → Bool
lambda-exp? : Lc-exp → Bool
app-exp? : Lc-exp → Bool
Finally, the extractors are
var-exp->var : Lc-exp → Var
lambda-exp->bound-var : Lc-exp → Var
lambda-exp->body : Lc-exp → Lc-exp
app-exp->rator : Lc-exp → Lc-exp
app-exp->rand : Lc-exp → Lc-exp
Occurs-free?
𝐿𝑐 − 𝑒𝑥𝑝 ∷= 𝐼𝑑𝑒𝑛𝑡𝑖𝑓𝑖𝑒𝑟
occurs-free? : 𝑆𝑦𝑚 × 𝐿𝑐𝐸𝑥𝑝 → 𝐵𝑜𝑜𝑙 ::= (lambda 𝐼𝑑𝑒𝑛𝑡𝑖𝑓𝑖𝑒𝑟 𝐿𝑐 − 𝑒𝑥𝑝)
(define occurs-free? ::= (𝐿𝑐 − 𝑒𝑥𝑝 𝐿𝑐 − 𝑒𝑥𝑝 )
(lambda (search-var exp)
(cond
((var-exp? exp) (equal? search-var (var-exp->var exp)))
((lambda-exp? exp)
(and
(not (equal? search-var (lambda-exp->bound-var exp)))
(occurs-free? search-var (lambda-exp->body exp))))
(else
(or
(occurs-free? search-var (app-exp->rator exp))
(occurs-free? search-var (app-exp->rand exp)))))))
Interfaces for Recursive Data Types
Designing an interface for a recursive data type
1. Include one constructor for each kind of data in the data
type.
2. Include one predicate for each kind of data in the data
type.
3. Include one extractor for each piece of data passed to a
constructor of the data type.