Exercise 164: Argue why it is acceptable to use last on Polygons. Also argue why you may reuse the template for connect-dots for last:
If we go back to the definition of a Polygon we can see that it consists of either a list of three Posns, or
a Posn cons'd with another (valid) Polygon. It is acceptable to use last on a polygon as it will always consist of at least three valid Posn's and so a Posn will always be able to be safely returned.
This question confused me at first, at in the stub function, the first p in the Polygon is returned. This is to make the stub valid as it can't just return P as then it would be returning a Polygon, rather than a Posn. However, reading a bit further it is clear that this is just a stub and not the complete function!
We are told we can re-use the template we used for the connect-dots:
(define (last p) |
(cond |
[(empty? (rest p)) (... (first p) ...)] |
[else (... (first p) ... (last (rest p)) ...)])) |
The reason we can use this template is that the definition of Polygon guarantees that this will be a Non empty list of Posns.
Even though we're not asked to implement the function last I thought I would have a go before reading on further.
First create some simple tests:
; Check a list of 3 posns (a triangle) correctly returns the
; last point
(check-expect (last (list (make-posn 10 10)
(make-posn 60 60)
(make-posn 10 60)))
(make-posn 10 60))
; Check a list of 4 posns (a square) correctly returns the
; last point
(check-expect (last (list (make-posn 10 10)
(make-posn 60 10)
(make-posn 60 60)
(make-posn 10 60)))
(make-posn 10 60))
I chose just to use the length of the list rather than getting the rest of the list three times. Both of these
definitions pass the tests, but looking back on the text we find this quote:
"Since all Polygons consist of at least three Posns, using rest three times is legal. Unlike length, rest is also a primitive, easy-to-understand operation with a clear operational meaning. It selects the second field in a cons structure and that is all it does."
I'm not going to argue with the author, but from my perspective, using a primitive operator is not neccessarily better than a higher level operator - especially when the latter is shorter and easier to read.
; NELoP -> Posn; to extract the last Posn on p
(define (my_last p)
(cond
[(= (length p) 3) (third p)]
[else (last (rest p))]))
; NELoP -> Posn
; extract the last Posn from p
(define (last p)
(cond
[(empty? (rest (rest (rest p)))) (third p)]
[else (last (rest p))]))