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
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))))
DOCTEST to make sure the examples given in FACTORIAL'sdocumentation string work as expected by writingCL-USER> (doctest :test)
.....
T
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 version- NCYCLE).
- DROP
- DROP-WHILE
- FLIP
- GROUP
- INSERT
- INTERSPERSE(and its destructive version- NINTERSPERSE).
- PARTITION
- REPLICATE
- SCAN*(using the key parameters- :INITIAL-VALUEand- :FROM-ENDit works as- scanl,- scanl1,- scanr, or- scanr1)
- 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.