define directly instead of a lambda that is then named. We used the lambda form just to emphasize what is going on, and that a function is just like any other value. So, normall use define, as in the second form of each example below: (define f (lambda (x y) (+ x y)))
(define (f x y) (+ x y))
(define f (lambda args (print args)))
(define (f1 . args) (print args))
(define g (lambda (x y . args) (list (list x) (list y) (list args))))
(define (g1 x y . args) (list (list x) (list y) (list args)))
if statements into cond statements, and all let statements into applications of a lambda (as per the examples in the notes). #lang racket |
|
(define (transform-if-to-cond code) |
; locates every occurrence of an if expression in code and |
; transforms it into an equivalent cond |
|
; (if c p q) => (cond (c p) (else q )) |
|
(define (trans c p q) |
(list 'cond (list c p) (list 'else q) ) ) |
|
(if (and (list? code) (not (null? code)) (eq? 'if (first code))) |
|
; recursively transform the parts of the if before transforming the if |
(apply trans (map transform-if-to-cond (rest code))) |
|
(if (list? code) |
; recursively transform expressions |
(map transform-if-to-cond code) |
; leaf expression, leave alone |
code) |
)) |
|
(define (transform-let-to-apply code) |
; locates every occurrence of an let expression in code and |
; transforms it into an equivalent lambda applied to the defining expresions. |
|
; (let ( (v1 e1) (v2 e2) ... (vn en) ) b1 b2 ... bk ) |
; => |
; ((lambda (v1 v2 ... vn) b1 b2 ... bk) e1 e2 ... en) |
|
) |
transform-if-to-cond function is implemented, but the transform-let-into-apply is not. (define code-1 |
'(let ( (x 1) (y 2) ) |
;(displayln (list "got" x y)) |
(let ( (z (+ x y)) ) |
;(displayln (list "z" z)) |
(if (< x y) |
(list "x smaller") |
(list "y smaller") |
) |
) |
)) |
|
(define (test-1) |
(newline) |
(displayln code-1) |
(displayln (eval code-1)) |
(newline) |
|
; test the if transform |
(define t1 (transform-if-to-cond code-1)) |
(displayln t1) |
(displayln (eval t1)) |
(newline) |
|
; test the let transform |
(define t2 (transform-let-to-apply code-1)) |
(displayln t2) |
(displayln (eval t2)) |
(newline) |
|
; test the two different composition orders of the transforms |
(define t3 (transform-let-to-apply (transform-if-to-cond code-1))) |
(displayln t3) |
(displayln (eval t3)) |
(newline) |
(define t4 (transform-if-to-cond(transform-let-to-apply code-1))) |
(displayln t4) |
(displayln (eval t4)) |
) |
> (test-1) |
|
'(let ((x 1) (y 2)) (let ((z (+ x y))) (if (< x y) (list "x smaller") (list "y smaller")))) |
'("x smaller") |
|
'(let ((x 1) (y 2)) (let ((z (+ x y))) (cond ((< x y) (list "x smaller")) (else (list "y smaller"))))) |
'("x smaller") |
|
'((lambda (x y) ((lambda (z) (if (< x y) (list "x smaller") (list "y smaller"))) (+ x y))) 1 2) |
'("x smaller") |
|
'((lambda (x y) ((lambda (z) (cond ((< x y) (list "x smaller")) (else (list "y smaller")))) (+ x y))) 1 2) |
'("x smaller") |
|
'((lambda (x y) ((lambda (z) (cond ((< x y) (list "x smaller")) (else (list "y smaller")))) (+ x y))) 1 2) |
'("x smaller") |
transform-let-to-apply. -- data types are defined by constructor patterns |
-- Eq equality can be derived from structural equality |
|
-- a binary tree consisting of leaves L or internal nodes N |
data BinTree = L | N BinTree BinTree deriving (Eq, Show) |
|
-- this function creates the full binary tree of size 2^(n+1) -1 |
makeBinTree 0 = L |
makeBinTree n = N (makeBinTree (n-1)) (makeBinTree (n-1)) |
|
-- this function computes the size of a binary tree |
size L = 1 |
size (N t1 t2) = 1 + size t1 + size t2 |
$ ghci |
|
GHCi, version 7.6.3: http://www.haskell.org/ghc/ :? for help |
Loading package ghc-prim ... linking ... done. |
Loading package integer-gmp ... linking ... done. |
Loading package base ... linking ... done. |
Prelude> load assign-2-tree.hs |
|
Prelude> :load assign-2-tree.hs |
[1 of 1] Compiling Main ( assign-2-tree.hs, interpreted ) |
Ok, modules loaded: Main. |
|
*Main> makeBinTree 3 |
N (N (N L L) (N L L)) (N (N L L) (N L L)) |
|
*Main> let t1 = makeBinTree 3 |
|
*Main> t1 |
N (N (N L L) (N L L)) (N (N L L) (N L L)) |
|
*Main> size t1 |
15 |
BinTree, each internal N node must have two sub trees. Thus there is no way to construct a tree of size 2. If you want a binary tree that can be of any size, then you need to introduce a new constructor N1 to BinTree that allows an internal node with only one subtree. Make this modification to BinTree.
size to account for this modification.
depth that gives the depth of the modified BinTree tree. The depth of a tree of size 1 is 1.
makeABinTree such that makeABinTree s constructs a BinTree of size exactly s and that has minimum possible depth. I.e. a long stringy tree is not allowed. #lang racket |
|
|
(define (transform-if-to-cond code) |
; locates every occurrence of an if expression in code and |
; transforms it into an equivalent cond |
|
; (if c p q) => (cond (c p) (else q )) |
|
(define (trans c p q) |
(list 'cond (list c p) (list 'else q) ) ) |
|
(if (and (list? code) (not (null? code)) (eq? 'if (first code))) |
|
; recursively transform the parts of the if before transforming the if |
(apply trans (map transform-if-to-cond (rest code))) |
|
(if (list? code) |
; recursively transform expressions |
(map transform-if-to-cond code) |
; leaf expression, leave alone |
code) |
)) |
|
(define (transform-let-to-apply code) |
; locates every occurrence of an let expression in code and |
; transforms it into an equivalent lambda applied to the values. |
|
; (let ( (v1 e1) (v2 e2) ... (vn en) ) b1 b2 ... bk ) |
; => |
; ((lambda (v1 v2 ... vn) b1 b2 ... bk) e1 e2 ... en) |
|
; vars: list of variables being defined (v1 v2 ... vn) |
; exprs list of defining expressions (e1 e2 ... en) to evaluate |
; body: list of code inside the let (b1 b2 ... bk) |
|
(define (trans vars exprs body) |
(append (list (append (list 'lambda vars) body)) exprs) |
) |
|
(if (and (list? code) (not (null? code)) (eq? 'let (first code))) |
|
(let ( (vars (map first (second code)) ) |
(exprs (map second (second code)) ) |
(body (rest (rest code)) ) |
) |
; recursively transform |
(trans vars (map transform-let-to-apply exprs) (map transform-let-to-apply body)) |
) |
|
|
(if (list? code) |
; recursively transform expressions |
(map transform-let-to-apply code) |
; leaf expression, leave alone |
code) |
)) |
|
|
(define c1 |
'(if #t |
"c1-true" |
"c1-false" |
)) |
|
(define c2 |
'(if #f |
(if (and #t (if #f #t #f)) |
"c11" |
"c12" |
) |
"c21" |
)) |
|
(define c3 |
'(let ( (x 1) (y (+ 2 3)) (z (if #t 42 -1)) ) (println "let-1") (+ x y z) ) |
) |
|
(define code-1 |
'(let ( (x 1) (y 2) ) |
;(println (list "got" x y)) |
(let ( (z (+ x y)) ) |
;(println (list "z" z)) |
(if (< x y) |
(list "x smaller") |
(list "y smaller") |
) |
) |
)) |
|
; why this does not work is explained in http://docs.racket-lang.org/guide/eval.html |
; the definitions window does not have a namespace by default. |
(define ns (make-base-namespace)) |
|
(define (test1) |
(println c1) |
(println (eval c1 ns)) |
(println (transform-if-to-cond c1)) |
(println (eval (transform-if-to-cond c1) ns)) |
(newline) |
(println c2) |
(println (eval c2 ns)) |
(println (transform-if-to-cond c2)) |
(println (eval (transform-if-to-cond c2) ns)) |
) |
|
;(test1) |
|
; our own println |
; displayln will drop the " " around strings, which makes it difficult to |
; read the transformed code. So we use println instead. |
(define (println s) (print s) (newline)) |
|
(define (test2a) |
(println c3) |
(println (eval c3 ns)) |
(println (transform-let-to-apply c3)) |
(println (eval (transform-let-to-apply c3) ns)) |
(newline) |
(println code-1) |
(println (eval code-1 ns)) |
(println (transform-let-to-apply code-1)) |
(println (eval (transform-let-to-apply code-1) ns)) |
|
) |
|
(define (test-1) |
(newline) |
(println code-1) |
(println (eval code-1)) |
(newline) |
|
; test the if transform |
(define t1 (transform-if-to-cond code-1)) |
(println t1) |
(println (eval t1)) |
(newline) |
|
; test the let transform |
(define t2 (transform-let-to-apply code-1)) |
(println t2) |
(println (eval t2)) |
(newline) |
|
; test the two different composition orders of the transforms |
(define t3 (transform-let-to-apply (transform-if-to-cond code-1))) |
(println t3) |
(println (eval t3)) |
(newline) |
(define t4 (transform-if-to-cond(transform-let-to-apply code-1))) |
(println t4) |
(println (eval t4)) |
) |
-- data types are defined by constructor patterns |
-- Eq equality can be derived from structural equality |
|
-- a binary tree consisting of leaves L or internal nodes N |
data BinTree = L | N1 BinTree | N BinTree BinTree deriving (Eq, Show) |
|
-- this function creates the full binary tree of size 2^(n+1) -1 |
makeBinTree 0 = L |
makeBinTree n = N (makeBinTree (n-1)) (makeBinTree (n-1)) |
|
-- this function computes the size of a binary tree |
size L = 1 |
size (N1 t1) = 1 + size t1 |
size (N t1 t2) = 1 + (size t1) + (size t2) |
|
-- this function computes the depth of a binary tree |
depth L = 1 |
depth (N1 t1) = 1 + (depth t1) |
depth (N t1 t2) = 1 + max (depth t1) (depth t2) |
|
-- makeABinTree creates a binary tree of size s of minimum depth |
-- in mixed prefix/infix form: (quot (s-1) 2) + (rem (s-1) 2) |
-- in pure prefix form: ( (+) (quot (s-1) 2) (rem (s-1) 2)) |
|
makeABinTree :: Integral a => a -> BinTree |
makeABinTree s |
| s <= 1 = L |
| s == 2 = N1 L |
| otherwise = |
N (makeABinTree (sizeLeft s)) (makeABinTree (sizeRight s)) |
where |
sizeLeft :: Integral a => a -> a |
sizeRight :: Integral a => a -> a |
|
sizeLeft s = max 1 ( (s-1) `quot` 2 + (s-1) `rem` 2) |
sizeRight s = max 1 ((s-1) `quot` 2) |
|
|
if expressions. It negates the condition and exchanges the true and false parts, thus preserving the logic but rewriting the expression. #lang racket |
|
(define (modify-if trans code) |
; locates every occurrence of an if expression in code and |
; applies the transform function to it. |
|
(if (and (list? code) (not (null? code)) (eq? 'if (first code))) |
; found an (if ...) expr, transform the parts |
(trans (second code) (third code) (fourth code) ) |
|
; not an if, so if a ( ) then transform each of the parts |
; otherwise leave alone |
(if (list? code) |
(map (lambda (c) (modify-if trans c)) code) |
code) |
)) |
|
|
(define c1 |
'(if true |
"c1-true" |
"c1-false" |
)) |
|
(define c2 |
'(if false |
(if (and true (if false true false)) |
"c11" |
"c12" |
) |
"c21" |
)) |
|
; transform (if c p q) into (if (not c) q p) |
(define (trans-1 c p q) |
(list 'if (list 'not c) q p) |
) |
|
(define (test1) |
(displayln (eval c1)) |
(displayln (modify-if trans-1 c1)) |
(displayln (eval (modify-if trans-1 c1))) |
(newline) |
(displayln (modify-if trans-1 c2)) |
) |
|
; (test1) |
#lang racket |
|
(define (modify-if trans code) |
; locates every occurrence of an if expression in code and |
; applies the transform function to it. |
|
; remember the trans function |
(define (mf c) (modify-if trans c)) |
|
(if (and (list? code) (not (null? code)) (eq? 'if (first code))) |
|
; recursivley transform the parts of the if before transforming the if |
(apply trans (map mf (rest code))) |
; Note above is better than |
; (trans (mf (second code)) (mf (third code)) (mf (fourth code))) |
|
(if (list? code) |
; recursively transform expressions |
(map mf code) |
; leaf expression, leave alone |
code) |
)) |
|
|
(define c1 |
'(if true |
"c1-true" |
"c1-false" |
)) |
|
(define c2 |
'(if false |
(if (and true (if false true false)) |
"c11" |
"c12" |
) |
"c21" |
)) |
|
(define (wrap-not x) |
(list 'not x) ) |
|
; transform (if c p q) into (if (not c) q p) |
(define (trans-1 c p q) |
(list 'if (wrap-not c) q p) |
) |
|
(define (test1) |
(displayln c1) |
;(displayln (eval c1)) |
(displayln (modify-if trans-1 c1)) |
(displayln (eval (modify-if trans-1 c1))) |
(newline) |
(displayln c2) |
(displayln (modify-if trans-1 c2)) |
) |
|
; (test1) |
if into a cond. #lang racket |
|
; a e-tree represents the kinds of program structures that we want to |
; manipulate. Defined recursively: |
|
; atom := a single primative unstructured element, like a number 42 |
; or symbol 'x |
|
; e-tree := atom | ( e-tree * ) |
; that is, an e-tree is an atom, or a list of 0 or more e-trees |
|
; a path in an e-tree is a list of indexes that navigate to a |
; sub-component of the e-tree. Navigation via a path is defined |
; by the following function, modelled after list-ref |
|
(define (e-tree-ref e-tree path) |
(if (null? path) e-tree |
(e-tree-ref (list-ref e-tree (first path)) (rest path))) |
) |
|
; examples: |
(define (test-e-tree-ref) |
(define t1 '(a (b c) (d (e f) (g h (i j))))) |
|
(map displayln (list |
t1 |
" " |
(e-tree-ref t1 '()) |
(e-tree-ref t1 '(0)) |
(e-tree-ref t1 '(2)) |
(e-tree-ref t1 '(2 1 1)) |
(e-tree-ref t1 '(2 2 2 1)) |
|
)) |
(void) |
) |
|
; given an e-tree and a path, we would like to edit the sub e-tree |
; at the endpoint of the path. |
|
; first we need a primative for editing an element a list |
|
; given editing function edit-fn, list l, and position n inside l, |
; return a list |
; which is l with (list-ref l n) replaced with (fn (list-ref l n)) |
|
(define (list-edit edit-fn l n) |
(let-values |
( [ (a b) (split-at l n) ] ) ; note capture a 2 value result |
|
(if (null? b) |
(error (format "edit-list n of ~a >= length l of ~a" n l)) |
(append a (list (edit-fn (first b))) (rest b)) |
) |
) |
) |
|
; given tree e-tree, and path edit-path, return an e-tree |
; which is the original with the subtree t reached by edit-path |
; repaced by (edit-fn t) |
|
(define (e-tree-edit edit-fn e-tree edit-path) |
(cond |
; empty path edits entire e-tree |
[ (null? edit-path) (edit-fn e-tree) ] |
|
; for longer path, replace the (first edit-path) position in e-tree with the |
; result of recursing down the sub e-tree at that position using the rest |
; of the edit path |
|
[ else (list-edit |
(lambda (sub-e-tree) (e-tree-edit edit-fn sub-e-tree (rest edit-path))) |
e-tree |
(first edit-path)) |
] |
)) |
|
(define (test-e-tree-edit) |
(define t1 '(a (b c) (d (e f) (g h (i j))))) |
|
(map displayln (list |
t1 |
" " |
(e-tree-edit (lambda (s) (rest s)) t1 '()) |
(e-tree-edit (lambda (s) 'f1) t1 '(0)) |
(e-tree-edit (lambda (s) (list (second s) (first s))) t1 '(1)) |
(e-tree-edit (lambda (s) (list 'f2 s) ) t1 '(2 1 1)) |
(e-tree-edit (lambda (s) (list 'lambda (list (e-tree-ref s '(1))) s) ) t1 '(2 2 2)) |
|
)) |
(void) |
) |
|
; perform the same edit at multiple points in the tree. You need |
; to be careful if the paths intersect in the sense that the endpoint |
; of one path is in a sub e-tree below the endpoint of another path. |
|
(define (e-tree-edit-many edit-fn in-tree edit-paths) |
(foldl (lambda (path cur-e-tree) (e-tree-edit edit-fn cur-e-tree path)) |
in-tree edit-paths) |
) |
|
(define (test-e-tree-edit-many) |
(define t1 '(a (b c) (d (e f) (g h (i j))))) |
|
(map displayln (list |
t1 |
" " |
(e-tree-edit-many (lambda (s) 'f1) t1 '( (0) (1 1))) |
(e-tree-edit-many (lambda (s) (list 'f2 s) ) t1 '( (2 1 1) (2 2 2)) ) |
|
)) |
(void) |
) |
|
; find paths to all instances of term in expression expr |
; A path is a list of 0-origin indexes into the expression tree |
; indicating how to navigate down to the root to locate the term |
|
(define (e-tree-find-paths target-e-tree e-tree [paths '()] [rootpath '()] ) |
(cond |
; are we at a matching term? |
[ (equal? target-e-tree e-tree) (append paths (list rootpath) ) ] |
|
; ok, no match, no recursion since e-tree empty |
[ (null? e-tree) paths ] |
|
; recurse into sub e-trees accumulating paths to matches |
[ (list? e-tree) |
(second |
(foldl |
; (e s) is current sub e-tree e, prior state s |
; s state is (n p) where |
; n is position of e in containing e-tree, |
; p collection of paths found so far |
(lambda (e s) |
(let ( |
; pull apart state |
(n (first s)) |
(p (second s)) |
) |
; new state - next position, add newly found paths |
(list |
(add1 n) |
(e-tree-find-paths target-e-tree e p (append rootpath (list n))) |
) |
) |
) |
(list 0 paths) e-tree |
) |
) ] |
|
[ else paths ] |
) |
) |
|
(define (test-e-tree-find-paths) |
(define t1 '(a (b a) (b (a f) (f h (b a))))) |
|
(map displayln (list |
t1 |
" " |
(e-tree-find-paths 'a t1) |
(e-tree-find-paths 'b t1) |
(e-tree-find-paths '(b a) t1) |
|
|
(e-tree-edit-many (lambda (s) 'delta) t1 (e-tree-find-paths 'a t1)) |
(e-tree-edit-many (lambda (s) 'delta) t1 (e-tree-find-paths '(b a) t1) ) |
)) |
(void) |
) |
|
; NOTE: write a version of e-tree-find-paths that has a test predicate |
; other than equal? |
|
; Program transformations |
|
; Here is an attempt to extract out the continuation for a term |
; inside a e-tree. |
|
; assuming that e-tree contains non special forms, then extract the |
; continuation context about the term at point path. |
|
|
(define (extract-continuation-1 e-tree path) |
(cond |
; if the path has length 1, we are at a term ( ... ) |
|
; in first position of list, it is a fn call |
[ (equal? path '(0)) '(lambda (x) x) ] |
|
; in other positions it is a term to evaluate then pass up to |
; enclosing expressions |
[ (null? (rest path)) |
(let ( [ var (gensym 'v- ) ] ) |
(list 'lambda (list var) (list-edit (lambda (s) var) e-tree (first path)) ) |
)] |
|
; the path descends, so we need to capture the context around and |
; descend further |
|
[ else |
(let ( [ var (gensym 'v- ) ] ) |
(list 'lambda (list var) (e-tree-edit (lambda (s) var) e-tree path)) |
)] |
|
|
)) |
|
(define (f-test x y) |
(if (null? x) y (cons (first x) (f-test (rest x) (cons (first x) y))) ) |
) |
|
; run this test by doing |
; (eval (test-convert)) |
; (test-cc '(a b) '(1 2)) |
; you should get: '(a b b a 1 2) |
; |
(define (test-convert) |
(define e1 ' |
(define (f x y) |
(if (null? x) y (cons (first x) (f (rest x) (cons (first x) y))) ) |
)) |
|
(eval (convert-to-cc-style e1 'test-cc)) |
|
) |
|
; converts a cannonical recursive definition into continuation- |
; passing style. |
; the function MUST have the form |
; (define (f ... ) |
; (if (basecase? ...) base-expression recursion-expression) ) |
; |
(define (convert-to-cc-style defn new-fn-name) |
|
(or (eq? 'if (e-tree-ref defn '(2 0))) |
(error (format "not a if-style definition ~a" defn))) |
|
; get the parts of the definition |
(let ( |
[ args (e-tree-ref defn '(1)) ] |
[ fn-name (e-tree-ref defn '(1 0)) ] |
[ base-test (e-tree-ref defn '(2 1)) ] |
[ base-case (e-tree-ref defn '(2 2)) ] |
[ recursion (e-tree-ref defn '(2 3)) ] |
) |
|
|
; locate the recursive call in the recursion |
(define p1 (e-tree-find-paths fn-name recursion)) |
|
; make sure only one call |
(or (= 1 (length p1)) |
(error (format "not a single recursion in ~a" defn) ) ) |
|
; move up to expression that needs result of recursive call |
; and make that the continuation |
(define cc1 (list-edit |
(lambda (s) (list 'cc s)) |
(extract-continuation-1 recursion (drop-right (first p1) 1)) |
2)) |
(displayln cc1) |
|
; insert continuation into the original recursion |
(define new-rec (append (e-tree-ref recursion (drop-right (first p1) 1)) (list cc1))) |
|
(displayln new-rec) |
|
; create the default continuation |
|
; rewrite defintion to be in continuation passing style |
(define new-def |
(list 'define (append (second defn) '( (cc (lambda (x) x)))) |
(list 'if base-test (list 'cc base-case) new-rec) ) |
) |
|
|
(displayln new-def) |
|
; rename the original function to the new name |
(define final-def |
(e-tree-edit-many (lambda (s) new-fn-name) new-def (e-tree-find-paths fn-name new-def)) ) |
|
(displayln final-def) |
|
; then eval the final result to create the new function. |
|
final-def |
|
)) |
|
| 8. Week 6 - Feb 10 CMPUT 325 Schedule / Version 2.31 2014-04-04 |