Here are two possible constructors for frames:
(define (make-frame origin edge1 edge2) (list origin edge1 edge2)) (define (make-frame origin edge1 edge2) (cons origin (cons edge1 edge2)))
For each constructor supply the appropriate selectors to produce an implementation for frames.
First, let's look at the structures the two constructors produce.
The first (list-based) version of
make-frame
constructs a list of origin
, edge1
and edge2
in that order. As shown in section 2.2.1, a list is really represented as a chain of pairs with each pair consisting of the next element in the list and the next pair in the chain (with the last pair having nil
as its second element. This means that the list constructed is a chain of three pairs where the first components of each pair are origin
, edge1
and edge2
respectively. I.e.
make-frame
also constructs a chain of pairs, but in this case it is done directly and only consists of two pairs. The first pair has origin
as its first component and the second pair as its second component. The second pair has edge1
as its first component and edge2
as its second component. I.e.
edge2
is included in them. This means that selectors origin-frame
and edge1-frame
will be identical regardless of the constructor implementation used. The former simply returns the first element of the root pair via car
, while the latter returns the first element of the second pair in the structure via cadr
:
(define (origin-frame f) (car f)) (define (edge1-frame f) (cadr f))The only difference is in the implementations of
edge2-frame
. In the list-based representation we need this selector to return the first element of the third pair. I.e. via caddr
:
(define (edge2-frame f) (caddr f))However, in the pair-based representation we need this selector to return the second element of the second pair. I.e. via
cddr
:
(define (edge2-frame f) (cddr f))The choice as to which we we use is fairly arbitrary. It could be argued that the pair-based representation is more compact, eliminating the third pair. However the list-based representation feels more elegant.
Anyway, here they are in use. First the list-based representation:
> (define (make-frame origin edge1 edge2) (list origin edge1 edge2)) > (define (origin-frame f) (car f)) > (define (edge1-frame f) (cadr f)) > (define (edge2-frame f) (caddr f)) > (define origin (make-vect 10 5)) > (define edge1 (make-vect 0 100)) > (define edge2 (make-vect 40 -4)) > (define frame (make-frame origin edge1 edge2)) > (origin-frame frame) '(10 . 5) > (edge1-frame frame) '(0 . 100) > (edge2-frame frame) '(40 . -4)...and the pairs-based representation:
> (define (make-frame origin edge1 edge2) (cons origin (cons edge1 edge2))) > (define (origin-frame f) (car f)) > (define (edge1-frame f) (cadr f)) > (define (edge2-frame f) (cddr f)) > (define origin (make-vect 10 5)) > (define edge1 (make-vect 0 100)) > (define edge2 (make-vect 40 -4)) > (define frame (make-frame origin edge1 edge2)) > (origin-frame frame) '(10 . 5) > (edge1-frame frame) '(0 . 100) > (edge2-frame frame) '(40 . -4)As you'd expect, although the internal representations differ, the constructor and selectors hide this from the next layer of abstraction up and so they behave identically.
Hey JoT.
ReplyDeleteI can't seem to get my head around the implementation of frames in SICP.
The book states
> We will use coordinates in the unit square (0< x,y< 1) to specify images
How are images expressed as coordinates? The only interpretation I can muster is that all images, being lines, can only be mapped to a frame whose boundary can not exceed that of a unit square. But I doubt that because the next line in the book, explaining a "frame coordinate map", says
> The map transforms the unit square into the frame by mapping the vector v = (x,y) to the vector sum Origin(Frame) + x*Edge1(Frame) + y*Edge2(Frame)
The vector (0,0) being mapped to the origin of the frame and (1,1) to the vertex diagonally opposite the origin, only adds to my confusion. What are these vectors? The origin of the image or what?
I can't make sense of this and it's preventing me from moving further into the text as everything discussed after builds on this concept. I'd find it very helpful if I could get a detailed explanation of how you understood this idea.
Hi Michael,
ReplyDeleteIt's the image, not the frame, that's defined within a unit square. Within this unit square you can define an image by whatever method is appropriate to the image being represented. For example, each of the images required for the exercises in the book can be represented as a collection of lines. E.g. the wave image is a collection of 17 lines:
- 5 connected lines to give the top of the left arm and left side of the head (that's left as you're looking at the image!)
- 4 connected lines for the underneath of the left arm and left side
- 4 connected lines for the right side of the head and top of the right arm
- 2 connected lines for the underneath of the right arm and right side
- 2 connected lines for the inside legs
A frame can be thought of as a parallelogram within a Cartesian coordinate system. As such it can be defined by three vectors:
- The "frame origin vector" defines where one corner of the frame is with respect to the origin of the coordinate system.
- The "frame edge1 vector" defines where a second corner of the frame is, relative to the point indicated by the "frame origin vector". - The "frame edge2 vector" defines a third corner of the frame is, again relative to the corner indicated by the "frame origin vector". As the frame maps to a parallelogram the fourth corner is implicitly defined by these three vectors. However, it can be calculated by adding the three vectors together.
The idea in the SICP picture language is that you can draw images into these frames. When you do so:
- The bottom left-hand corner of the image will be drawn at the point indicated by the "frame origin vector".
- The bottom right-hand corner of the image will appear at the point defined by the "frame edge1 vector" when taken relative to the point indicated by the "frame origin vector".
- The top left-hand corner of the image will appear at the point defined by the "frame edge2 vector" when taken relative to the point indicated by the "frame origin vector".
- The top right-hand corner of the image will appear at the implicit fourth corner of the frame.
Note that the three vectors which describe the frame don't necessarily describe a parallelogram that aligns nicely with the Cartesian axes and their traditional directions of increment. As a result they can stretch, skew, rotate and/or reflect the image! E.g. if "frame edge1 vector" went from (0,0) to (1,0) (aligning nicely with the x-axis), but "frame edge2 vector" went from (0,0) to (0,-1) (inverting the normal y-axis direction) then an image painted into this frame would remain as a unit square, but would be drawn upside-down.
Hope that helps!