;;; Dealt with simple data abstraction, got into more complexity than ;;; was perhaps clear with the Geography example. Go back and do some ;;; more simple stuff. Will involve us with the representation of ;;; state, generic (Polymorphic) operators, and message-passing. ;;; Simple version: (let [[balance 0]] (define WITHDRAW (lambda [amount] (if (< balance amount) "Insufficient funds" (begin (set balance (- balance amount)) balance)))) (define DEPOSIT (lambda [amount] (set balance (+ balance amount)))) (define CURRENT-BALANCE (lambda [] balance))) ;;; More complexity: error messages, $10 fine for insufficient funds: (let [[balance 0]] (define WITHDRAW (lambda [amount] (if (< balance amount) (let [[old-balance balance] [cr-string (string-cons cr "")]] (begin (set balance (max 0 (- balance 10))) (string-append "Insufficient funds; $10 fine" cr-string " Previous balance: $" (externalize ^old-balance) cr-string " Requested withdrawal: $" (externalize ^amount) cr-string " New balance: $" (externalize ^balance)))) (begin (set balance (- balance amount)) balance)))) (define DEPOSIT (lambda [amount] (set balance (+ balance amount)))) (define CURRENT-BALANCE (lambda [] balance))) ;;; But wont' worry about such complexities here. ;;; But the problem here is that there is only one account. ;;; Clearly want lots of accounts. Define MAKE-ACCOUNT of ;;; an initial balance: (define MAKE-ACCOUNT (lambda [initial-balance] (let [[balance initial-balance]] (define WITHDRAW (lambda [amount] (if (< balance amount) "Insufficient funds" (begin (set balance (- balance amount)) balance)))) (define DEPOSIT (lambda [amount] (set balance (+ balance amount)))) (define CURRENT-BALANCE (lambda [] balance))))) ;;; But this redefines WITHDRAW, DEPOSIT, etc., every time a new ;;; account is made, thereby losing the ability to refer to any ;;; previous account. Solution: (define MAKE-ACCOUNT (lambda [balance] (letrec [[WITHDRAW-FUN (lambda [amount] (if (< balance amount) "Insufficient funds" (begin (set balance (- balance amount)) balance)))] [DEPOSIT-FUN (lambda [amount] (set balance (+ balance amount)))] [CURRENT-BALANCE-FUN (lambda [] balance)]] (lambda [message] (cond [(= message 'withdraw) withdraw-fun] [(= message 'deposit) deposit-fun] [(= message 'current-balance) current-balance-fun] [$T (error "unknown message" message)]))))) (define WITHDRAW (lambda [account amount] ((account 'withdraw) amount))) (define DEPOSIT (lambda [account amount] ((account 'deposit) amount))) (define CURRENT-BALANCE (lambda [account] ((account 'current-balance)))) ;;; Note don't need initial-balance; can just use the local variable ;;; balance. ;;; This works fine, but it can be improved in terms of modularity ;;; and abstraction: ;;; First, define a CELL, over which CONTENTS and UPDATE are defined. ;;; Very much like "balance": (define CELL (lambda [contents] (letrec [[UPDATE-FUN (lambda [new-contents] (set contents new-contents))] [CONTENTS-FUN (lambda [] contents)]] (lambda [message] (cond [(= message 'contents) contents-fun] [(= message 'update) update-fun] [$T (error "unknown message" message)]))))) (define UPDATE (lambda [cell new-contents] ((cell 'update) new-contents))) (define CONTENTS (lambda [cell] ((cell 'contents)))) ;;; Can now define ACCOUNT in terms of CELLS, although it is a more ;;; complex definition to read (but it localizes the definitions of ;;; CELLS in a single place, which is good): (define MAKE-ACCOUNT (lambda [balance] (let [[account (cell balance)]] (letrec [[WITHDRAW-FUN (lambda [amount] (if (< (contents account) amount) "Insufficient funds" (update account (- (contents account) amount))))] [DEPOSIT-FUN (lambda [amount] (update account (+ (contents account) amount)))] [CURRENT-BALANCE-FUN (lambda [] (contents account))]] (lambda [message] (cond [(= message 'withdraw) withdraw-fun] [(= message 'deposit) deposit-fun] [(= message 'current-balance) current-balance-fun] [$T (error "unknown message" message)])))))) (define WITHDRAW (lambda [account amount] ((account 'withdraw) amount))) (define DEPOSIT (lambda [account amount] ((account 'deposit) amount))) (define CURRENT-BALANCE (lambda [account] ((account 'current-balance)))) ;;; But more interesting is to abstract the "message-passing" ;;; routines. The definitions of WITHDRAW, DEPOSIT, CURRENT-BALANCE, ;;; etc., are all very much the same. ;;; First, define a "send-message" facility: (define => (lambda args (if (< (length args) 2) (error "Too few arguments to =>" ^args) (((first args) (second args)) . (tail 2 args))))) ;;; It would also be nice to have something like the following: (define DEFINE-MESSAGE (lambda [message] (define (lambda args (((first args) message) . (rest args)))) ;;; Problem is with the part; should be quoted. As it ;;; happens, we can define this with a macro, which I won't explain ;;; (the code is given in the appendix). Can then define CELLS ;;; as follows: (define CELL (lambda [contents] (letrec [[UPDATE-FUN (lambda [new-contents] (set contents new-contents))] [CONTENTS-FUN (lambda [] contents)]] (lambda [message] (cond [(= message 'contents) contents-fun] [(= message 'update) update-fun] [$T (error "unknown message" message)]))))) (define-message UPDATE) (define-message CONTENTS) ;;; Even more seriously, can define a new OBJECT macro, so as ;;; to wrap all of this into one! This would allow: (define-object CELL (lambda [contents] [UPDATE (lambda [new-contents] (set contents new-contents))] [CONTENTS (lambda [] contents)])) (define-object MAKE-ACCOUNT (lambda [balance] [WITHDRAW (lambda [amount] (if (< balance amount) "Insufficient funds" (set balance (- balance amount))))] [DEPOSIT (lambda [amount] (set balance (+ balance amount))] [CURRENT-BALANCE (lambda [] balance)])) (define-object FAMILY (lambda [mother father kids] [MOTHER (lambda [] mother)] [FATHER (lambda [] father)] [KIDS (lambda [] kids)] [NEW-KID (lambda [kid] (set kids (cons kid kids)))])) ;;; ;;; Given this machinery, lets define a new kind of rail, to be ;;; called a mutable rail or M-RAIL, in which you can replaced ;;; any element or tail. (define-object M-CONS (lambda [element tail] [M-RAIL (lambda [] $true)] [M-FIRST (lambda [] element)] [M-REST (lambda [] tail)] [NEW-FIRST (lambda [new-element] (set element new-element))] [NEW-REST (lambda [new-tail] (set tail new-tail))] [M-NULL (lambda [] $false)] [M-LENGTH (lambda [] (+ 1 (m-length tail)))])) (define-object EMPTY-M-RAIL (lambda [] [M-RAIL (lambda [] $true)] [M-FIRST (lambda [] (error "empty m-rail" '?))] [M-REST (lambda [] (error "empty m-rail" '?))] [NEW-FIRST (lambda [new-element] (error "empty m-rail" '?))] [NEW-REST (lambda [new-tail] (error "empty m-rail" '?))] [M-NULL (lambda [] $true)] [M-LENGTH (lambda [] 0)])) ;;; These are hard to see; define M-EXTERNALISE: ;;; (define-object M-CONS (lambda [element tail] [M-RAIL (lambda [] $true)] [M-FIRST (lambda [] element)] [M-REST (lambda [] tail)] [NEW-FIRST (lambda [new-element] (set element new-element))] [NEW-REST (lambda [new-tail] (set tail new-tail))] [M-NULL (lambda [] $false)] [M-LENGTH (lambda [] (+ 1 (m-length tail)))] [M-EXTERNALIZE (lambda [] (let [[rs (m-externalize tail)]] (string-append "<" (externalize ^element) (if (m-null tail) "" " ") (substring 2 (string-length rs) rs))))])) (define-object EMPTY-M-RAIL (lambda [] [M-RAIL (lambda [] $true)] [M-FIRST (lambda [] (error "empty m-rail" '?))] [M-REST (lambda [] (error "empty m-rail" '?))] [NEW-FIRST (lambda [new-element] (error "empty m-rail" '?))] [NEW-REST (lambda [new-tail] (error "empty m-rail" '?))] [M-NULL (lambda [] $true)] [M-LENGTH (lambda [] 0)] [M-EXTERNALIZE (lambda [] "<>")])) ;;; This is all fine, but there is another abstraction we need: ;;; Define it so that the SAME length, first, rest, etc., work on ;;; all of these various data types. ;;; Can't be done with FIRST, REST, etc., because these are primitive ;;; functions, and 3-LISP isn't set up to do this. But the principle ;;; isn't hard. Note already that M-FIRST is defined over both empty ;;; and non-empty m-rails; it happens to work because it is a message ;;; for both of them. So we could define something like: (define REST! (lambda [arg] (cond [(rail arg) (rest arg)] [(sequence arg) (rest arg)] [(m-rail arg) (m-rest arg)] [$T (error "type error for rest" ^arg)]))) ;;; But you could imagine that *everything* would be an "object" of ;;; this sort, and that therefore this last sort of definition wouldn't ;;; be required. ;;; One final detail: hierarchies of types. ;;; =============================================================== ;;; Put the following stuff into an appendix, because it is hard: ;;; (define DEFINE-MESSAGE (mlambda [call] '(define ,(arg 1 call) (lambda args (((first args) ,^(arg 1 call)) . (rest args)))))) ;;; OBJECT: ;;; ;;; General form: ;;; ;;; (object ;;; [ ] ;;; [ ] ;;; ... ;;; [ ]) (define OBJECT (mlambda [call] (letseq [[pairs (pargs call)] [fun-names (map (lambda [pair] (acons)) pairs)]] '(begin ,(map (lambda [pair] '(define-message ,(first pair))) pairs) (letrec ,(map (lambda [pair fun-name] '[,fun-name ,(second pair)]) pairs fun-names) (lambda [message] (cond . ,(map (lambda [pair fun-name] '[(= message ,^(first pair)) ,fun-name]) pairs fun-names)))))))) (define DEFINE-OBJECT (mlambda [call] (let [[name (arg 1 call)] [variables (arg 1 (arg 2 call))] [pairs (rest (pargs (arg 2 call)))]] (let [[fun-names (map (lambda [pair] (acons)) pairs)]] '(begin (define ,name (lambda ,variables (letrec ,(map (lambda [pair fun-name] '[,fun-name ,(second pair)]) pairs fun-names) (lambda [message] (cond . ,(map (lambda [pair fun-name] '[(= message ,^(first pair)) ,fun-name]) pairs fun-names)))))) ,(map (lambda [pair] '(define-message ,(first pair))) pairs) ,^name)))))