The library (INCF CL)
is a collection of convenience functions and
macros for Common Lisp.
The features it provides are:
- List comprehensions.
- Doctest suite for automatic verification of examples in docstrings.
- List manipulation functions similar to those in Haskell’s prelude.
- Nesting functions akin to those available in Mathematica.
This library is released under the X11 license and it has been tested with the following Common Lisp implementations:
- Armed Bear Common Lisp 1.6.0-dev
- Clozure Common Lisp 1.12-dev
- Embeddable Common Lisp 16.1.3
- Steel Bank Common Lisp 1.5.1.398
The easiest way to install (INCF CL)
is to use Quicklisp:
(ql:quickload "incf-cl")
You may alternatively clone the source code repository by issuing the following command:
$ git clone https://github.com/jmbr/incf-cl.git
and then follow the ASDF installation procedure for your CL implementation.
To begin using the library, write:
(use-package :incf-cl)
The function RANGE
is similar to MATLAB’s vector notation. Some use
cases are:
CL-USER> (range 1 10)
(1 2 3 4 5 6 7 8 9 10)
CL-USER> (range 0 1/4 1)
(0 1/4 1/2 3/4 1)
List comprehensions are a programming language construct that closely
mimics the way you declare a set in mathematics and are sometimes more
succinct and readable than a composition of MAPCAR
and DELETE-IF
or a loop.
Here are two examples of how to use the LC
(short for List
Comprehension) macro:
CL-USER> (lc (sin x) (<- x (range 0 .25 (/ pi 2))))
(0.0 0.24740396 0.47942555 0.6816388 0.84147096 0.9489846 0.997495)
CL-USER> (lc (cons x y) (<- x (range 0 2)) (<- y (range 0 2))
(= (+ x y) 2))
((0 . 2) (1 . 1) (2 . 0))
DOCTEST
checks documentation strings for correctness.
For every exported function in the package name passed to DOCTEST
,
- each docstring is scanned for pieces of text resembling interactive sessions,
- then those snippets are evaluated,
- and the resulting values are checked against the expected ones.
For example, consider the package TEST
:
(defpackage :test
(:use :common-lisp :incf-cl)
(:export :factorial))
(in-package :test)
(defun factorial (n &optional (acc 1))
"Returns the factorial of N, where N is an integer >= 0.
Examples:
TEST> (lc (factorial n) (<- n (range 1 5)))
(1 2 6 24 120)
TEST> (factorial 450/15)
265252859812191058636308480000000
TEST> (signals-p arithmetic-error (factorial -1))
T
TEST> (signals-p type-error (factorial 30.1))
T
TEST> (factorial 0)
1"
(declare (type integer n))
(cond
((minusp n) (error 'arithmetic-error))
((/= n (floor n)) (error 'type-error)))
(if (= n 0)
acc
(factorial (1- n) (* n acc))))
You can use DOCTEST
to make sure the examples given in FACTORIAL
’s
documentation string work as expected by writing
CL-USER> (doctest :test)
.....
T
Or, equivalently,
CL-USER> (doctest 'test::factorial)
.....
T
Some list manipulation functions patterned after Haskell’s prelude are available. Namely,
BREAK*
CYCLE
(and its destructive versionNCYCLE
).DROP
DROP-WHILE
FLIP
GROUP
INSERT
INTERSPERSE
(and its destructive versionNINTERSPERSE
).PARTITION
REPLICATE
SCAN*
(using the key parameters:INITIAL-VALUE
and:FROM-END
it works asscanl
,scanl1
,scanr
, orscanr1
)SPAN
SPLIT-AT
TAKE
TAKE-WHILE
UNZIP
The on-line documentation for each of them can be read using
DESCRIBE
(or M-x slime-describe-symbol
in SLIME). See also A Tour
of the Haskell Prelude by Bernie Pope for more information.
Since Common Lisp doesn’t guarantee tail call elimination, these functions are written iteratively to avoid stack overflows.
The function NEST-LIST
applies a function to an initial value, then
applies the same function to the previous result, and so on. This
stops after a specified number of evaluations or when a given
predicate is true and a list containing all the results is returned.
NEST
works as NEST-LIST
but it only returns the last result, not
the whole list.
Some examples:
CL-USER> (setf *print-circle* nil)
NIL
CL-USER> (nest-list (lambda (x) `(sin ,x)) 'z :max 3)
(Z (SIN Z) (SIN (SIN Z)) (SIN (SIN (SIN Z))))
CL-USER> (nest-list #'+ '(1 1) :max 10)
(1 1 2 3 5 8 13 21 34 55 89 144)
CL-USER> (nest #'+ '(1 1) :max 10)
144
CL-USER> (nest-list (lambda (x) (mod (* 2 x) 19))
2
:test (lambda (x) (/= x 1)))
(2 4 8 16 13 7 14 9 18 17 15 11 3 6 12 5 10 1)
The closely related function FIXED-POINT
returns the fixed point of
a function starting from an initial value. Whether a fixed point has
been reached or not is determined by a test function (EQL
by
default).
For example, the square root of 2 using Newton’s method can be computed as:
CL-USER> (fixed-point (lambda (x)
(float (- x (/ (- (expt x 2) 2) (* 2 x)))))
1)
1.4142135
There’s an implementation of UNFOLD
and UNFOLD-RIGHT
as specified
in SRFI 1: List library. Here’s an example of UNFOLD
:
(defun euler (f x0 y0 interval h)
"Computes an approximate solution of the initial value problem:
y'(x) = f(x, y), x in interval; y(x0) = y0
using Euler's explicit method. Interval is a list of two elements
representing a closed interval. The function returns a list of
points and the values of the approximate solution at those points.
For example,
EULER> (euler (lambda (x y)
(declare (ignore y))
(- (sin x)))
0 1 (list 0 (/ pi 2)) 0.5)
((0 1) (0.5 1.0) (1.0 0.7602872) (1.5 0.33955175))"
(assert (<= (first interval) (second interval)))
(unfold (lambda (x) (> (first x) (second interval)))
#'identity
(lambda (pair)
(destructuring-bind (x y) pair
(list (+ x h) (+ y (* h (funcall f x y))))))
(list x0 y0)))
The function $
returns the composition of several functions. The
following example illustrates its use:
CL-USER> (funcall ($ (lambda (x) (* x x))
(lambda (x) (+ x 2)))
2)
16
DOHASH
iterates over a hash table with semantics similar to those of
DOLIST
:
CL-USER> (defparameter *hash-table* (make-hash-table))
*HASH-TABLE*
CL-USER> (setf (gethash "one" *hash-table*) 1)
1
CL-USER> (setf (gethash "two" *hash-table*) 2)
2
CL-USER> (setf (gethash "three" *hash-table*) 3)
3
CL-USER> (dohash (key value *hash-table*)
(format t "~a => ~d~%" key value))
three => 3
two => 2
1
NIL
CL-USER> (let ((product 1))
(dohash (key value *hash-table* product)
(setf product (* product value))))
6
STRING-JOIN
glues together a list of strings placing a given
separator between each string. By default, the separator is a space.
CL-USER> (string-join '("Hello" "world"))
"Hello world"
CL-USER> (string-join '("Hello" "world") ", ")
"Hello, world"
Please use Github to send patches and bug reports.