Skip to content

Commit

Permalink
Add slides; update logfile
Browse files Browse the repository at this point in the history
  • Loading branch information
nmheim committed Mar 11, 2024
1 parent 118446b commit ee80095
Show file tree
Hide file tree
Showing 4 changed files with 157 additions and 73 deletions.
8 changes: 2 additions & 6 deletions lectures/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,14 @@ introduces the programming language [Racket](https://racket-lang.org/).
[Slides](https://github.com/aicenter/FUP/blob/main/lectures/lecture01.pdf).
[Log](https://github.com/aicenter/FUP/blob/main/lectures/lecture01.rkt).


## [Lecture 02](lecture02): Lists & Trees
Focuses on Racket lists and trees. Further, it introduces the unit testing library
[Rackunit](https://docs.racket-lang.org/rackunit/index.html).

[Slides](https://github.com/aicenter/FUP/blob/main/lectures/lecture02.pdf).
[Log](https://github.com/aicenter/FUP/blob/main/lectures/lecture02.rkt).

## [Lecture 3](lecture03): Higher Order Functions
Deals with higher-order functions like (`map`, `filter`, `foldl`), function closures and Racket
structures.

[Slides](https://github.com/aicenter/FUP/blob/main/lectures/lecture03.pdf).
[Log](https://github.com/aicenter/FUP/blob/main/lectures/lecture03.rkt).

## [Lecture 3](lecture03): Higher Order Functions
Deals with higher-order functions like (`map`, `filter`, `foldl`), function closures and Racket
Expand All @@ -32,6 +27,7 @@ structures.
[Slides](https://github.com/aicenter/FUP/blob/main/lectures/lecture03.pdf).
[Log](https://github.com/aicenter/FUP/blob/main/lectures/lecture03.rkt).


## [Lecture 4](lecture04): Lazy Evaluation
Introduces pattern matching, and explains how to implement lazy evaluation and streams in Racket.

Expand Down
35 changes: 27 additions & 8 deletions lectures/lecture04.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ outline: deep
---


# Pattern matching, Lazy evaluation, Streams

# Pattern matching & Lazy evaluation

## Pattern matching

Expand Down Expand Up @@ -341,7 +340,7 @@ In Racket, the implicit definition of the stream of natural numbers based on the
(define nats2 (stream-cons 0 (add-streams ones nats2)))
```

### Applications of streams
## Applications of streams

We have seen that streams provide an exciting way to deal with potentially infinite structures. Let us see some concrete situations where streams could be helpful.

Expand All @@ -365,7 +364,14 @@ cpu time: 875 real time: 869 gc time: 640
49999995000000
```

Streams are further helpful because they can improve code modularity. When we need to generate a potentially infinite data structure, we must insert some tests into the generating code to stop the generation process. Consequently, the generating code and the tests are inseparable. On the other hand, if we use lazily evaluated data structures like streams, we can pretend that the infinite structure is first generated entirely, and then we do its post-processing independently. For more details on this idea, see the paper by John Hughes.[^why-fp-matters]
### Newton-Raphson

Streams are further helpful because they can improve code modularity. When we need to generate a
potentially infinite data structure, we must insert some tests into the generating code to stop the
generation process. Consequently, the generating code and the tests are inseparable. On the other
hand, if we use lazily evaluated data structures like streams, we can pretend that the infinite
structure is first generated entirely, and then we do its post-processing independently. For more
details on this idea, see the paper by John Hughes.[^why-fp-matters]

[^why-fp-matters]: John Hughes: Why Functional Programming Matters. Comput. J. 32(2): 98-107 (1989)

Expand All @@ -377,7 +383,7 @@ A terminating condition could be $|1-\frac{g_i}{g_{i+1}}|\leq\varepsilon$ for a

Now, we compare the code that mixes the generating code with the terminating condition and the modular code utilizing streams.

```scheme
```scheme:line-numbers
(define eps 0.000000000001)
(define (mean . xs) (/ (apply + xs) (length xs)))
(define (next-guess n g) (mean g (/ n g)))
Expand Down Expand Up @@ -434,11 +440,24 @@ Joining these two pieces gives us the desired square-root function:
```
It is reasonable to consider a solution using streams because of code modularity. When the solution is modular, the coder can independently focus on smaller pieces of code. This is mentally easier than devising one complex function. The above example should give you an idea of modularity. On the other hand, it is perhaps too simple to illustrate the advantage of streams, as the first solution is pretty straightforward.

For a more exciting example, consider a situation when our application needs to explore a graph of some states or configurations. For instance, we can look for a path in a digraph leading to a goal state, or the states can represent states of a game like chess, and we need to find the next move based on the graph exploration. During the exploration, we typically build a tree of already explored states as we explore the graph. We start in the initial state, which is the root node. Its children are the neighbors of the initial state. Other nodes are generated by getting neighbors of neighbors, and so on. This tree could be large or infinite, e.g., if the explored graph has cycles.

Without lazy evaluation, we usually generate the tree and simultaneously test conditions telling us when to stop the generating process. Using a lazily evaluated tree, we can first create the tree completely (even if it is infinite) and then traverse it to reveal the necessary portion of the tree nodes. Let us discuss an example using lazy evaluation to make it more concrete.
### Depth First Search (DFS)
For a more exciting example, consider a situation when our application needs to explore a graph of
some states or configurations. For instance, we can look for a path in a digraph leading to a goal
state, or the states can represent states of a game like chess, and we need to find the next move
based on the graph exploration. During the exploration, we typically build a tree of already
explored states as we explore the graph. We start in the initial state, which is the root node. Its
children are the neighbors of the initial state. Other nodes are generated by getting neighbors of
neighbors, and so on. This tree could be large or infinite, e.g., if the explored graph has cycles.

Without lazy evaluation, we usually generate the tree and simultaneously test conditions telling us
when to stop the generating process. Using a lazily evaluated tree, we can first create the tree
completely (even if it is infinite) and then traverse it to reveal the necessary portion of the tree
nodes. Let us discuss an example using lazy evaluation to make it more concrete.

Suppose we are given a digraph to explore, i.e., we have a function generating neighbors of a node. Initially, we are in the state $1$, and we look for a path leading from $1$ to node $3$; see the picture below. Note that the edge between $1$ and $2$ is bidirectional.
Suppose we are given a digraph to explore, i.e., we have a function generating neighbors of a node.
Initially, we are in the state $1$, and we look for a path leading from $1$ to node $3$; see the
picture below. Note that the edge between $1$ and $2$ is bidirectional.

![](/img/digraph.png){ style="width: 80%; margin: auto;" }

Expand Down
Binary file added lectures/lecture04.pdf
Binary file not shown.
187 changes: 128 additions & 59 deletions lectures/lecture04.rkt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#lang racket

(require rackunit)
(require plot)
;(require racket/stream)

;;; Lecture 4 - Lazy evaluation
Expand All @@ -15,35 +16,18 @@
(define (my-lazy-if c a b) (if c (a) (b)))
(my-lazy-if #t (thunk 0) (thunk (/ 1 0)))

; pass a thunk executed in a new thread
(thread
(thunk
(let ([x (foldl + 0 (range 0 10000000))])
(displayln x))))

;;; Streams
; My stream application
(define (ints-from n)
(cons n (delay (ints-from (+ n 1)))))

(define nats (ints-from 0))
(force (cdr nats))

; Racket stream implementation
(define (stream-from n)
(stream-cons n (stream-from (+ n 1))))

(define stream-nats (stream-from 0))
(stream->list (stream-take stream-nats 10))
(time (stream-fold + 0 (in-range 1000000)))
(time (foldl + 0 (range 1000000)))

(define (log x)
(printf "Logging: ~a~n" x)
x)
(define ps (stream-cons (/ 1 0) (/ 1 0)))
;(stream-first ps)
(stream-rest ps)

(define st (stream (log 1) (log 2) (log 3)))
(stream-first st)
(stream-rest st)
(stream-first (stream-rest st))
;;; Explicit definitions of streams
; naturals
(define (nats n)
(stream-cons n (nats (+ n 1))))

; Return the first successful application of f on elements of lts
(define (first-success f lst)
Expand All @@ -53,44 +37,129 @@
(if (eqv? res #f) ; is the application successul?
(first-success f (cdr lst)) ; no - try next element
res ; yes - return it
)
)
)
)
))))

; Lazy version
(define (lazy-first-success f st)
(stream-first (stream-filter identity (stream-map f st)))
)
(stream-first (stream-filter identity (stream-map f st))))

(define (test n)
;(displayln n)
(if (> n 500) n #f)
)
(if (> n 500) n #f))

(time (car (filter identity (map test (range 0 100000)))))
(time (lazy-first-success test (range 0 100000)))

;; Macros

; lazy if
(define-syntax macro-if
(syntax-rules ()
((macro-if c a b)
(my-lazy-if c (thunk a) (thunk b)))))

(macro-if (null? '()) '() (car '()))

; list comprehension
(define-syntax list-comp
(syntax-rules (: <- if)
[(list-comp <expr> : <id> <- <lst>)
(map (lambda (<id>) <expr>) <lst>)]

[(list-comp <expr> : <id> <- <lst> if <cond>)
(map (lambda (<id>) <expr>)
(filter (lambda (<id>) <cond>) <lst>))]
)
)

(list-comp (+ x 2) : x <- '(2 3 5) if (>= 3 x))


;;; Explicit definitions
(define (ints-from n)
(cons n (delay (ints-from (+ n 1)))))

(define naturals (ints-from 0))

(define (repeat f a0)
(stream-cons a0 (repeat f (f a0))))


;;; Implicit definitions
; constant stream
(define ones (stream-cons 1 ones))
(stream->list (stream-take ones 10))

; cyclic streams
(define ab (stream-cons 'a (stream-cons 'b ab)))
(stream->list (stream-take ab 10))

(define abc (stream* 'a 'b 'c abc))
(stream->list (stream-take abc 10))

; stream-append arguments are not delayed
; we can redefine it to define cyclic streams
(define (stream-append s1 s2)
(if (stream-empty? s1)
(force s2)
(stream-cons (stream-first s1) (stream-append (stream-rest s1) s2))))

(define week-days '(mon tue wed thu fri sat sun))
(define stream-days (stream-append week-days (delay stream-days)))

; summing infinite streams
(define (add-streams s1 s2)
(stream-cons (+ (stream-first s1)
(stream-first s2))
(add-streams (stream-rest s1)
(stream-rest s2))))

; natural numbers defined implicitly
(define nats2 (stream-cons 0 (add-streams ones nats2)))
(stream->list (stream-take nats2 10))

;;; Eager Newtwon-Raphson

(define eps 0.000000000001)
(define (mean . xs) (/ (apply + xs) (length xs)))
(define (next-guess n g) (mean g (/ n g)))
(define (good-enough? n1 n2 eps) (< (abs (- 1 (/ n1 n2))) eps))

; generation & termination are intertwined
(define (my-sqrt n [g 1.0])
(define new-g (next-guess n g))
(if (good-enough? g new-g eps)
new-g
(my-sqrt n new-g)))

;;; Lazy Newton-Raphson
(define (within eps seq)
(define fst (stream-first seq))
(define rest (stream-rest seq))
(define snd (stream-first rest))
(if (good-enough? fst snd eps)
snd
(within eps rest)))

(define (lazy-sqrt n [g 1.0])
(within eps (repeat (curry next-guess n) g)))


;;; Depth First Search
(struct arc (source target) #:transparent)
(struct node (data children) #:transparent)

(define g (list (arc 1 2) (arc 2 1)
(arc 1 5) (arc 2 4)
(arc 4 5) (arc 5 6)
(arc 6 3) (arc 3 4)))

(define (get-neighbors g v)
(map arc-target (filter (lambda (arc) (equal? v (arc-source arc))) g)))

(define (make-tree get-successors v)
(define successors (get-successors v))
(node v (stream-map (curry make-tree get-successors) successors)))

(define t (make-tree (curry get-neighbors g) 1))

(define (get-ext-paths g path)
(define end (car path))
(define neighbors (get-neighbors g end))
(map (curryr cons path) neighbors))

(define t-en (make-tree (curry get-ext-paths g) '(1)))

(define (filter-children pred tree)
(match tree
[(node data children)
(node data (stream-map (curry filter-children pred)
(filter pred (stream->list children))))]))

(define t-en-f
(filter-children (compose not check-duplicates node-data) t-en))

(define (dfs goal? tree)
(match tree
[(node path children)
(if (goal? path)
(reverse path)
(ormap (curry dfs goal?) (stream->list children)))]))

(dfs (compose (curry eqv? 3) car) t-en-f)
; => '(1 2 4 5 6 3)

0 comments on commit ee80095

Please sign in to comment.