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.
Figure
collects the function, structure, data, and
variable definitions that model the ball:
To move the ball a few times we can write
(define the-ball (make-ball 10 20 -5 +17))though this gets tedious after a while. We should instead develop a function that moves the ball until it is out of bounds.(and (draw-and-clear the-ball) (and (draw-and-clear (move-ball the-ball)) ...))
The easy part is to define out-of-bounds?, a function that determines whether a given ball is still visible on the canvas:
;; out-of-bounds? : a-ball -> boolean
;; to determine whether a-ball is outside of the bounds
;; domain knowledge, geometry
(define (out-of-bounds? a-ball)
(not
(and
(<= 0 (ball-x a-ball) WIDTH)
(<= 0 (ball-y a-ball) HEIGHT))))
We have defined numerous functions like out-of-bounds? 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 thus far. Let's start with the basics of the function:
;; move-until-out : a-ball -> true ;; to model the movement of a ball until it goes out of bounds (define (move-until-out a-ball) ...)Because the function consumes a ball and draws its movement on a canvas, it produces true like all other functions that draw onto a canvas. Designing it with the recipe for structures makes no sense, however. After all, it is already clear how to draw-and-clear 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 cond-expression:
(define (move-until-out a-ball)
(cond
[(out-of-bounds? a-ball) ...]
[else ...]))
We have already defined the function out-of-bounds? because it was
clear from the problem description that ``being out of bounds'' was a
separate concept.
If the ball consumed by move-until-out is outside of the canvas's boundaries, the function can produce true, 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 move-until-out again, which means the function is recursive:
;; move-until-out : a-ball -> true
;; to model the movement of a ball until it goes out of bounds
(define (move-until-out a-ball)
(cond
[(out-of-bounds? a-ball) true]
[else (and (draw-and-clear a-ball)
(move-until-out (move-ball a-ball)))]))
Both (draw-and-clear a-ball) and (move-until-out (move-ball a-ball)) produce true, and both expressions must be
evaluated. So we combine them with an and-expression.
We can now test the function as follows:
(start WIDTH HEIGHT) (move-until-out (make-ball 10 20 -5 +17)) (stop)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 cond-expression 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, move-until-out generates an entirely new and different ball 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.
What happens if we place the following three expressions
(start WIDTH HEIGHT) (move-until-out (make-ball 10 20 0 0)) (stop)at the bottom of the Definitions window and click Execute? 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
Exercise 25.1.2
Develop move-balls. The function consumes a list of balls and moves each one until all of them have moved out of bounds.
Hint: It is best to write this function using filter,
andmap, and similar abstract functions from
part
. Solution