I've read a lot of good things about Land of Lisp so I thought that I might go through it to see what there was to see.
(defun tweak-text (lst caps lit)
(when lst
(let ((item (car lst))
(rest (cdr lst)))
(cond
; If item = space, then call recursively starting with ret
; Then, prepend the space on to the result.
((eq item #\space) (cons item (tweak-text rest caps lit)))
; if the item is an exclamation point. Make sure that the
; next non-space is capitalized.
((member item '(#\! #\? #\.)) (cons item (tweak-text rest t lit)))
; if item = " then toggle whether we are in literal mode
((eq item #\") (tweak-text rest caps (not lit)))
; if literal mode, just add the item as is and continue
(lit (cons item (tweak-text rest nil lit)))
; if either caps or literal mode = true capitalize it?
((or caps lit) (cons (char-upcase item) (tweak-text rest nil lit)))
; otherwise lower-case it.
(t (cons (char-downcase item) (tweak-text rest nil nil)))))))
(the comments are mine)
(FYI -- the method signature is (list-of-symbols bool-whether-to-caps bool-whether-to-treat-literally)
but the author shortened these to (lst caps lit)
.)
But anyway, here's the question:
This has (cond... (lit ...) ((or caps lit) ...))
in it. My understanding is that this would translate to if(lit){ ... } else if(caps || lit){...}
in a C style syntax. Isn't the or statement redundant then? Is there ever a condition where the (or caps lit)
condition will be called if caps is nil
?
Indeed, you are correct. See the errata for the book.
Page 97: The function tweak-text has two glitches in it, though it will run OK on most Lisp implementations. First of all, it uses the eq function to compare characters- Characters should always be checked with other functions such as eql or char-equal as per the ANSI spec. Also, there's an unnecessary check of (or caps lit) that can be simplified to caps.
I would write that as:
(defun tweak-text (list caps lit)
(when list
(destructuring-bind (item . rest) list
(case item
((#\space) (cons item (tweak-text rest caps lit)))
((#\! #\? #\.) (cons item (tweak-text rest t lit)))
((#\") (tweak-text rest caps (not lit)))
(otherwise (cond (lit (cons item (tweak-text rest nil lit)))
(caps (cons (char-upcase item)
(tweak-text rest nil lit)))
(t (cons (char-downcase item)
(tweak-text rest nil nil)))))))))
The CASE statement dispatches on the character. The COND statement then takes care of the other conditions. CASE compares with EQL. That means CASE works for also for characters and even can compare with multiple items. I'm also a fan of a code layout style that lines up corresponding expressions - this is only useful with monospaced fonts. This helps me to detect patterns visually in the code and helps detecting code that can be simplified.
DESTRUCTURING-BIND takes the list apart.
For fun, rewritten using LOOP:
(defun tweak-text (list)
(loop with caps and lit
for item in list
when (eql item #\space)
collect item
else when (member item '(#\! #\? #\.))
collect item and do (setf caps t)
else when (eql item #\")
do (setf lit (not lit))
else when lit
collect item and do (setf caps nil)
else when caps
collect (char-upcase item) and do (setf caps nil)
else
collect (char-downcase item) and
do (setf caps nil lit nil)))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With