incf-cl
2019-07-11
INCF CL is a library of convenience functions for Common Lisp
1Overview
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
2Usage
2.1Installation
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 CLimplementation.2.2Loading the library
To begin using the library, write:
(use-package :incf-cl)
2.3Features
2.3.1Ranges
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)
2.3.2List comprehensions
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))
2.3.3Doctests
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
'sdocumentation string work as expected by writingCL-USER> (doctest :test)
.....
T
Or, equivalently,CL-USER> (doctest 'test::factorial)
.....
T
2.3.4Prelude
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
DESCRIBE
(or M-x slime-describe-symbol
in SLIME). See also [[http://berniepope.id.au/assets/files/haskell.tour.pdf][A Tourof 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.
2.3.5Nesting
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
2.3.6Unfolds
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)))
2.3.7Functions
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
2.3.8Hash table utilities
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
one => 1
NIL
CL-USER> (let ((product 1))
(dohash (key value *hash-table* product)
(setf product (* product value))))
6
2.3.9Strings
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"
3Feedback
Please use Github to send patches and bug reports.