Previous: , Up: Miscellaneous Features   [Contents][Index]


15.25.12.2 Destructuring

Destructuring allows you to bind a set of variables to a corresponding set of values anywhere that you can normally bind a value to a single variable.21

Suppose you want to assign values from a list to the variables a, b, and c. You could use one ‘for’ clause to bind the variable numlist to the car of the specified expression, and then you could use another ‘for’ clause to bind the variables a, b, and c sequentially.

;;; Collect values by using FOR constructs.
(loop for numlist in '((1 2 4.0) (5 6 8.3) (8 9 10.4))
      for a = (first numlist)
      for b = (second numlist)
      for c = (third numlist)
      collect (list a b c))

Destructuring makes this process easier by allowing the variables to be bound in parallel in each loop iteration. You can declare data types by using a list of ‘type-spec’ arguments. If all the types are the same, you can use a shorthand destructuring syntax, as the second example following illustrates.

;;; Destructuring simplifies the process.
(loop for (a b c) (integer integer float) in
      '((1 2 4.0) (5 6 8.3) (8 9 10.4))
      collect (list c b a)))
;;; If all the types are the same, this way is even simpler.
(loop for (a b c) float in
      '((1.0 2.0 4.0) (5.0 6.0 8.3) (8.0 9.0 10.4))
      collect (list c b a))

If you use destructuring to declare or initialize a number of groups of variables into types, you can use the loop keyword ‘and’ to simplify the process further.

;;; Initialize and declare variables in parallel
;;; by using the AND construct.
(loop with (a b) float = '(1.0 2.0)
      and (c d) integer = '(3 4)
      and (e f)
      return (list a b c d e f))

A data type specifier for a destructuring pattern is a tree of type specifiers with the same shape as the tree of variables, with the following exceptions:

;;; Declare X and Y to be of type VECTOR and FIXNUM, respectively.
(loop for (x y) of-type (vector fixnum) in my-list do ...)

If nil is used in a destructuring list, no variable is provided for its place.

(loop for (a nil b) = '(1 2 3)
      do (return (list a b)))

Note that nonstandard lists can specify destructuring.

(loop for (x . y) = '(1 . 2)
      do (return y))
(loop for ((a . b) (c . d))
          of-type ((float . float) (integer . integer))
          in '(((1.2 . 2.4) (3 . 4)) ((3.4 . 4.6) (5 . 6)))
      collect (list a b c d))
  1. initially - finally - finally return
    initially {expr}*
    finally [do | doing] {expr}*
    finally return expr
    
    initially

    The ‘initially’ construct causes the specified expression to be evaluated in the loop prologue, which precedes all loop code except for initial settings specified by constructs ‘with’, ‘for’, or ‘as’.

    finally

    The ‘finally’ construct causes the specified expression to be evaluated in the loop epilogue after normal iteration terminates.

    expr

    The expr argument can be any non-atomic Common Lisp form.

    Bypass finally

    Clauses such as

    • =return,
    • always’,
    • never’, and
    • thereis

    can bypass the ‘finally’ clause.

    Avoiding finally using return

    The Common Lisp macro return (or the ‘return loop’ construct) can be used after ‘finally’ to return values from a loop. The evaluation of the return form inside the ‘finally’ clause takes precedence over returning the accumulation from clauses specified by such keywords as ‘collect’, ‘nconc’, ‘append’, ‘sum’, ‘count’, ‘maximize’, and ‘minimize’; the accumulation values for these pre-empted clauses are not returned by the loop if return is used.

    Arbitrary Number of Forms Grouped by progn

    The constructs

    • do’,
    • initially’, and
    • finally

    are the only loop keywords that take an arbitrary number of (non-atomic) forms and group them as if by using an implicit progn.

    Examples

    ;;; This example parses a simple printed string representation
    ;;; from BUFFER (which is itself a string) and returns the
    ;;; index of the closing double-quote character.
    
    (loop initially (unless (char= (char buffer 0) #\")
                      (loop-finish))
          for i fixnum from 1 below (string-length buffer)
          when (char= (char buffer i) #\")
            return i)
    
    ;;; The FINALLY clause prints the last value of I.
    ;;; The collected value is returned.
    
    (loop for i from 1 to 10
          when (> i 5)
            collect i
          finally (print i)) ;Prints 1 line
    ;;; => (6 7 8 9 10)
    
    ;;; Return both the count of collected numbers
    ;;; as well as the numbers themselves.
    
    (loop for i from 1 to 10
          when (> i 5)
            collect i into number-list
            and count i into number-count
          finally (return (values number-count number-list)))
    ;;; => 5 and (6 7 8 9 10)
    
  2. named
    named name
    

    The ‘named’ construct allows you to assign a name to a loop construct so that you can use the Common Lisp special form return-from to exit the named loop.

    Only one name may be assigned per loop; the specified name becomes the name of the implicit block for the loop.

    If used, the ‘named’ construct must be the first clause in the loop expression, coming right after the word ‘loop’.

    Example

    ;;; Just name and return.
    (loop named max
          for i from 1 to 10
          do (print i)
          do (return-from max 'done)) ;Prints 1 line
    ;;; => DONE
    

Footnotes

(21)

[It is worth noting that the destructuring facility of loop predates, and differs in some details from, that of destructuring-bind, an extension that has been provided by many implementors of Common Lisp.-GLS]


Previous: Data Types, Up: Miscellaneous Features   [Contents][Index]