Stream - IIIA CSIC

Streams
IIIA
o Data organization: like a signal processing system:
o Abstractions over data management patterns.
o Programming as a circuit design process with high level
components.
o Clarity, modularity, reusability.
Initial
Value
Initial
Stream
Integral
TAPIA 2005-2006
Example I
IIIA
o Function to add the square of the values of the
odd leaves of a tree.
(defun add-square-odd (tree)
(if (leaf? tree)
(if (odd? tree)
(square tree)
0)
(+ ( add-square-odd (left-tree tree))
( add-square-odd (right-tree tree)))))
TAPIA 2005-2006
Example II
IIIA
o Function to list the odd Fibonacci numbers
fib(0) = 0
fib(1) = 1
fib(n) = fib(n-1) +fib(n-2)
0,1,1,2,3,5,8,13,...
(defun odd? (x)
(not (= (mod x 2) 0)))
(defun fib (x)
(case x
(0 0)
(1 1)
(t (+ (fib (- x 1)) (fib (- x 2))))))
(defun list-odd-fibs (n)
(defun next (k)
(unless (> k n)
(let ((f (fib k)))
(if (odd? f)
(cons f (next (1+ k)))
(next (1+ k))))))
(next 1))
TAPIA 2005-2006
Engineering
IIIA
o add-square-odd
o
o
o
o
Enumerate the leaves of a tree
Filter selecting the odd ones
Compute the squares of the selected
Accumulate the results
ENUMERATE
leaves tree
FILTER
odd?
MAP
square
ACCUMULATE
+, 0
o List-odd-fibs
o
o
o
o
Enumerate the integers from 1 to N
Compute the corresponding Fibonacci numbers
Filter selecting the odd ones
Accumulate the results on a list
ENUMERATE
integers
TAPIA 2005-2006
MAP
fib
FILTER
odd?
ACCUMULATE
cons, nil
Stream model
IIIA
o Previous programs:
o Enumeration and selection: odd? function and recursive call
o Accumulation: in the test and recursive call
o Previous functions are a mix of abstract components
o STREAM TECHNIQUE GOAL: to built programs
where the flow of the signal is explicit
o Advantatges:
o Conceptual clarity of the code
o Modularity and reusability: filter, map, accumulate (second
order?)
o Signals are implemented as data structures called
STREAMS: a sequence of elements
TAPIA 2005-2006
IIIA
(Abstract Data Type) Stream
Independent of the implementation
Domain: Elements, Streams, Bool
Functions: the-empty-stream: Stream
cons-stream: Element x Stream Æ Stream
head: Stream Æ Element
tail: Stream Æ Stream
empty-stream?: Stream Æ Bool
Axioms: head(cons-stream(a,S)) = a
tail(cons-stream(a,S)) = S
empty-stream?(the-empty-stream) = true
Syntax
Semantics
We can change the implementation without changing the syntax and the semantics
TAPIA 2005-2006
First implementation
IIIA
Possible LISP
implementation: dotted
pairs or lists
the-empty-stream
cons-stream
head
tail
empty-stream?
()
CONS
FIRST
REST
NULL
(defconstant the-empty-stream nil)
(defun empty-stream? (s) (null s))
(defun head (s) (car s))
(defun cons-stream (a b) (cons a b))
(defun tail (s) (cdr s))
TAPIA 2005-2006
Example I
IIIA
(defun enumerate-tree (tree)
(if (leave? tree)
(cons-stream tree the-empty-stream)
(append-streams (enumerate-tree (left-tree tree))
(enumerate-tree (right-tree tree)))))
(defun append-streams (s1 s2)
(if (empty-stream? s1)
s2
(cons-stream (head s1)
(append-streams (tail s1) s2))))
(defun filter-odd (s)
(cond ((empty-stream? s) the-empty-stream)
((odd? (head s))
(cons-stream (head-s) (filter-odd (tail s))))
(t (filter-odd (tail s)))))
TAPIA 2005-2006
Example I
IIIA
(defun map-square (s)
(if (empty-stream? s)
the-empty-stream
(cons-stream (square (head s)) (map-square (tail s)))))
(defun acumulate-+ (s)
(if (empty-stream? s)
0
(+ (head s) (acumulate-+ (tail s)))))
(defun add-square-odd (tree)
(if (leaf? tree)
(if (odd? tree)
(square tree)
0)
(+ ( add-square-odd (left-tree tree))
( add-square-odd (right-tree tree)))))
(defun add-square-odd (tree)
(acumulate-+
(map-square
(filter-odd
(enumerate-tree tree)))))
TAPIA 2005-2006
Reusability
IIIA
o Similarly with the other function …
(defun list-odd-fibs (n)
(acumulate-cons
(filter-odd
(map-fib
(enumerate-interval 1 n)))))
o Using the same functions:
addition of the squares of the n
firsts numbers of Fibonacci.
TAPIA 2005-2006
(defun list-square-fib (n)
(accumulate-cons
(map-square
(map-fib
(enumerate-interval 1 n)))))
Higher order functions
IIIA
(defun accumulate (combiner init stream)
(if (empty-stream? stream)
init
(defun sum-stream (stream)
(funcall combiner
(accumulate #'+ 0 stream))
(head stream)
(defun prod-stream (stream)
(accumulate combiner
init
(accumulate #'* 1 stream))
(tail stream))))) (defun concat-stream (stream)
(accumulate #'cons nil stream))
(defun filter (pred stream)
(cond ((empty-stream? stream)
the-empty-stream)
((funcall pred (head stream))
(cons-stream (head stream)
(filter pred (tail stream))))
(t (filter pred (tail stream)))))
TAPIA 2005-2006
First summary
IIIA
o Pros:
o Streams can be considered the standard interface among
programs
o Stream programming is elegant and produces compact code.
o Cons:
o The current implementation is very inefficient in time and
memory
TAPIA 2005-2006
IIIA
Efficiency
(defun add-primes (a b)
(labels ((iterate (cont acum)
(cond ((> cont b) acum)
((prime? cont) (iterate (1+ cont) (+ cont acum)))
(t (iterate (1+ cont) acum)))))
(iterate a 0)))
(defun add-primes (a b)
(accumulate '+
0
(filter prime?
(enumerate-interval a b))))
Consider:
(head (tail (filter primo? (enumerate-interval 10000 1000000))))
We have to generate and memorize 1.000.000 integers!
TAPIA 2005-2006
IIIA
Lazy evaluation
o Elegance and efficiency of streams is based on lazy
evaluation.
o IDEA: implementation of CONS-STREAM that builds
partially a stream. The stream will be built “on
demand”
(defmacro delay (exp) `(function (lambda () ,exp)))
(defun force (exp) (funcall exp))
Second
implementation
TAPIA 2005-2006
(defconstant the-empty-stream nil)
(defun empty-stream? (s) (null s))
(defmacro cons-stream (a b) `(cons ,a (delay ,b)))
(defun head (s) (first s))
(defun tail (s) (force (rest s)))
Enumerate-interval
IIIA
(defun enumerate-interval (x y)
(unless (> x y)
(cons-stream x
(enumerate-interval (+ x 1) y))))
o Without delay
o Overflow 1000
o Overflow 10000 compiled (compile ‘enumerate-interval)
o Continuation? Tail recursive.
o With delay
o (enumerate-interval 1 10) Æ (1 . #<closure SPECIAL>)
o (tail (enumerate-interval 1 10) Æ (tail (1 . #<closure SPECIAL>))
(force (rest (1 . #<closure SPECIAL>))) Æ
(force (#<closure SPECIAL>)) Æ (funcall ...
TAPIA 2005-2006
IIIA
Delayed evaluation, efficiency
(head
(tail
(filter odd?
(enumerate-interval 10000 1000000))))
(enumerate-interval 10000 1000000)
(cons 10000
(delay (enumerate-interval 10001 1000000)))
(1 …)
(defun filter (pred
stream)
(cond ((empty-stream? stream)
the-empty-stream)
((funcall pred (head stream))
(cons-stream (head stream)
(filter pred (tail stream))))
(t (filter pred (tail stream)))))
TAPIA 2005-2006
IIIA
Delayed evaluation, efficiency
o Filter: head (10000) is even, then it force the
evaluation of the tail of the stream
(cons 10001
(delay (enumerate-interval 1002 1000000)))
o The final result is 10003
o The elements generated are those strictly necessary
to obtain the result
o Lazy: eval when necessary
TAPIA 2005-2006
IIIA
LISP: lexical closures
o Lexical closure: a function that, when invoked on
arguments, executes the body in the lexical
environment that was captured at the time of the
creation of the lexical closure, augmented by bindings
of the function's parameters to the corresponding
arguments.
o Lexical environment: that part of the environment
that contains bindings whose names have lexical scope.
o Dynamic environment
(let ((x 0))
(defun f (y)
(setf x (1+ x))
TAPIA 2005-2006
Improvement: Memoization
IIIA
o Lazy evaluation: call-by-name (Algol 60)
o Do not evaluate the same argument many times
o Memoizing: call-by-need, to put into memory the
results of evaluation
o Table of argument/result
TAPIA 2005-2006
IIIA
Memoization
(defmacro memo-proc (proc)
`(let ((already-run? nil)
(result nil))
#’(lambda ()
(if (not already-run?)
(block then
(setq result (,proc))
(setq already-run? t)
result)
result))))
Third
implementation
(defmacro delay (exp) `(memo-proc (lambda () ,exp)))
o Force is the same function
TAPIA 2005-2006
Lexical Closures
IIIA
o Lexical closures are dangerous: side-effects with the
assignment of its variables
(defun lex-clos ()
(let ((x 0)
(a the-empty-stream))
(setq a (cons-stream 3 (cons-stream x a)))
(pprint (head (tail a)))
(setq x 1)
(pprint (head (tail a)))))
with memoizing:
==> 0
==> 1
without memoizing:
==> 0
==> 0
TAPIA 2005-2006
Infinite Streams
IIIA
o Streams are only evaluated “component to component”
by demand
o Infinite streams
o Infinite recursion based on streams
(defun integers-starting-from (n)
(cons-stream n (integers-starting-from (1+ n))))
(defun integers () (integers-starting-from 1))
TAPIA 2005-2006
IIIA
Infinite Streams
o Definition of all the “no-divisible by 7” integers
(defun divisible? (x y) (= (rem x y) 0))
(defun no-sevens
(filter #'(lambda (x) (not (divisible? x 7)))
integers))
(defun filter (pred stream)
(cond ((empty-stream? stream) the-empty-stream)
((funcall pred (head stream))
(cons-stream (head stream)
(filter pred (tail stream))))
(t (filter pred (tail stream)))))
TAPIA 2005-2006
IIIA
Infinite Streams
o With the nth-stream function
(defun nth-stream (n s)
(if (= n 0)
(head s)
(nth-stream (1- n) (tail s))))
(nth-stream 100 no-sevens) Æ 117
TAPIA 2005-2006
Implicit Streams
IIIA
o Interesting definition of Fibonacci numbers
o Recursive definition of variables!
(setq fibs (cons-stream 0
(cons-stream 1
(add-streams (tail fibs) fibs))))
(defun add-streams (s1 s2)
(cond ((empty-stream? s1) s2)
((empty-stream? s2) s2)
(t (cons-stream (+ (head s1) (head s2))
(add-streams (tail s1) (tail s2))))))
(0 1 (add-streams (1 ...)(0 ...))
(0 1 1 (add-streams (1 ...)(1 ...))
(0 1 1 2 (add-streams (2 ...)(1 ...))
(0 1 1 2 3 (add-streams ...
TAPIA 2005-2006
Experiments
IIIA
o Comparison of delay techniques
o Call-by-need
o (time (nth-stream 1000 fibs)) Æ 0.417 s
o Call-by-name
o (time (nth-stream 20 fibs))
Æ 10.383 s
o Compilation of nth-stream (call-by-need, tail
recursive)
o (time (nth-stream 10000 fibs)) Æ 2.3 s
o (time (nth-stream 10000 fibs)) Æ 0.3 s (compiled)
o Other implicit definitions
o (setq ones (cons-stream 1 ones))
o (setq integers (cons-stream 1 (add-streams ones integers)))
TAPIA 2005-2006
Sieve of Eratosthenes
IIIA
Sieve
Head
Tail
cons
Filter: Not
Divisible?
Sieve
Signal processing technique applied to Sieve
(defun sieve (stream)
(cons-stream
(head stream)
(sieve (filter
#'(lambda (x) (not (divisible? x (head stream))))
(tail stream)))))
(setf primes (sieve (integers-starting-from 2)))
(2 3 4 5 ...
(2 3 4 5 6 7 8 9 ...
(2 3 5 7 9 ...
(2 3 5 7 9 ...
TAPIA 2005-2006
IIIA
Summary
o Modular and homogeneous programming of functions
o Delay and force are the main functions for lazy
evaluation
o Lazy evaluation =
call-by-need + lazy constructors
o State without assignment
o Infinite concepts, recursively defined
o Functional input/output: stream interface
TAPIA 2005-2006
State representation
IIIA
o Stream of withdraws
(defun make-withdraw (balance)
#'(lambda (amount)
(setq balance (- balance amount))
balance)
(defun stream-withdraw (balance amount-stream)
(cons-stream
balance
(stream-withdraw (- balance
(head amount-stream))
(tail amount-stream))))
TAPIA 2005-2006
Stream problems
IIIA
1. Stream consumption
2. Difficulty to represent relations
Bank account:
Joan
Merge
Account
Maria
(a1 b1 a2 b2 a3 ...)
o Solution: non functional component, non deterministic
merge. Wait for input in the streams and consume it
immediately. Problem: non functional time concept.
TAPIA 2005-2006
IIIA
Relations
o In the following relation there is no
A+B=C
an input/output relation.
o Restrictions over the values of A, B y C.
o Signal processing model is not useful
TAPIA 2005-2006