In the htdp book, how much leeway do we have with adjusting the templates?

Hi, everyone. First time posting here. A novice in programming.
So I have this question which I've posted on stackoverflow - you can read the long version of it here:
https://stackoverflow.com/q/77874527/22037671

One way to sum the question up perhaps is, while we're defining a function, how much can we modify the template based on the purpose of the function?

For example, selector expressions can be omitted if they're irrelevant to a particular function, or maybe even duplicated, if need be, if I've understood correctly. But what about the predicate for the base case? Can we modify the input to the base case's predicate so that it matches our purpose, but ignores the data definition?

Perhaps I'm missing something in the exercise, hence this confusion...

If you are a student in a course/an instructor of one, don’t modify the predicates in the template. Dropping selector expressins is perfectly fine. Sw is a message to future developers. A developer typically writes many functions for the same data def. (or as you will call them later, class hierarchies). It tells a future reader (older version of yourself) how to approach these functions, when (not if) the data def. changes to include more variants, to use different version of the variants, and so on. So if the base case changes, a developer immediately knows that it is the cond line using the base predicate that must be modified. If this line doesn’t exist, you’re stealing time — the most valuable good around — from this future friend. — Matthias

Thank you very much for your prompt reply!
Just to confirm that I've understood correctly: it's not that the template will/may ignore the data definition, but rather that we may make use of alternative data definitions/ variants from which we can then derive different templates based on how each corresponding variant is differently laid out.
Then, the predicate is an important part of the template that must match the data definition chosen. And that variant must be suitable and well-formed in the first place, or worked on, to help solve the problem, in any case.

Excellent. If data def. 1 does not yield the nice function def. you want, change the data def. to something else. BUT, keep in mind that a data definition is just the programmer’s choice to represent the information that exists in the problem domain. So if both data def. 1 and data def. 2 do a good job representing the information you care about, great. But if one of them doesn’t, don’t use it.

Example. Temperature is information in the real world. We can measure it several different ways: Celsius, Fahrenheit, Kelvin, and a few more. Furthermore, we may just want a data representation for everyday use or we may want one to express something for the climatechange press. So we may want something like

A Temperature is an Integer number greater than -273.
Interpretation A temperature t denotes t degrees Celsius, e.g., 2 means 2C.
Alternative interpretation: A temperature t denotes t/10 degrees Celsius, e.g., 2 means .2C.

or something like this:

A Temperature is a Real number greater than -273.15.
Interpretation A temperature t denotes t degrees Celsius, e.g., 2.1 means 2.1C.

This leaves us with three data representations of Celsius temperatures written down as three different data definitions. The second (“Alternative”) and third are somewhat equivalent but you could use different predicates to discover whether the temperature is at the freezing point: zero? for both or =~ for the third (one should never compare “inexact” numbers aka floating point numbers with plain = so therefore https://docs.racket-lang.org/htdp-langs/beginner.html#%28def.htdp-beginner.%28%28lib.lang%2Fhtdp-beginner..rkt%29.~3d~7e%29%29)

I see... I'm much more clear now about how these ideas mesh together.
How we represent information depends on the kind of application we wish to build, and there may be various ways to define our data (which will guide the templates). Some ways work, some don't and we could always compare how the various working choices impact our design and the output. This experimentation has actually shown up before in the book, I think, but now it makes even more sense. Alright.

Thank you so much for taking the time to explain this and for further elucidating with the examples!

I'm curious about why the code in the Stack Overflow post would not work. For Matrix, I thought about this template:

#;
(define (process-matrix m)
  (cond
    [(empty? (rest m))
     (... (first m) ...)]
    [else
     (... (first m) ... (process-matrix (rest m)) ...)])

This ended up giving a very similar implementation of first*. It passes the tests.

; Matrix -> List-of-numbers
; produces the first column as a list of numbers
(check-expect (first* tam1)
              (cons 11 (cons 12 '())))
(check-expect (first*
               (cons (cons 1 (cons 4 '()))
                     (cons (cons 2 (cons 5 '()))
                           (cons (cons 3 (cons 6 '()))
                                 '()))))
              (cons 1 (cons 2 (cons 3 '()))))
(define (first* m)
  (cond
    [(empty? (rest m))
     (cons (first (first m)) '())]
    [else
     (cons (first (first m))
           (first* (rest m)))]))

Is there something different?

No, no, perhaps I didn't emphasize it well enough in the post - the code worked. It's just that I couldn't wrap my head around solving the exercise with the template as it was.

It's in the result of this predicate that I was most likely confused. So what essentially happened is I instead considered the base case of a matrix being an empty one, which was a data definition different from the one suggested by the exercise.
But indeed, both approaches work. Seeing your answer according to the suggested definition now somehow seems obvious :sweat_smile: , but at the time I couldn't reason about it.

Thank you!