Review

Pragmatics of set!

You are a disgruntled programmer (they have you writing a function even called toil-away), and you decide to sabotage your code. Namely, you want it to run correctly the first 500 times its called, and then stop. [Disclaimer: Do not try this yourselves.]

Well, you realize that this is an example where we want to maintain some history from previous calls. So like the random function, we'll have a placeholder:

(define times-called 0)
(define (toil-away ...)
   (begin
     (set! times-called (add1 times-called))
     (if (> times-called 500)
         'hahaha----look-who-is-laughing-now
         ; Now, the code which actually does what it should. 
         ...)))
(Yes, it's wrong to have the magic constant 500 hard-coded in there, but that pales in comparison to what the function does.)

The customers get a teachpack containing the code, but they can't modify the teachpack, so you think your plan is fine. However, there's one problem: after your colleagues realize what you did, the customers don't even need to download a new version -- customer support tells them "just type (set! times-called 0) and things will work again. next month's version will have a feature where you won't even need to do this; you can upgrade for only $99!"

Question: How can you work this so that times-called isn't accessible to everybody?
Answer: We can just use local, and the function w/o revealing times-called:

(define toil-away
  (local [(define times-called 0)]
    ; Now, the body of the local: a function.
    (lambda (..)
       (begin
         (set! times-called (add1 times-called))
         (if (> times-called 500)
             'hahaha----look-who-is-laughing-now
             ; Now, the code which actually does what it should. 
             ...)))))
Now toil-away is the function we want, but times-called is inaccessible outside of that local.
Recall the Law of Scheme for local: it renamed times-called to be times-called%473 (which includes an invisible character impossible to type, so this name is unique, past and future), and really toil-away refers to the placeholder times-called%473.

Question: How many times does drscheme encounter the local?
Just once (even if calling the lambda-function many times).
Next question: What if the local had instead been mis-placed inside the lambda? (how many times does drscheme encounter the local in that situation?)

phonebook redux: on to objects

Let's revisit the phonebook for an extended example. We previously made it possible for two functions add-entry! and lookup to share the same state via the variable directory, a list of entries. One problem though: directory was a variable anybody could modify. We don't want people who buy our code to go mucking with directory directly, since we depend on its properties (e.g. perhaps no duplicate entries) in our code. We want to foolproof our code, so they can't break it even if they try.
You might think "go ahead give them the power to change the code; if they do something stupid that's their fault." But one lesson that has been learned in the past: if other programmers have the power to mis-use something, they will, and it result in buggy programs and crashes.
By the way, we're not only foolproofing it from customers, but also from teammates who might change variables not realizing they're violating some invariant, and from yourself in the future.

Okay, back to solving the problem. We could use the exact same approach as in toil-away, except that we need to return (a list of) two functions from the body of the local, and then just give public names to the first and second items of that list. The local renames the variable directory so that those two functions now each refer to directory%876, a renamed variable we can't access.

But somebody mentioned last time, a way to do solve the problem slightly differently. Rather than export all the utility functions outside the local, we'll export a single function phonebook-send which simply calls the other functions as needed. That is:

;; campus-phonebook-send: symbol, list-of-args --> ANY
;; Take in one of the symbols
;;    'do-lookup
;;    'add-new-entry
;; and a list of arguments for the indicated function,
;; and we'll do the requested thing.
;;
(define campus-phonebook-send
  (local [(define directory empty)
          (define (lookup name) ...directory...)
          (define (add-entry! name number)
             ...(set! directory (cons .. directory))...)]
    ; Now, as the body of the local, return the dispatch function:
    (lambda (msg args)
       (cond [(symbol=? msg 'do-lookup)    
              (lookup (first args))]
             [(symbol=? msg 'add-new-entry)
              (add-entry! (first args) (second args))]
             [else 
              (error 'phonebook-send 
                     (format "~s not a phonebook method (~s).~n" msg args))]))))


;; Examples of use:
(campus-phonebook-send 'do-lookup (list 'ian)) = false
(campus-phonebook-send 'add-new-entry (list 'ian 3843)) ; = ...the directory
(campus-phonebook-send 'do-lookup (list 'ian))   = 3843
directory  ; ERROR undefined symbol "directory" -- it was local!
This approach also solves the problem of sharing directory between the different functions, but hiding it from outsiders.

It used a different sort of perspective: rather than saying "call the function lookup, and pass it a phonebook", we are saying "take the phonebook, and tell it to do a lookup." This perspective is called object-oriented: the object is fundamental, it contains data (like a structure) but it also contains functions ("methods") inside of it, and we can have it run any of its methods.

If that's all there was to object-orient programming, it would be boring (just a superficially different way of calling functions), although it does use local in a way to provide data-hiding, which is a good thing. Also note that the dispatch function can be written automatically by the computer itself. (All object-oriented languages do this. Later we'll visit scheme's objects proper, in the last week of class.)

Our phonebook proves so successful on campus, that SWBell wants to use our code for its houston and austin phonebooks.

(define houston-phonebook-send ...)
(define austin-phonebook-send  ...)
Okay, we could copy-and-paste the above local into "...", but that's ugly. Wait, we had named the first local, the name "campus-phonebook-send". Can we just insert that name for the "..."s? Of course not -- we want separate phonebooks for each city. (This only would've worked before we introduced set!.) Aha, of course, we can make a function that returns a phonebook:
;; new-phonebook: --> phonebook-object
;; Where a phonebook-object is a function
;; like we had before (e.g. campus-phonebook was
;; one particular phonebook-object);
;; it understands the messages 'do-lookup and 'add-a-new-entry.
;;
(define (new-phonebook)
  (local [(define directory empty)
          (define (lookup name) ...directory...)
          (define (add-entry! name number)
             ...(set! directory (cons .. directory))...)]
    ; Now, as the body of the local, return the dispatch function:
    (lambda (msg args)
       (cond [(symbol=? msg 'do-lookup)
              (lookup (first args))]
             ...))))


(define  campus-phonebook-send (new-phonebook))
(define houston-phonebook-send (new-phonebook))
(define  austin-phonebook-send (new-phonebook))

;; Examples of use:
(campus-phonebook-send 'do-lookup (list 'ian))   = false
(campus-phonebook-send 'add-new-entry (list 'ian 3843)) = ;...
(campus-phonebook-send 'do-lookup (list 'ian))   = 3843

(austin-phonebook-send 'do-lookup (list 'ian))   = false  ; ian not in austin !-(

How does this work, exactly? It all works fine, thanks to local's renaming. Take a moment to hand-evaluate the above functions, to see how the stepper ends up with several top-level definitions of renamed placeholders, and renamed functions using specific placeholders:

(define directory%001 empty)
(define lookup%001     (lambda (name) ...directory%001...))
(define add-entry!%001 (lambda (name number) ...(set! directory%001 ..) ...))

(define directory%002 empty)
(define lookup%002     (lambda (name) ...directory%001...))
(define add-entry!%002 (lambda (name number) ...(set! directory%001 ..) ...))

...
(define campus-phonebook-send
  (lambda (msg args) (cond [...lookup%001...add-entry!%001...])))
(define houston-phonebook-send
  (lambda (msg args) (cond [...lookup%002...add-entry!%002...])))
Thus we see that different phonebooks are using slightly different functions -- namely, functions which refer to different (renamed) directories!

Some more terminology: we call the type "phonebook", as described in the purpose of new-phonebook, a class. That is, we had one phonebook class, and we made three instances of it (e.g. we made three objects, each of type phonebook). The function new-phonebook is called the constructor for its class.

Aside: Inheritance
We could think of having different variations of phonebooks -- perhaps later we'll implement a "phonebook-plus(tm)" which allow reverse lookups. That is, they accept all the same messages as a regular phonebook, but also accept additional messages. We'd say that phonebook-plus(tm) is a new class which extends the original phonebook class (or: it inherits from the phonebook). Note that any function which took in an (old, original) phonebook as an argument can also accept a phonebook-plus(tm), and will work fine! (This is the power of inheritance.) (How might you write new-phonebook-plus<tm>, which returns new phonbook-plus(tm)s, without having to re-write any code you already wrote, (but still keeping the old type of phonebook around)?)
Finally: Imagine a function phonebook-factory, which could make either regular old phonebooks, or (for customers who pay more money), it can make phonebook-plus(tm)s. This is called "the factory pattern".

Okay, one last exercise about using local variables and functions. Suppose we want to have each phonebooks have an ID number. so campus-phonebook-send and houston-phonebook-send can each be queried as to what their ID number is; the first will reply "1" while the other will reply "2".

(campus-phonebook-send  'get-id-num) = 1
(houston-phonebook-send 'get-id-num) = 2
((new-phonebook) 'get-id-num)        = 4
((new-phonebook) 'get-id-num)        = 5
(campus-phonebook-send  'get-id-num) = 1
How can we do this? Well, clearly each phonebook has not only a local directory variable, but also a local id-num, which is defined when the phonebook is created.
  (local [(define directory empty)
          (define (lookup name) ...directory...)
          (define id-num ids-so-far) ; Worry later 'bout maintaining ids-so-far.
          ;...
And of course we'll understand the message get-id-num, which simply returns that variable.
      (lambda (msg args)
         (cond [(symbol=? msg 'do-lookup)    
                (lookup (first args))]
               [(sumbol=? msg 'get-id-num)
                id-num]
               ;...
But how to make it so new-phonebook keeps track of ids-so-far, the next id number to give out? Oh wait, this is nothing more than the principle we saw at the first of the lecture -- keeping some state around about how many times the function has been called. We'll slip a local inbetween the define and the lambda, which defines that variable. Every time new-phonebook is actually called, in addition to returning a function (the phonebook object), we first increment our counter.
;; new-phonebook: --> phonebook-object
;; ...
;;
(define new-phonebook
  (local [(define ids-so-far 0)]

    ; Now the body of the local, which defines new-phonebook:
    ; a function which increment ids-so-far, and returns a phonebook object.
    (lambda ()
      (begin
        (set! ids-so-far (add1 ids-so-far))

        ; Now, the new phonebook function(object), in the body of this local:
        (local [(define directory empty)
                (define id-num ids-so-far)
                ..]
          (lambda (msg args)
             (cond [(symbol=? msg ...) ...]...)))))))
Pay particular attention to how ids-so-far is looked at whenever we create a phonebook object, inside the local. In this example, ids-so-far was consulted only upon creating phonebooks (and each phonebook remembered what the variable had been when it was created). But in general, there is nothing stopping the functions like lookup from making use of the current value of ids-so-far. Note that this value would be shared by all the different phonebooks in existence! (But inaccessible to anybody else.)

Terminology (will not be tested): Since id-so-far is local to the class (i.e. to all phonebooks), it's called a static variable, or class variable. (Keep in mind that this is very different from the member variables like directory, where each phonebook has their own separate version.)

Aside: Interestingly, the interplay between local, lambda, and set! is make these objects behave like structures -- we write our own functions to get (and set) the fields (member variables), and these changes can be seen by anybody who has a hold of the lambda.

Exercise:
The idea was to have shopper-card objects (one per customer), which keeps track of their most recently bought item. The following code fails to do this.

(define make-shopper-card
  (local [(define most-recently-bought #f)]
    (lambda (name)
       (lambda (command some-item)
          (cond [(eq? command 'show-name) name]
                [(eq? command 'buy) (set! most-recently-bought some-item)]
                [(eq? command 'show-fave) most-recently-bought])))))
  1. (a)What does it do instead?
  2. (b)How to fix it to get the original intent?

When is an example when we might want indeed to have the local be insdie the lambda, to create a local var many times? Probably when the value returned is a function and each function returned wants to use its own local variable (alternative: all functions returned refer to the same variable.)
The complete code from lecture:
;; toil: --> 'hi-ho-hi-ho-off-to-work
;; BUT sabotaged to only work for 3 times before breaking.
;;
(define toil-away
  (local [(define times-called 0)]
    ; Now, the body of the local: a function.
    (lambda ()
      (begin (set! times-called (add1 times-called))
             (if (> times-called 3)
                 'hahaha----look-who-is-laughing-now
                 ; Now, the code which actually does what it should:
                 'hi-ho-hi-ho-off-to-work)))))

(toil-away)
(toil-away)
(toil-away)
(toil-away)
(toil-away)





;;;;;;;; Phonebook, version 1 (one lone phonebook).

;; campus-phonebook-send: symbol, list-of-args --> ANY
;; Take in one of the symbols
;;    'do-lookup
;;    'add-new-entry
;; and a list of arguments for the indicated function,
;; and we'll do the requested thing.
;;
(define campus-phonebook-send
  (local [(define directory empty)
          (define (lookup name) (assoc name directory))
          (define (add-entry! name number)
             (set! directory (cons (list name number) directory)))]
    ; Now, as the body of the local, return the dispatch function:
    (lambda (msg args)
       (cond [(symbol=? msg 'do-lookup)    
              (lookup (first args))]
             [(symbol=? msg 'add-new-entry)
              (add-entry! (first args) (second args))]
             [else 
              (error 'campus-phonebook-send 
                     (format "~s not a phonebook method (~s).~n" msg args))]))))


;; Examples of use:
(campus-phonebook-send 'do-lookup (list 'ian)) = false
(campus-phonebook-send 'add-new-entry (list 'ian 3843))  ; = ...
(campus-phonebook-send 'do-lookup (list 'ian))   = (list 'ian 3843)
;directory  ; ERROR undefined symbol "directory" -- it was local!








;;;; Phonebooks version two (multiple independent phonebooks) 

;; new-phonebook: --> phonebook-object
;; Where a phonebook-object is a function
;; like we had before (e.g. campus-phonebook was
;; one particular phonebook-object);
;; it understands the messages 'do-lookup and 'add-a-new-entry.
;;
(define (new-phonebook)
  (local [(define directory empty)
          (define (lookup name) (assoc name directory))
          (define (add-entry! name number)
             (set! directory (cons (list name number) directory)))]
    ; Now, as the body of the local, return the dispatch function:
    (lambda (msg args)
       (cond [(symbol=? msg 'do-lookup)    
              (lookup (first args))]
             [(symbol=? msg 'add-new-entry)
              (add-entry! (first args) (second args))]
             [else 
              (error 'phonebook
                     (format "~s not a phonebook method (~s).~n" msg args))]))))


;; send: (symbol, list-of-any --> any), symbol, list-of-any --> any
;; A superficial function to stress that we're sending
;; a message to an object.
;;
(define (send object message args)
  (object message args))


(define  campus-phonebook (new-phonebook))
(define houston-phonebook (new-phonebook))
(define  austin-phonebook (new-phonebook))


;; Examples of use:
(send campus-phonebook 'do-lookup (list 'ian))   = false
(send campus-phonebook 'add-new-entry (list 'ian 3843)) ;= ...
(send campus-phonebook 'do-lookup (list 'ian))   = (list 'ian 3843)

(send austin-phonebook 'do-lookup (list 'ian))   = false  ; ian not in austin !-(



#|  A sample result of using the stepper:

(define directory%001 empty)
(define lookup%001     (lambda (name) ...directory%001...))
(define add-entry!%001 (lambda (name number) ...(set! directory%001 ..) ...))

(define directory%002 empty)
(define lookup%002     (lambda (name) ...directory%001...))
(define add-entry!%002 (lambda (name number) ...(set! directory%001 ..) ...))

...
(define campus-phonebook
  (lambda (msg args) (cond [...lookup%001...add-entry!%001...])))
(define houston-phonebook
  (lambda (msg args) (cond [...lookup%002...add-entry!%002...])))

; Thus we see that different phonebooks are
; using slightly different internal functions like "lookup" -- namely,
; functions which refer to different (renamed) directories!
|#





;;;;;;;; Phonebooks v. 3 (with an id-number)

;; new-phonebook: --> phonebook-object
;; ...
;;
(define new-phonebook
  (local [(define ids-so-far 0)]

    ; Now the body of the local, which defines new-phonebook:
    ; a function which increment ids-so-far, and returns a phonebook object.
    (lambda ()
      (begin
        (set! ids-so-far (add1 ids-so-far))
        ; Now, the new phonebook function(object), in the body of this local:
      (local [(define directory empty)
              (define (lookup name) (assoc name directory))
              (define id-num ids-so-far)
              (define (add-entry! name number)
                 (set! directory (cons (list name number) directory)))]
        ; Now, as the body of the local, return the dispatch function:
        (lambda (msg args)
           (cond [(symbol=? msg 'do-lookup)    
                  (lookup (first args))]
                 [(symbol=? msg 'add-new-entry)
                  (add-entry! (first args) (second args))]
                 [(symbol=? msg 'get-id-num)
                  id-num]
                 [else 
                  (error 'phonebook-send 
                         (format "~s not a phonebook method (~s).~n" msg args))])))))))


(define  campus-phonebook (new-phonebook))
(define houston-phonebook (new-phonebook))
(define  austin-phonebook (new-phonebook))

(send  campus-phonebook  'get-id-num empty) = 1
(send houston-phonebook  'get-id-num empty) = 2
(send    (new-phonebook) 'get-id-num empty) = 4
(send    (new-phonebook) 'get-id-num empty) = 5
(send  campus-phonebook  'get-id-num empty) = 1






;;;;; Exercise:

; The idea was to have shopper-card objects (one per customer),
; which keeps track of their most recently bought item.
; The following code fails to do this.
;
(define make-shopper-card
  (local [(define most-recently-bought #f)]
    (lambda (name)
       (lambda (command some-item)
          (cond [(eq? command 'show-name) name]
                [(eq? command 'buy) (set! most-recently-bought some-item)]
                [(eq? command 'show-fave) most-recently-bought])))))