Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

CL Format recipe: Dealing with nil as a value

I've been looking through formatting recipes , and I can't quite find what I'm looking for...

(format nil CONTROL-STRING day name num-apples)

Suppose I don't want to change the arguments in the above form, just CONTROL-STRING.

day and num-apples will always be non-nil, but name might be nil.

When name is nil, I want the output to look like

"Today is Monday. Hello, you have 3 apples."

but when name is defined, I want it to look like

"Today is Monday. Hello Adam, you have 3 apples."

So the control string needs to look at name, use it in the non-nil case, not use it in the nil case, but consume it in both cases.

Maybe it can be accomplished by consuming nil and printing it as ""? If so I don't know how to do that.

like image 271
brian_o Avatar asked Aug 19 '15 17:08

brian_o


1 Answers

The question you linked to, Lisp format directive that interprets nil argument to empty string instead of "NIL", does include an answer that shows how you can do this, but doesn't cite any of the documentation. Since you're generating English text, there are also a few other options that you might want to consider as well.

First, with ~@[consequent~], you can process the consequent format directive just in the case that the argument is non-nil, and the argument to ~@[ isn't consumed, so it's still available. In general, 22.3.7.2 Tilde Left-Bracket: Conditional Expression describes lots of options, but about ~@[ it says:

~@[consequent~] tests the argument. If it is true, then the argument is not used up by the ~[ command but remains as the next one to be processed, and the one clause consequent is processed. If the arg is false, then the argument is used up, and the clause is not processed. The clause therefore should normally use exactly one argument, and may expect it to be non-nil.

You can use this as follows:

(defun test (day name n-apples)
  (format nil "Today is ~a. Hello~@[ ~a~], you have ~a apples."
          day name n-apples))

CL-USER> (test 'monday 'adam 2)
"Today is MONDAY. Hello ADAM, you have 2 apples."
CL-USER> (test 'tuesday nil 42)
"Today is TUESDAY. Hello, you have 42 apples."

To make this even more robust, you should consider using ~p for pluralization, so that you get "1 apple" and "3 apples".

(defun test (day name n-apples)
  (format nil "Today is ~a. Hello~@[ ~a~], you have ~a apple~:P."
          day name n-apples))

CL-USER> (test 'monday 'john 2)
"Today is MONDAY. Hello JOHN, you have 2 apples."
CL-USER> (test 'tuesday 'john 1)
"Today is TUESDAY. Hello JOHN, you have 1 apple."
CL-USER> (test 'wednesday nil 0)
"Today is WEDNESDAY. Hello, you have 0 apples."

Finally, since you're generating text, you might appreciate some case normalization (e.g., print proper nouns with initial capitals), and writing the numbers in text:

(defun test (day name n-apples)
  (format nil "Today is ~:(~a~). Hello~@[ ~:(~a~)~], you have ~r apple~:P."
          day name n-apples))
CL-USER> (list
          (test 'monday 'adam 4)
          (test 'tuesday 'john 1)
          (test 'wednesday 'mary\ sue 42)
          (test 'thursday 'jim-bob 0))
("Today is Monday. Hello Adam, you have four apples."
 "Today is Tuesday. Hello John, you have one apple."
 "Today is Wednesday. Hello Mary Sue, you have forty-two apples."
 "Today is Thursday. Hello Jim-Bob, you have zero apples.")
like image 175
Joshua Taylor Avatar answered Nov 16 '22 11:11

Joshua Taylor