set-[struct]-[field]!(define-struct [struct] (... [field] ...))Then there is a corresponding function
(set-[struct]-[field]! [a-struct-expr] exp)
Semantics of make-[struct]:
Instead of going off and creating a chest-of-drawers with the
indicated contents and returning that chest-of-drawers (the structure),
we refine this:
make-[struct]
creates the structure but returns a reference to that
structure.
References are a new type of value;
in particular we don't pass structures to other functions,
but only references to structures.
(We have no way of ever getting hold of the structure itself;
most of the time we'll just say "the structure ..." rather
than "a reference to the structure...", for convenience.)
When hand-evaluating
make-[struct],
we will
draw a box (the created structure), and draw a signpost with
an arrow referring to the box (the reference).
(Sometimes, instead of the box, we'll just write the
make-[struct], and draw the reference to it.)
(If in full-text mode, we'll write
(define somereference^ (make-struct ...))and understand that
somereference^ is the
equivalent of the arrow referring to that make-struct.
Example:
(define-struct cat (name age)) (make-cat 'morris 93)) (define cat2 (make-cat 'garfield 87)) (define cat3 cat2)will hand-evaluate to:
(make-cat cat1 (make-cat 'morris 93))
= (define randomcat^ (make-cat 'morris 93))
(define cat2 (make-cat 'garfield 87))
= { (define somecat^ (make-cat 'garfield 87)) ; make-cat created the struct,
{ ; and made the reference to it.
{ (define cat2 somecat^) ; Make-cat returned a reference.
(define cat3 cat2)
= (define cat3 somecat^) ; Evaluate cat2, as usual: it's a placeholder;
; just look up the placeholder's define'd value.
Note that the (define somecat^ ...) is a bit of
abuse of notation: it's not a real define (since we can only define
placeholders, but the somecat^ we said is a reference
value (a signpost).
set-[struct]-[field]!
Evaluate [exp] to get val,
evaluate [a-struct-expr] to get strct
and then follow the reference strct
(it had better be (a reference to) a struct of type
[type], of course),
and modify the [field] to be val.
Note that no placeholders were modified in this process.
set-[struct]-[field]!
Call this a constructor (make-[struct])
once for each corresponding thing you're modeling.
That is, if you're working at a vet clinic and you have two separate cats
each named 'morris and age 93,
then have two calls to make-cat.
(If you are repeatedly seeing the same cat,
only create it once and use that same value in the future:
either name it with a placeholder, or keep a list of cat structures
(retrieving the right cat from the list as needed, etc.)
Call the mutator
set-[struct]-[field]!set-cat-age!
to modify the age field.)
What about translate-posn -- is it appropriate to change the object's fields, or to return a new posn? Probably the latter; we don't think of points themselves as changing. If something has a position, we might change it's position, but the old position doesn't change.
(define origin (make-posn 0 0)) ; Recall: rectangle structures contain a posn as their NW corner, ; and circle structures contain a posn for their center. ;; Character-shapes for my rad new videogame. ;; They both start out at Secret Base Headquarters: ;; (define blocko (make-rectangle origin 10 30 'blue)) (define blobbo (make-circle origin 20 'red))What if we want to translate one of these two shapes? But if we have a Rectangle structure (which contains a posn as its northwest corner), and we have the idea of this Rectangle being moved around, then we might well call
set-Rectangle-nw!,
installing an entirely new posn.
[Draw pictures!]
What happens if you used (set-posn-x! (Rectangle-nw blobbo) ..);
and other structures also had a reference to that same posn?
Suppose a structure had several posn's involved
(e.g.
(define-struct alien-body head legs),
where head and legs were each shapes, and should be moved
in synchronicity?
What if the alien gets blasted to smithereens?
So, what does the hand-evaluation of
(cons 3 (cons 4 (cons 5 empty)))look like? How about for
(cons (make-cat 'morris 93) (cons (make-cat 'garfield 87) empty))What if , as in previous lectures, cats had an additional field
bestbuddy refering to a cat?
Exercise: make the smallest example of you can,
of a structure which somehow contains a reference to itself.
(How does it print out?
Remember shared from last lecture.)
How would we represent a map of the US, before we had mutating structures? How can we make them more properly, now?
(define-struct city (name nhbrs seen?)) ;; ;; A city is a ;; (make-city [symbol] [list-of-cities] [boolean]) ;; where seen? is to be used later, in path-finding. ; Initially no neighbors: (define nyc (make-city 'nyc empty false)) (define la (make-city 'la empty false)) (define slc (make-city 'slc empty false)) (define hou (make-city 'houston empty false)) (define nome (make-city 'nome empty false)) (define reno (make-city 'reno empty false)) (define cities (list nyc la slc hou nome reno)) (set-city-nhbrs! nyc (list la slc hou nyc)) (set-city-nhbrs! la (list reno slc nyc)) (set-city-nhbrs! hou (list nyc la)) (set-city-nhbrs! slc (list reno)) (set-city-nhbrs! reno (list slc))
What is our algorithm for searching?
;; path: city, city --> list-of-city or false
;; Return false if no path, otherwise return
;; a path which includes both endpoints.
;;
(define (path src dest)
(cond [(eq? src dest) (list src)]
[else (local {(define otherways (map (lambda (s) (path s dest))
(city-nhbrs src)))
(define viable-otherways (filter list? otherways))}
(cond [(empty? viable-otherways) false]
[else (cons src (first viable-otherways))]))]))
;;; Alternate version of path, w/o using map or fold:
;;
(define (path src dest)
(cond [(eq? src dest) (list src)]
[else (local {(define otherway (path-from-list (city-nhbrs src) dest))}
(if (list? otherway)
(cons src otherway)
false))]))
;; path-from-list: list-of-city, city --> list-of-city or false
;; Return either a path from one of srcs to dest,
;; or false if no such path exist from anybody in srcs.
;;
(define (path-from-list srcs dest)
(cond [(empty? srcs) false]
[else (local [(define try-first (path (first srcs) dest))]
(if (list? try-first)
try-first
(path-from-list (rest srcs) dest)))]))
(path slc nyc) =
;; Uh-oh, runs forever!
;; Hand-evaluation reveals
;; It's checking slc, reno, slc, reno, slc, ...
N.B.
Instead of a separate function
path-from-list,
we could instead mapped "find a path to dst" on each
of the cities neighbors,
and then grabbed the first element from all these solutions
(or, checked if map returned a list containing only false
from each neighbor).
Same diff.
What do we need to do, to avoid loops?
;; path: city, city --> list-of-city or false
;; Return false if no path, otherwise return
;; a path which includes both endpoints.
;;
(define (path src dest)
(cond [(eq? src dest) (list src)]
[(city-seen? src) false] ; Already visited, so don't go here again.
[else
(begin
(set-city-seen?! src false)
(local [(define otherway (path-from-list (city-nhbrs src) dest))]
(if (list? otherway)
(cons src otherway)
false))]))
(find-path slc nyc) = false
(find-path nyc slc) = ; nyc -> la -> slc, printed in "shared" form.
; Note, it's not the shortest path.
(code)
This approach is called "depth-first search": it searches the first neighbor, and the first of that neighbor, and ... and backtracks when it reaches a dead end (or something seen before).
A fundamentally different way of finding a path from one place to another is "breadth-first search": look at all places one step away from src, then two steps away, then three steps, ... This method is guaranteed to always return a shortest path (can you seen why?).
©2002 Ian Barland