The :KEY
parameter is included in some functions that ship with Common Lisp. All of the descriptions that I have found of them are unhelpful, and :KEY
Imagine that we have a list of cities:
(defparameter *cities*
; City Population Area km^2
'((Paris 2265886 105.4)
(Mislata 43756 2.06)
(Macau 643100 30.3)
(Kallithea 100050 4.75)
(Nea-Smyrni 73090 3.52)
(Howrah 1072161 51.74)))
Now we can compute the population density in people/km^2
(defun city-density (city)
"the density is the population number divided by the area"
(/ (second city) (third city)))
Now we want to compute a list of all cities which have a density less than 21000 people/km^2.
We remove all larger ones from the list and are providing a :test-not
function. We need to provide an anonymous function which does the test and computes the density of the city to compare.
CL-USER 85 > (remove 21000 *cities*
:test-not (lambda (a b)
(>= a (city-density b))))
((NEA-SMYRNI 73090 3.52) (HOWRAH 1072161 51.74))
We can write it simpler without the anonymous function by providing the numeric :test-not
function >=
and use the city-density
function as the key to compute the value from each provided cities:
CL-USER 86 > (remove 21000 *cities* :test-not #'>= :key #'city-density)
((NEA-SMYRNI 73090 3.52) (HOWRAH 1072161 51.74))
So having both a test predicate and a key function makes it easier to provide the building blocks for sequence computations...
Now imagine that we use CLOS and a list of city CLOS objects:
(defclass city ()
((name :initarg :name :reader city-name)
(population :initarg :population :reader city-population)
(area :initarg :area :reader city-area)))
(defparameter *city-objects*
(loop for (name population area) in *cities*
collect (make-instance 'city
:name name
:population population
:area area)))
(defmethod density ((c city))
(with-slots (population area)
c
(/ population area)))
Now we compute the list as above:
CL-USER 100 > (remove 21000 *city-objects* :test-not #'>= :key #'density)
(# #)
CL-USER 101 > (mapcar #'city-name *)
(NEA-SMYRNI HOWRAH)
If we have the density as a slot with a getter, we can do this:
(defclass city ()
((name :initarg :name :reader city-name)
(population :initarg :population :reader city-population)
(area :initarg :area :reader city-area)
(density :reader city-density)))
(defmethod initialize-instance :after ((c city) &key)
(with-slots (density)
c
(setf density (density c))))
(defparameter *city-objects*
(loop for (name population area) in *cities*
collect (make-instance 'city
:name name
:population population
:area area)))
Now we compute the list as above, but the key is the getter of the density slot:
CL-USER 102 > (remove 21000 *city-objects* :test-not #'>= :key #'city-density)
(# #)
CL-USER 103 > (mapcar #'city-name *)
(NEA-SMYRNI HOWRAH)