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 map
ped "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