About me

About me

Feeds

RSS feed

Predicates that don't return 't'

19th June 2024

If you look up the definition of a predicate (in computing) you'll get something like:

"A logical expression that evaluates to TRUE or FALSE"

However, unlike in many other languages, Common Lisp’s policy is that where possible predicates should return a more useful non-nil value rather than just t.

I've been thinking about the predicates that fall into this category, and when you might want to make use of the value that's returned.

'and' and 'or'

The most obvious examples are and and or. If every argument to and is non-nil it returns the last argument:

> (and (< 3 4) (plusp 1) 7)
7

By comparison, or returns the first argument that is not nil:

> (or (> 3 4) (plusp 0) 7)
7

So and and or can often be used in place of if, when, and unless, although it's not very intuitive to do this.

'member'

Perhaps the most useful example of this type of predicate is member. This doesn't just return t if there's a match, but returns the rest of the list starting from the matching item:

> (member 13 '(2 3 5 7 11 13 17 19))
(13 17 19)

A case where this is useful is if the item you're finding isn't the same as the key. For example, this allows you to find the first item larger than the key in a sorted list:

> (first (member 15 '(2 3 5 7 11 13 17 19) :test #'<))
17

It's also useful if you want to destructively modify the matching element in the list, using setf:

> (let ((a '(2 3 5 7 11 13 17 19))) (setf (car (member 13 a)) -1) a)
(2 3 5 7 11 -1 17 19)

String comparison functions

Another example is the string comparison functions, such as string<. They don't return t if the comparison succeeds, but instead return the integer index of the first mismatch. For example:

> (string< "lisp" "list")
3

The only exception is string=, since there is no mismatch index to return if the answer is t.

> (string= "lisp" "lisp")
t

One application is when writing your own specialised string search function. For example, the following function string=* behaves like string=, except that a * at the end of one string matches zero or more characters in the other string:

(defun string=* (string1 string2)
  (let ((nomatch (string/= string1 string2)))
    (cond
     ((not nomatch) t)
     ((and (> (length string1) nomatch) (eq (char string1 nomatch) #\*)) t)
     ((and (> (length string2) nomatch) (eq (char string2 nomatch) #\*)) t)
     (t nil))))

For example:

> (string=* "fred" "fred")
t

> (string=* "fred" "fredabc")
nil

> (string=* "fred*" "fredabc")
t 

'digit-char-p'

The predicate digit-char-p is useful for processing numbers in strings; it returns nil if its character argument isn't a digit character, and the value of the character otherwise:

> (digit-char-p #\9)
9

> (digit-char-p #\A)
nil

For example, here's a function that takes advantage of this feature. It reads an integer from a string up to the first non-digit character:

(defun parseint (str)
  (do ((l (length str))
       (i 0 (1+ i))
       (d 0 (and (< i l) (digit-char-p (char str i))))
       (result 0 (+ d (* result 10))))
      ((null d) result)))

For example:

> (parseint "12345/")
12345

Pitfalls

Because some predicates in Lisp return values other than nil and t it's good practice never to test for a true result with (eq t); use (not nil) instead.


blog comments powered by Disqus