License: Public Domain , Load it with Quicklisp: (ql:quickload "multiple-value-variants")
Library type: Operator overlay macro , Project complexity: Medium

multiple-value-variants gives access to multiple-value variants of operators through one macro: multiple-value.

There are built-in variants for some standard operators; it's easy to create your own variants for other operators.

The multiple-value mapping operators are especially useful.

multiple-value-variants » API overview

First of all, in the way of packages there's the multiple-value-variants package, which is also nicknamed multiple-value-variant, mv-variants and mv-variant. The primary exported symbol is the multiple-value macro. Explicitly (:import-from #:multiple-value-variants #:multiple-value) for normal usage. Don't (:use)!

The multiple-value-variants package also exports other symbols related to creation of new multiple-value variants and querying of existing ones (documentation pending, check package.lisp for a list of all exported symbols). The most important of these is mv-variant:define, which is normally used to define new multiple-value variants. You should normally explicitly package-qualify this symbol.

There are 2 recurring features throughout the API:

multiple-value-variants » Built-in multiple-value variants

Variant progn (&key (identity '(values)))
  (&body forms)

Just like progn, except if there are no forms then the identity is evaluated instead.

(multiple-value () (progn 1 2 3))
=> 3
(multiple-value () (progn))
=> [no values]
Variant prog1 ()
  (result &body body)

This straightforwardly expands to a multiple-value-prog1.

Variant and (&key identity (nth 0))
  (&rest forms)

If identity is not specified, then there must be at least one form.

This is like normal and, except if one of the non-last forms' nth-value is false, returns all values that were returned by that form, not just the primary value.

(multiple-value ()
  (and (values nil 'other 'values)
       t))
=> NIL, OTHER, VALUES
Variant or (&key identity (nth 0))
  (&rest forms)

If identity is not specified, then there must be at least one form.

This is like normal or, except if one of the non-last forms' nth-value is true, returns all values that were returned by that form, not just the primary value.

(multiple-value ()
  (or (find-symbol "OR" '#:cl)
      (find-symbol "XOR" '#:cl)))
=> OR, :EXTERNAL
Variant cond (&key (nth 0))
  (&rest clauses)

This is just like normal cond, except:

  • If a clause that has no forms succeeds, then all the values that were returned by the test-form are returned, not just the primary value;

  • If no clause succeeds, then no values are returned (instead of nil).

(let ((hash (make-hash-table)))
  (multiple-value () (cond ((gethash 'key hash))
                           ((values)))))
=> [no values]
(let ((hash (make-hash-table)))
  (setf (gethash 'key hash) 'value)
  (multiple-value () (cond ((gethash 'key hash))
                           ((values)))))
=> VALUE, T
Variant when (&key (else '(values)) (identity '(values)))
  (test &body forms)

If test evaluates to true, evaluate forms as an implicit (multiple-value progn) with identity as the identity.

Else, evaluate else.

(multiple-value ()
  (when nil
    (print "side-effect")))
=> [no values]
Variant unless (&key (else '(values)) (identity '(values)))
  (test &body forms)

If test evaluates to false, evaluate forms as an implicit (multiple-value progn) with identity as the identity.

Else, evaluate else.

Variant case () (keyform &body cases)
Variant ccase () (keyplace &body cases)
Variant ecase () (keyform &body cases)
Variant typecase () (keyform &body cases)
Variant ctypecase () (keyplace &body cases)
Variant etypecase () (keyform &body cases)

These are like their normal counterparts, except the forms in each case is an implicit (multiple-value progn), and if no case matches in case or typecase, then no values are returned (instead of nil).

Variant mapcar (multiple-values-count)
  (function &rest+ lists)
Variant mapcan (multiple-values-count)
  (function &rest+ lists)
Variant maplist (multiple-values-count)
  (function &rest+ lists)
Variant mapcon (multiple-values-count)
  (function &rest+ lists)

These are just like the normal variants, except they can accumulate multiple results at the same time. This is especially useful to "triage" values (ex: split the elements of a list into 2 lists according to some criteria), and to accumulate multiple "layers" of values in one pass for macroexpansions, as an alternative to repeated mapping (sometimes with some readability problems due to reduced "correlation").

multiple-values-count is not evaluated, and must be a non-negative integer indicating the number of results (values) to accumulate and return. function would normally return that many values. If function returns less than multiple-values-count values, then the remaining values are nil. If function returns more than multiple-values-count values, then the excess values are ignored.

(multiple-value (2)
  (mapcar #'truncate '(3 5/4 5.5)))
=> (3 1 5), (0 1/4 0.5)
(multiple-value 2
  (mapcan (lambda (object)
            (if (numberp object)
                (values (list object) nil)
                (values nil (list object))))
          '(0 a 2 3/4 c)))
=> (0 2 3/4), (A C)
(multiple-value 3
  (maplist (lambda (tail)
             (values tail
                     (reverse tail)
                     (list (first tail) (second tail))))
           '(a b c d e)))
=>
((A B C D E) (B C D E) (C D E) (D E) (E))
((E D C B A) (E D C B) (E D C) (E D) (E))
((A B) (B C) (C D) (D E) (E NIL))