Let's consider the simple-looking problem of modeling the moves of a ball
across a table. Assume the ball rolls at a constant speed until drops. We
can model the table with a canvas of some fixed width and height. The ball
is a disk that moves across the canvas, which we express with drawing the
disk, waiting, and clearing it, until it is out of bounds.
<#32788#>;; TeachPack: draw.ss <#32788#>
<#32789#>(define-struct<#32789#> <#32790#>ball<#32790#> <#32791#>(x<#32791#> <#32792#>y<#32792#> <#32793#>delta-x<#32793#> <#32794#>delta-y))<#32794#>
<#71450#>;; A <#66135#><#32795#>ball<#32795#><#66135#> is a structure: <#71450#>
<#32796#>;; (make-ball number number number number)<#32796#>
<#71451#>;; <#66136#><#32797#>draw-and-clear<#32797#> <#32798#>:<#32798#> <#32799#>a-ball<#32799#> <#32800#><#32800#><#32801#>-;SPMgt;<#32801#><#32802#><#32802#> <#32803#>true<#32803#><#66136#><#71451#>
<#32804#>;; draw, sleep, clear a disk from the canvas <#32804#>
<#32805#>;; structural design, Scheme knowledge<#32805#>
<#32806#>(d<#32806#><#32807#>efine<#32807#> <#32808#>(draw-and-clear<#32808#> <#32809#>a-ball)<#32809#>
<#32810#>(<#32810#><#32811#>and<#32811#>
<#32812#>(draw-solid-disk<#32812#> <#32813#>(make-posn<#32813#> <#32814#>(ball-x<#32814#> <#32815#>a-ball)<#32815#> <#32816#>(ball-y<#32816#> <#32817#>a-ball))<#32817#> <#32818#>5<#32818#> <#32819#>RED)<#32819#>
<#32820#>(sleep-for-a-while<#32820#> <#32821#>DELAY)<#32821#>
<#32822#>(clear-solid-disk<#32822#> <#32823#>(make-posn<#32823#> <#32824#>(ball-x<#32824#> <#32825#>a-ball)<#32825#> <#32826#>(ball-y<#32826#> <#32827#>a-ball))<#32827#> <#32828#>5<#32828#> <#32829#>RED)))<#32829#>
<#71452#>;; <#66137#><#32830#>move-ball<#32830#> <#32831#>:<#32831#> <#32832#>ball<#32832#> <#32833#><#32833#><#32834#>-;SPMgt;<#32834#><#32835#><#32835#> <#32836#>ball<#32836#><#66137#><#71452#>
<#71453#>;; to create a new ball, modeling a move by <#66138#><#32837#>a-ball<#32837#><#66138#><#71453#>
<#32838#>;; structural design, physics knowledge<#32838#>
<#32839#>(d<#32839#><#32840#>efine<#32840#> <#32841#>(move-ball<#32841#> <#32842#>a-ball)<#32842#>
<#32843#>(make-ball<#32843#> <#32844#>(+<#32844#> <#32845#>(ball-x<#32845#> <#32846#>a-ball)<#32846#> <#32847#>(ball-delta-x<#32847#> <#32848#>a-ball))<#32848#>
<#32849#>(+<#32849#> <#32850#>(ball-y<#32850#> <#32851#>a-ball)<#32851#> <#32852#>(ball-delta-y<#32852#> <#32853#>a-ball))<#32853#>
<#32854#>(ball-delta-x<#32854#> <#32855#>a-ball)<#32855#>
<#32856#>(ball-delta-y<#32856#> <#32857#>a-ball)))<#32857#>
<#32858#>;; Dimension of canvas <#32858#>
<#32859#>(define<#32859#> <#32860#>WIDTH<#32860#> <#32861#>100)<#32861#>
<#32862#>(define<#32862#> <#32863#>HEIGHT<#32863#> <#32864#>100)<#32864#>
<#32865#>(define<#32865#> <#32866#>DELAY<#32866#> <#32867#>.1)<#32867#>
<#66139#>Figure: Auxiliaries for <#32871#>move-until-out<#32871#><#66139#>
Figure~#figmoveout#32873> collects the function, structure, data, and
variable definitions that model the ball:
- A ball is a structure with four fields, which are the current
position and the velocity in each direction. That is, the first two numbers
in a <#66140#><#32875#>ball<#32875#><#66140#> structure are the current position on the canvas, and
the next two numbers describe how far the ball moves in the two directiones
per step.
- The function <#66141#><#32876#>move-ball<#32876#><#66141#> models the physical movement of the
ball. It consumes a ball and creates a new one, modeling one step.
- The function <#66142#><#32877#>draw-and-clear<#32877#><#66142#> draws the ball at its current
position, then waits for a short time, and clears it again.
The variable definitions specify the dimensions of the canvas and the delay
time.
To move the ball a few times we can write
<#32883#>(define<#32883#> <#32884#>the-ball<#32884#> <#32885#>(make-ball<#32885#> <#32886#>10<#32886#> <#32887#>20<#32887#> <#32888#>-5<#32888#> <#32889#>+17))<#32889#>
<#32890#>(<#32890#><#32891#>and<#32891#>
<#32892#>(draw-and-clear<#32892#> <#32893#>the-ball)<#32893#>
<#32894#>(a<#32894#><#32895#>nd<#32895#>
<#32896#>(draw-and-clear<#32896#> <#32897#>(move-ball<#32897#> <#32898#>the-ball))<#32898#>
<#32899#>...))<#32899#>
though this gets tedious after a while. We should instead develop a
function that moves the ball until it is out of bounds.
The easy part is to define <#66143#><#32903#>out-of-bounds?<#32903#><#66143#>, a function that
determines whether a given ball is still visible on the canvas:
<#71454#>;; <#66144#><#32908#>out-of-bounds?<#32908#> <#32909#>:<#32909#> <#32910#>a-ball<#32910#> <#32911#><#32911#><#32912#>-;SPMgt;<#32912#><#32913#><#32913#> <#32914#>boolean<#32914#><#66144#><#71454#>
<#71455#>;; to determine whether <#66145#><#32915#>a-ball<#32915#><#66145#> is outside of the bounds<#71455#>
<#32916#>;; domain knowledge, geometry<#32916#>
<#32917#>(d<#32917#><#32918#>efine<#32918#> <#32919#>(out-of-bounds?<#32919#> <#32920#>a-ball)<#32920#>
<#32921#>(<#32921#><#32922#>not<#32922#>
<#32923#>(a<#32923#><#32924#>nd<#32924#>
<#32925#>(;SPMlt;=<#32925#> <#32926#>0<#32926#> <#32927#>(ball-x<#32927#> <#32928#>a-ball)<#32928#> <#32929#>WIDTH)<#32929#>
<#32930#>(;SPMlt;=<#32930#> <#32931#>0<#32931#> <#32932#>(ball-y<#32932#> <#32933#>a-ball)<#32933#> <#32934#>HEIGHT))))<#32934#>
We have defined numerous functions like <#66146#><#32938#>out-of-bounds?<#32938#><#66146#> in the
first few sections of the book.
In contrast, writing a function that draws the ball on the canvas until it
is out of bounds belongs to a group of programs that we haven't encountered
thusfar. Let's start with the basics of the function:
<#71456#>;; <#66147#><#32943#>move-until-out<#32943#> <#32944#>:<#32944#> <#32945#>a-ball<#32945#> <#32946#><#32946#><#32947#>-;SPMgt;<#32947#><#32948#><#32948#> <#32949#>true<#32949#><#66147#><#71456#>
<#32950#>;; to model the movement of a ball until it goes out of bounds<#32950#>
<#32951#>(define<#32951#> <#32952#>(move-until-out<#32952#> <#32953#>a-ball)<#32953#> <#32954#>...)<#32954#>
Because the function consumes a ball and draws its movement on a canvas, it
produces <#66148#><#32958#>true<#32958#><#66148#> like all other functions that draw into a canvas.
Designing it with the recipe for structures makes no sense, however. After
all, it is already clear how to <#66149#><#32959#>draw-and-clear<#32959#><#66149#> the ball and how to
move it, too. What is needed instead, is a case distinction that checks
whether the ball is out of bounds or not.
Let us refine the function header with an appropriate <#66150#><#32960#>cond<#32960#>-expression<#66150#>:
<#32965#>(d<#32965#><#32966#>efine<#32966#> <#32967#>(move-until-out<#32967#> <#32968#>a-ball)<#32968#>
<#32969#>(c<#32969#><#32970#>ond<#32970#>
<#32971#>[<#32971#><#32972#>(out-of-bounds?<#32972#> <#32973#>a-ball)<#32973#> <#32974#>...]<#32974#>
<#32975#>[<#32975#><#32976#>else<#32976#> <#32977#>...]<#32977#><#32978#>))<#32978#>
We have already defined the function <#66151#><#32982#>out-of-bounds?<#32982#><#66151#> because it was
clear from the problem description that ``being out of bounds'' was a
separate concept.
If the ball consumed by <#66152#><#32983#>move-until-out<#32983#><#66152#> is outside of the canvas's
boundaries, the function can produce <#66153#><#32984#>true<#32984#><#66153#>, following the
contract. If the ball is still inside the boundaries, two things must
happen. First, the ball must be drawn and cleared from the canvas. Second,
the ball must be moved and then we must do things all over again. This
implies that after moving the ball, we apply <#66154#><#32985#>move-until-out<#32985#><#66154#> again,
which means the function is recursive:
<#71457#>;; <#66155#><#32990#>move-until-out<#32990#> <#32991#>:<#32991#> <#32992#>a-ball<#32992#> <#32993#><#32993#><#32994#>-;SPMgt;<#32994#><#32995#><#32995#> <#32996#>true<#32996#><#66155#><#71457#>
<#32997#>;; to model the movement of a ball until it goes out of bounds<#32997#>
<#32998#>(d<#32998#><#32999#>efine<#32999#> <#33000#>(move-until-out<#33000#> <#33001#>a-ball)<#33001#>
<#33002#>(c<#33002#><#33003#>ond<#33003#>
<#33004#>[<#33004#><#33005#>(out-of-bounds?<#33005#> <#33006#>a-ball)<#33006#> <#33007#>true]<#33007#>
<#33008#>[<#33008#><#33009#>else<#33009#> <#33010#>(and<#33010#> <#33011#>(draw-and-clear<#33011#> <#33012#>a-ball)<#33012#>
<#33013#>(move-until-out<#33013#> <#33014#>(move-ball<#33014#> <#33015#>a-ball)))]<#33015#><#33016#>))<#33016#>
Both <#66156#><#33020#>(draw-and-clear<#33020#>\ <#33021#>a-ball)<#33021#><#66156#> and <#66157#><#33022#>(move-until-out<#33022#><#33023#> <#33023#><#33024#>(move-ball<#33024#>\ <#33025#>a-ball))<#33025#><#66157#> produce true, and both expressions must be
evaluated. So we combine them with an <#66158#><#33026#>and<#33026#>-expression<#66158#>.
We can now test the function as follows:
<#33031#>(start<#33031#> <#33032#>WIDTH<#33032#> <#33033#>HEIGHT)<#33033#>
<#33034#>(move-until-out<#33034#> <#33035#>(make-ball<#33035#> <#33036#>10<#33036#> <#33037#>20<#33037#> <#33038#>-5<#33038#> <#33039#>+17))<#33039#>
<#33040#>(stop)<#33040#>
This creates a canvas of proper size and a ball that moves left and down.
A close look at the function definition reveals two peculiarities. First,
although the function is recursive, its body consists of a
<#66159#><#33044#>cond<#33044#>-expression<#66159#> whose conditions have nothing to do with the input
data. Second, the recursive application in the body does not consume a part
of the input. Instead, <#66160#><#33045#>move-until-out<#33045#><#66160#> generates an entirely new
and different <#66161#><#33046#>ball<#33046#><#66161#> structure, which represents the original ball
after one step, and uses it for the recursion. Clearly, none of our design
recipes could possibly produce such a definition. We have encountered a new
way of programming.
<#33049#>Exercise 25.1.1<#33049#>
What happens if we place the following three expressions
<#33055#>(start<#33055#> <#33056#>WIDTH<#33056#> <#33057#>HEIGHT)<#33057#>
<#33058#>(move-until-out<#33058#> <#33059#>(make-ball<#33059#> <#33060#>10<#33060#> <#33061#>20<#33061#> <#33062#>0<#33062#> <#33063#>0))<#33063#>
<#33064#>(stop)<#33064#>
at the bottom of the <#33068#>Definitions<#33068#> window and click
<#33069#>Execute<#33069#>? Does the second expression ever produce a
value so that the third expression is evaluated and the canvas disappears?
Could this happen with any of the functions designed according to our old
recipes?~ Solution<#66162#><#66162#>
<#33075#>Exercise 25.1.2<#33075#>
Develop <#66163#><#33077#>move-balls<#33077#><#66163#>. The function consumes a list of balls and
moves each one until all of them have moved out of bounds.
<#33078#>Hint:<#33078#> It is best to write this function using <#66164#><#33079#>filter<#33079#><#66164#>,
<#66165#><#33080#>andmap<#33080#><#66165#>, and similar abstract functions from
part~#partabstract#33081>. Solution<#66166#><#66166#>