From 2821275df73eb5159d7ae005315f450269d83ae2 Mon Sep 17 00:00:00 2001 From: Matthias Felleisen Date: Thu, 8 Jan 2009 23:04:27 +0000 Subject: [PATCH] simplified world terminology svn: r13041 --- .../2htdp/scribblings/universe.scrbl | 162 +++++++++--------- 1 file changed, 84 insertions(+), 78 deletions(-) diff --git a/collects/teachpack/2htdp/scribblings/universe.scrbl b/collects/teachpack/2htdp/scribblings/universe.scrbl index 6609c611b4..14997b1957 100644 --- a/collects/teachpack/2htdp/scribblings/universe.scrbl +++ b/collects/teachpack/2htdp/scribblings/universe.scrbl @@ -32,9 +32,9 @@ This @tt{universe.ss} teachpack implements and provides the functionality for creating interactive, graphical programs that consist of plain - mathematical functions. We refer to such programs as @defterm{world} + mathematical functions. We refer to such programs as @deftech{world} programs. In addition, world programs can also become a part of a - @defterm{universe}, a collection of worlds that can exchange messages. + @deftech{universe}, a collection of worlds that can exchange messages. The purpose of this documentation is to give experienced Schemers and HtDP teachers a concise overview for using the library. The first part of the @@ -42,7 +42,7 @@ The purpose of this documentation is to give experienced Schemers and HtDP presents an illustration of how to design such programs for a simple domain; it is suited for a novice who knows how to design conditional functions for symbols. The second half of the documentation focuses on - @tech{universe} programs: how it is managed via a server, how @tech{world} + "universe" programs: how it is managed via a server, how @tech{world} programs register with the server, etc. The last two sections show how to design a simple universe of two communicating worlds. @@ -138,17 +138,17 @@ The following picture provides an intuitive overview of the workings of a @image["nuworld.png"] - The @scheme[big-bang] form installs @scheme[World_0] as the initial - world. The handlers @scheme[tock], @scheme[react], and @scheme[click] transform + The @scheme[big-bang] form installs @scheme[World_0] as the initial @tech{WorldState}. + The handlers @scheme[tock], @scheme[react], and @scheme[click] transform one world into another one; each time an event is handled, @scheme[done] is used to check whether the world is final, in which case the program is shut down; and finally, @scheme[draw] renders each world as a scene, which is then displayed on an external canvas. -@deftech{World} : @scheme[any/c] +@deftech{WorldState} : @scheme[any/c] The design of a world program demands that you come up with a data - definition of all possible states. We use @tech{World} to refer to + definition of all possible states. We use @tech{WorldState} to refer to this collection of data, using a capital W to distinguish it from the program. In principle, there are no constraints on this data definition though it mustn't be an instance of the @tech{Package} @@ -176,7 +176,7 @@ The design of a world program demands that you come up with a data starts a @tech{world} program in the initial state specified with @scheme[state-expr], which must of course evaluate to an element of - @tech{World}. Its behavior is specified via the handler functions + @tech{WorldState}. Its behavior is specified via the handler functions designated in the optional @scheme[spec] clauses, especially how the @tech{world} program deals with clock ticks, with key events, with mouse events, and eventually with messages from the universe; how it renders @@ -190,7 +190,7 @@ The design of a world program demands that you come up with a data @item{ @defform[(on-tick tick-expr) #:contracts - ([tick-expr (-> (unsyntax @tech{World}) (unsyntax @tech{World}))])]{ + ([tick-expr (-> (unsyntax @tech{WorldState}) (unsyntax @tech{WorldState}))])]{ tell DrScheme to call the @scheme[tick-expr] function on the current world every time the clock ticks. The result of the call becomes the @@ -199,7 +199,7 @@ current world. The clock ticks at the rate of 28 times per second.}} @item{ @defform/none[(on-tick tick-expr rate-expr) #:contracts - ([tick-expr (-> (unsyntax @tech{World}) (unsyntax @tech{World}))] + ([tick-expr (-> (unsyntax @tech{WorldState}) (unsyntax @tech{WorldState}))] [rate-expr natural-number/c])]{ tell DrScheme to call the @scheme[tick-expr] function on the current world every time the clock ticks. The result of the call becomes the @@ -234,7 +234,7 @@ A character is used to signal that the user has hit an alphanumeric @defform[(on-key change-expr) #:contracts - ([change-expr (-> (unsyntax @tech{World}) key-event? (unsyntax @tech{World}))])]{ + ([change-expr (-> (unsyntax @tech{WorldState}) key-event? (unsyntax @tech{WorldState}))])]{ tell DrScheme to call @scheme[change-expr] function on the current world and a @tech{KeyEvent} for every keystroke the user of the computer makes. The result of the call becomes the current world. @@ -288,7 +288,7 @@ All @tech{MouseEvent}s are represented via symbols: @defform[(on-mouse clack-expr) #:contracts ([clack-expr - (-> (unsyntax @tech{World}) natural-number/c natural-number/c (unsyntax @tech{MouseEvent}) (unsyntax @tech{World}))])]{ + (-> (unsyntax @tech{WorldState}) natural-number/c natural-number/c (unsyntax @tech{MouseEvent}) (unsyntax @tech{WorldState}))])]{ tell DrScheme to call @scheme[clack-expr] on the current world, the current @scheme[x] and @scheme[y] coordinates of the mouse, and and a @tech{MouseEvent} for every (noticeable) action of the mouse by the @@ -303,7 +303,7 @@ All @tech{MouseEvent}s are represented via symbols: @defform[(on-draw render-expr) #:contracts - ([render-expr (-> (unsyntax @tech{World}) scene?)])]{ + ([render-expr (-> (unsyntax @tech{WorldState}) scene?)])]{ tell DrScheme to call the function @scheme[render-expr] whenever the canvas must be drawn. The external canvas is usually re-drawn after DrScheme has @@ -312,7 +312,7 @@ All @tech{MouseEvent}s are represented via symbols: @defform/none[(on-draw render-expr width-expr height-expr) #:contracts - ([render-expr (-> (unsyntax @tech{World}) scene?)] + ([render-expr (-> (unsyntax @tech{WorldState}) scene?)] [width-expr natural-number/c] [height-expr natural-number/c])]{ @@ -325,7 +325,7 @@ All @tech{MouseEvent}s are represented via symbols: @defform[(stop-when last-world?) #:contracts - ([last-world? (-> (unsyntax @tech{World}) boolean?)])]{ + ([last-world? (-> (unsyntax @tech{WorldState}) boolean?)])]{ tell DrScheme to call the @scheme[last-world?] function whenever the canvas is drawn. If this call produces @scheme[true], the world program is shut down. Specifically, the clock is stopped; no more @@ -436,7 +436,8 @@ it to the locked position; and} Simulating any dynamic behavior via a @tech{world} program demands two different activities. First, we must tease out those portions of our domain that change over time or in reaction to actions, and we must - develop a data representation @deftech{D} for this information. Keep in + develop a data representation for this information. This is what we call + @tech{WorldState}. Keep in mind that a good data definition makes it easy for readers to map data to information in the real world and vice versa. For all others aspects of the world, we use global constants, including graphical or visual @@ -447,7 +448,7 @@ Second, we must translate the actions in our domain---the arrows in the teachpack can deal with. Once we have decided to use the passing of time for one aspect, key presses for another, and mouse movements for a third, we must develop functions that map the current state of the - world---represented as data from @tech{D}---into the next state of the + world---represented as data from @tech{WorldState}---into the next state of the world. Put differently, we have just created a wish list with three handler functions that have the following general contract and purpose statements: @@ -455,16 +456,16 @@ Second, we must translate the actions in our domain---the arrows in the @(begin #reader scribble/comment-reader (schemeblock -;; tick : @tech{D} -> @tech{D} +;; tick : WorldState -> WorldState ;; deal with the passing of time (define (tick w) ...) -;; click : @tech{D} @emph{Number} @emph{Number} @tech{MouseEvent} -> @tech{D} +;; click : WorldState @emph{Number} @emph{Number} @tech{MouseEvent} -> WorldState ;; deal with a mouse click at @emph{(x,y)} of kind @emph{me} ;; in the current world @emph{w} (define (click w x y me) ...) -;; control : @tech{D} @tech{KeyEvent} -> @tech{D} +;; control : WorldState @tech{KeyEvent} -> WorldState ;; deal with a key event (symbol, char) @emph{ke} ;; in the current world @emph{w} (define (control w ke) ...) @@ -487,15 +488,14 @@ Our first and immediate goal is to represent the world as data. In this the door is whether it is locked, unlocked but closed, or open. We use three symbols to represent the three states: -@deftech{SD} : state of door - @(begin #reader scribble/comment-reader (schemeblock -;; The state of the door (SD) is one of: +;; WorldState is one of: ;; -- @scheme['locked] ;; -- @scheme['closed] ;; -- @scheme['open] +;; interpretation: state of door )) Symbols are particularly well-suited here because they directly express @@ -535,14 +535,14 @@ a visible scene.} ] -Let's start with @emph{automatic-closer}. Substituting @tech{SD} for -@tech{D} and @emph{automatic-closer} for @emph{tick}, we get its contract, +Let's start with @emph{automatic-closer}. Since @emph{automatic-closer} +acts as the @scheme[on-tick] handler, we get its contract, and it is easy to refine the purpose statement, too: @(begin #reader scribble/comment-reader (schemeblock -;; automatic-closer : @tech{SD} -> @tech{SD} +;; automatic-closer : WorldState -> WorldState ;; closes an open door over the period of one tick (define (automatic-closer state-of-door) ...) )) @@ -560,7 +560,7 @@ and it is easy to refine the purpose statement, too: @(begin #reader scribble/comment-reader (schemeblock -;; automatic-closer : @tech{SD} -> @tech{SD} +;; automatic-closer : WorldState -> WorldState ;; closes an open door over the period of one tick (check-expect (automatic-closer 'locked) 'locked) @@ -604,7 +604,7 @@ For the remaining three arrows of the diagram, we design a function that @(begin #reader scribble/comment-reader (schemeblock -;; door-actions : @tech{SD} @tech{KeyEvent} -> @tech{SD} +;; door-actions : WorldState @tech{KeyEvent} -> WorldState ;; key events simulate actions on the door (define (door-actions s k) ...) )) @@ -644,7 +644,7 @@ purpose: @(begin #reader scribble/comment-reader (schemeblock -;; render : @tech{SD} -> @tech{scene} +;; render : WorldState -> @tech{scene} ;; translate the current state of the door into a large text (define (render s) (text (symbol->string s) 40 'red)) @@ -719,9 +719,9 @@ Note the last clause includes @scheme[empty] of course. Each world-producing callback in a world program---those for handling clock tick events, keyboard events, and mouse events---may produce a - @tech{Package} in addition to just a @tech{World}. + @tech{Package} in addition to just a @tech{WorldState}. -@deftech{Package} represents a pair consisting of a @tech{World} (state) +@deftech{Package} represents a pair consisting of a @tech{WorldState} and a message from a @tech{world} program to the @tech{server}. Because programs only send messages via @tech{Package}, the teachpack does not provide the selectors for the structure, only the constructor and a @@ -731,38 +731,38 @@ Each world-producing callback in a world program---those for handling clock determine whether @scheme[x] is a @tech{Package}.} @defproc[(make-package [w any/c][m sexp?]) package?]{ - create a @tech{Package} from a @tech{World} and an @tech{S-expression}.} + create a @tech{Package} from a @tech{WorldState} and an @tech{S-expression}.} -As mentioned, all event handlers may return @tech{World}s or @tech{Package}s; +As mentioned, all event handlers may return @tech{WorldState}s or @tech{Package}s; here are the revised specifications: @defform/none[(on-tick tick-expr) #:contracts - ([tick-expr (-> (unsyntax @tech{World}) (or/c (unsyntax @tech{World}) package?))])]{ + ([tick-expr (-> (unsyntax @tech{WorldState}) (or/c (unsyntax @tech{WorldState}) package?))])]{ } @defform/none[(on-tick tick-expr rate-expr) #:contracts - ([tick-expr (-> (unsyntax @tech{World}) (or/c (unsyntax @tech{World}) package?))] + ([tick-expr (-> (unsyntax @tech{WorldState}) (or/c (unsyntax @tech{WorldState}) package?))] [rate-expr natural-number/c])]{ } @defform/none[(on-key change-expr) #:contracts - ([change-expr (-> (unsyntax @tech{World}) key-event? (or/c (unsyntax @tech{World}) package?))])]{ + ([change-expr (-> (unsyntax @tech{WorldState}) key-event? (or/c (unsyntax @tech{WorldState}) package?))])]{ } @defform/none[(on-mouse clack-expr) #:contracts ([clack-expr - (-> (unsyntax @tech{World}) natural-number/c natural-number/c (unsyntax @tech{MouseEvent}) - (or/c (unsyntax @tech{World}) package?))])]{ + (-> (unsyntax @tech{WorldState}) natural-number/c natural-number/c (unsyntax @tech{MouseEvent}) + (or/c (unsyntax @tech{WorldState}) package?))])]{ } If one of these event handlers produces a @tech{Package}, the content of the world field becomes the next world and the message field specifies what the world sends to the universe. This distinction also explains why the data - definition for @tech{World} may not include a @tech{Package}. + definition for @tech{WorldState} may not include a @tech{Package}. @subsection{Connecting with the Universe} @@ -823,14 +823,14 @@ The @scheme[on-receive] clause of a @scheme[big-bang] specifies the event handle @defform[(on-receive receive-expr) #:contracts - ([receive-expr (-> (unsyntax @tech{World}) sexp? (or/c (unsyntax @tech{World}) package?))])]{ + ([receive-expr (-> (unsyntax @tech{WorldState}) sexp? (or/c (unsyntax @tech{WorldState}) package?))])]{ tell DrScheme to call @scheme[receive-expr] for every message receipt, on the current - @tech{World} and the received message. The result of the call becomes the current - @tech{World}. + @tech{WorldState} and the received message. The result of the call becomes the current + @tech{WorldState}. Because @scheme[receive-expr] is (or evaluates to) a world-transforming function, it too can produce a @tech{Package} instead of just a - @tech{World}. If the result is a @tech{Package}, its message content is + @tech{WorldState}. If the result is a @tech{Package}, its message content is sent to the @tech{server}.} The diagram below summarizes the extensions of this section in graphical form. @@ -842,9 +842,9 @@ A registered world program may send a message to the universe server message is transmitted to the server, which may forward it to some other world program as given or in some massaged form. The arrival of a message is just another event that a world program must deal with. Like - all other event handlers @emph{receive} accepts a @tech{World} and some + all other event handlers @emph{receive} accepts a @tech{WorldState} and some auxiliary arguments (a message in this case) and produces a - @tech{World} or a @tech{Package}. + @tech{WorldState} or a @tech{Package}. When messages are sent from any of the worlds to the universe or vice versa, there is no need for the sender and receiver to synchronize. Indeed, a sender @@ -961,7 +961,7 @@ A @tech{server} keeps track of information about the @tech{universe} that represented depends on the situation and the programmer, just as with @tech{world} programs. -@deftech{Universe} @scheme[any/c] represent the server's state For running +@deftech{UniverseState} @scheme[any/c] represent the server's state For running @tech{universe}s, the teachpack demands that you come up with a data definition for (your state of the) @tech{server}. Any piece of data can represent the state. We just assume that you introduce a data definition @@ -1010,16 +1010,16 @@ description. Two of them are mandatory: @item{ @defform[(on-new new-expr) #:contracts - ([new-expr (-> (unsyntax @tech{Universe}) world? - (cons (unsyntax @tech{Universe}) [listof mail?]))])]{ + ([new-expr (-> (unsyntax @tech{UniverseState}) world? + (cons (unsyntax @tech{UniverseState}) [listof mail?]))])]{ tell DrScheme to call the function @scheme[new-expr] every time another world joins the universe.}} @item{ @defform[(on-msg msg-expr) #:contracts - ([msg-expr (-> (unsyntax @tech{Universe}) world? sexp? - (cons (unsyntax @tech{Universe}) [listof mail?]))])]{ + ([msg-expr (-> (unsyntax @tech{UniverseState}) world? sexp? + (cons (unsyntax @tech{UniverseState}) [listof mail?]))])]{ tell DrScheme to apply @scheme[msg-expr] to the current state of the universe, the world that sent the message, and the message itself. The handler must produce a state of the @@ -1039,7 +1039,7 @@ optional handlers: @item{ @defform/none[(on-tick tick-expr) #:contracts - ([tick-expr (-> (unsyntax @tech{Universe}) bundle?)])]{ + ([tick-expr (-> (unsyntax @tech{UniverseState}) bundle?)])]{ tell DrScheme to apply @scheme[tick-expr] to the current state of the universe. The handler is expected to produce a bundle of the new state of the universe and a list of mails. @@ -1047,7 +1047,7 @@ optional handlers: @defform/none[(on-tick tick-expr rate-expr) #:contracts - ([tick-expr (-> (unsyntax @tech{Universe}) bundle?)] + ([tick-expr (-> (unsyntax @tech{UniverseState}) bundle?)] [rate-expr natural-number/c])]{ tell DrScheme to apply @scheme[tick-expr] as above but use the specified clock tick rate instead of the default. @@ -1057,7 +1057,7 @@ optional handlers: @item{ @defform[(on-disconnect dis-expr) #:contracts - ([dis-expr (-> (unsyntax @tech{Universe}) world? bundle?)])]{ + ([dis-expr (-> (unsyntax @tech{UniverseState}) world? bundle?)])]{ tell DrScheme to invoke @scheme[dis-expr] every time a participating @tech{world} drops its connection to the server. The first argument is the current state of the universe; the second one is the world that got @@ -1068,7 +1068,7 @@ optional handlers: @item{ @defform[(to-string render-expr) #:contracts - ([render-expr (-> (unsyntax @tech{Universe}) string?)])]{ + ([render-expr (-> (unsyntax @tech{UniverseState}) string?)])]{ tell DrScheme to render the state of the universe after each event and to display this string in the universe console. } @@ -1134,7 +1134,7 @@ In summary, the first step of a protocol design is to introduce: @itemize[ @item{a data definition for the information about the universe that the -server tracks, call it @tech{Universe};} +server tracks, call it @tech{UniverseState};} @item{a data definition for the world(s) about their current relationship to the universe;} @@ -1172,13 +1172,13 @@ translates into the design of two functions with the following headers, @(begin #reader scribble/comment-reader (schemeblock -;; @tech{Universe} World -> (make-bundle @tech{Universe} [Listof mail?]) -;; create new @tech{Universe} when world w is joining the universe, +;; @tech{UniverseState} world? -> (make-bundle @tech{UniverseState} [Listof mail?]) +;; create new @tech{UniverseState} when world w is joining the universe, ;; which is in state s; also send mails as needed (define (add-world s w) ...) -;; @tech{Universe} World MsgW2U -> (make-bundle @tech{Universe} [Listof mail?]) -;; create new @tech{Universe} when world w is sending message m +;; @tech{UniverseState} world? MsgW2U -> (make-bundle @tech{UniverseState} [Listof mail?]) +;; create new @tech{UniverseState} when world w is sending message m ;; to universe in state s; also send mails as needed (define (process s p m) ...) )) @@ -1205,7 +1205,7 @@ As for the server's state, it must obviously keep track of all @tech{world}s tha no @tech{world}s and, at that point, the server has nothing to track. While there are many different useful ways of representing such a @tech{universe}, - we choose to introduce @tech{Universe} as a list of @tech{world}s, and we + we choose to introduce @tech{UniverseState} as a list of @tech{world}s, and we interpret non-empty lists as those where the first @tech{world} is active and the remainder are the passive @tech{world}s. As for the two possible events, @itemize[ @@ -1249,7 +1249,7 @@ The preceding subsection dictates that our server program starts like this: [schemeblock ;; teachpack: universe.ss -;; Universe is [Listof world?] +;; UniverseState is [Listof world?] ;; StopMessage is 'done. ;; GoMessage is 'it-is-your-turn. ]) @@ -1264,13 +1264,14 @@ The preceding subsection dictates that our server program starts like this: @(begin #reader scribble/comment-reader [schemeblock -;; Result is (make-bundle Universe (list (make-mail world? GoMessage))) +;; Result is +;; (make-bundle UniverseState (list (make-mail world? GoMessage))) -;; Universe world? -> Result +;; UniverseState world? -> Result ;; add world w to the universe, when server is in state u (define (add-world u w) ...) -;; Universe world? StopMessage -> Result +;; UniverseState world? StopMessage -> Result ;; world w sent message m when server is in state u (define (switch u w m) ...) ]) @@ -1279,7 +1280,7 @@ Although we could have re-used the generic contracts from this documentation, we also know from our protocol that our server sends a message to exactly one world. For this reason, both functions return the same kind of result: a bundle that contains the new state of the server -(@tech{Universe}) and a list that contains a single mail. These contracts +(@tech{UniverseState}) and a list that contains a single mail. These contracts are just refinements of the generic ones. (A type-oriented programmer would say that the contracts here are subtypes of the generic ones.) @@ -1311,7 +1312,7 @@ protocol. The protocol tells us that @emph{add-world} just adds the given @emph{world} structure---recall that this a data representation of the -actual @tech{world} program---to the @tech{Universe} and then sends a +actual @tech{world} program---to the @tech{UniverseState} and then sends a message to the first world on this list to get things going: @(begin @@ -1338,11 +1339,12 @@ Similarly, the protocol says that when @emph{switch} is invoked because a [schemeblock (define (switch univ wrld m) (local ((define univ* (append (rest univ) (list (first univ))))) - (make-bundle univ* (list (make-mail (first univ*) 'it-is-your-turn))))) + (make-bundle univ* + (list (make-mail (first univ*) 'it-is-your-turn))))) ]) As before, appending the first world to the end of the list guarantees - that there is at least this one world on the next @tech{Universe} + that there is at least this one world on the next @tech{UniverseState} (state). It is therefore acceptable to create a mail for this world. Exercise: The function definition simply assumes that @emph{wrld} is @@ -1371,31 +1373,35 @@ The final step is to design the ball @tech{world}. Recall that each world (schemeblock ;; teachpack: universe.ss -;; World is one of +;; WorldState is one of: ;; -- Number %% representing the @emph{y} coordinate ;; -- @scheme['resting] (define WORLD0 'resting) + +;; A WorldResult is one of: +;; -- WorldState +;; -- (make-package WorldState StopMessage) )) The definition says that initially a @tech{world} is passive. -The communication protocol and the refined data definition of @tech{World} +The communication protocol and the refined data definition of @tech{WorldState} imply a number of contract and purpose statements: @(begin #reader scribble/comment-reader (schemeblock -;; World GoMessage -> World or (make-package World StopMessage) +;; WorldState GoMessage -> WorldResult ;; make sure the ball is moving (define (receive w n) ...) -;; World -> World or (make-package World StopMessage) +;; WorldState -> WorldResult ;; move this ball upwards for each clock tick ;; or stay @scheme['resting] (define (move w) ...) -;; World -> Scene +;; WorldState -> Scene ;; render the world as a scene (define (render w) ...) )) @@ -1403,7 +1409,7 @@ The communication protocol and the refined data definition of @tech{World} Let's design one function at a time, starting with @emph{receive}. Since the protocol doesn't spell out what @emph{receive} is to compute, let's create a good set of functional examples, exploiting the structure of the - data organization of @tech{World}: + data organization of @tech{WorldState}: @(begin #reader scribble/comment-reader @@ -1458,7 +1464,7 @@ the scene every time @scheme['it-is-your-turn] is received. Design this function @(begin #reader scribble/comment-reader (schemeblock -; World -> World or @scheme[(make-package 'resting 'done)] +; WorldState -> WorldState or @scheme[(make-package 'resting 'done)] ; move the ball if it is flying (check-expect (move 'resting) 'resting) @@ -1498,7 +1504,7 @@ Finally, here is the third function, which renders the state as a scene: @(begin #reader scribble/comment-reader (schemeblock -; World -> Scene +; WorldState -> Scene ; render the state of the world as a scene (check-expect (render HEIGHT) (place-image BALL 50 HEIGHT MT)) @@ -1520,7 +1526,7 @@ Finally, here is the third function, which renders the state as a scene: @(begin #reader scribble/comment-reader (schemeblock -; String -> (World -> Scene) +; String -> (WorldState -> Scene) ; render the state of the world as a scene (check-expect @@ -1545,7 +1551,7 @@ Finally, here is the third function, which renders the state as a scene: #reader scribble/comment-reader (schemeblock -; String -> World +; String -> WorldState ; create and hook up a world with the @scheme[LOCALHOST] server (define (create-world name) (big-bang WORLD0