Approximate overview
Since I had the “Cheeps” example …
Before there was Facebook, we had a Grinnell-only social media site
called “Plans”. (It still lives on at https://grinnellplans.com.)
Before there was a Web, Plans even existed on our mainframe. You
could refer to other people with [username] (called “planlove”)
and you could find out who had referred to you.
A Grinnellian worked with the people who invented Twitter during
the time that they were developing Twitter. He suggested that they
include something like planlove. So they added @username.
string->clock(define string->clock
(lambda (str)
(apply clock (map string->number (string-split str ":")))))
or
(define string->clock
(lambda (str)
(when (not string? str)
(error "string->clock: expects a string, recieved" str))
(let ([parts (string-split str ":")])
(when (not (= 3 (length parts)))
(error "string->clock: string must have two colons" str))
(let ([hours (string->number (car parts))]
[minutes (string->number (cadr parts))]
[seconds (string->number (caddr parts))])
(cond
[(not hours)
(error "string->clock: hours must be a number")]
[(not (exact-integer? hours))
(error "string->clock: hours must be an exact integer")]
[(not (<= 0 hours 23))
(error "string->clock: hours must be between 0 and 23")]
[(not minutes)
(error "string->clock: minutes must be a number")]
[(not (exact-integer? minutes))
(error "string->clock: minutes must be an exact integer")]
[(not (<= 0 minutes 59))
(error "string->clock: minutes must be between 0 and 59")]
[(not seconds)
(error "string->clock: seconds must be a number")]
[(not (exact-integer? seconds))
(error "string->clock: seconds must be an exact integer")]
[(not (<= 0 seconds 59))
(error "string->clock: seconds must be between 0 and 59")]
[else
(clock hours minutes seconds)])))))
or
(define string->clock
(let ([clock-pattern
(rex-concat (rex-repeat (rex-char-range #\0 #\9))
(rex-string ":")
(rex-repeat (rex-char-range #\0 #\9))
(rex-string ":")
(rex-repeat (rex-char-range #\0 #\9)))])
(lambda (str)
(when (or (not-string? str)
(not (rex-matches? clock-pattern str)))
(error "invalid parameter"))
(apply clock (map string->number (string-split str ":"))))))
Morals?
There’s a tradeoff between elegance and robustness.
We should automate precondition testing (-:
TPS
Suppose we want to write a procedure, (vector-increment! vec),
that steps through vec, adding 1 to every number and adding an
“x” to the front of every string.
> (define sample (vector 1 2 "three" "four"))
> sample
'#(1 2 "three" "four")
> (vector-increment! sample)
> sample
'#(1 2 "three" "four")
'#(2 3 "xthree" "xfour")
What’s wrong with each of these solutions?
; V1
(define vector-increment!
(lambda (vec)
(vector-increment!/kernel vec 0)))
(define vector-increment!/kernel
(lambda (vec pos)
(when (< pos (vector-length vec))
(let ([val (vector-ref vec pos)])
(cond
[(number? val)
(vector-increment/kernel! (vector-set! vec pos (+ 1 val))
(+ pos 1))]
[(string? val)
(vector-increment/kernel! (vector-set! vec pos (cons "x" val))
(+ pos 1))]
[else
(vector-increment/kernel! (vector-set! vec pos val)
(+ pos 1))])))))
; V2
(define vector-increment!
(lambda (vec)
(vector-increment!/kernel vec 0)))
(define vector-increment!/kernel
(lambda (vec pos)
(let ([val (vector-ref vec pos)])
(when (< pos (vector-length vec))
(cond
[(number? val)
(vector-set! vec pos (+ 1 val))]
[(string? val)
(vector-set! vec pos (cons "x" val))])
(vector-increment!/kernel vec (+ pos 1))))))
; V3
(define vector-increment!
(lambda (vec)
(vector-increment!/kernel vec 0)))
(define vector-increment!/kernel
(lambda (vec pos)
(when (< pos (vector-length vec))
(let* ([current-val (vector-ref vec pos)]
[number-case (vector-set! vec pos (+ 1 current-val))]
[string-case (vector-set! vec pos (string-append "x" (current-val)))])
(cond
[(number? current-val)
number-case]
[(string? current-val)
string-case])
(vector-increment!/kernel vec (+ pos 1))))))
Having to write all of those (define foo-x foo-kernel-x) is a PITA.
Is there a better way?
Yeah, I think so.
I want to make my structures mutable. Is that possible?
As we’ve seen, there are some disadvantages to mutable types. But yes, you can make structures mutable.
Option 1: Add
#:mutableto the struct definition. That provides procedures of the formset-struct-field!, e.g.,set-clock-hours!.
Option 2: Write fields as
[field #:mutable].