spobooks bbv9810.0001.001 in

    Chapter 12: Applicative Programming

    12.1 Introduction to Applicative Programming

    Applicative programming is a technique that allows a function to be applied to each element of a list. A simple example of applicative programming is to add 2 to every element of a list. For example, the list (0 1 2 4) becomes (2 3 4 6). Applicative programming is a very simple yet powerful way to transform lists.

    12.2 Mapping a Function onto a List

    The Common LISP primitive MAPCAR allows you to apply a function to each element of a list. The applied function may be another Common LISP primitive or a user-defined function. Essentially, MAPCAR allows you to pass a function to a function. In Common LISP, when you pass a function to a function, the passed function must be proceeded by a #' (sharp-quote). The template for MAPCAR is:

    (MAPCAR #'FUNCTION '(LIST))

    Recall that the Common LISP primitive SQRT returns the square root of its argument.

    Example 12.2.1
    ? (sqrt 25)
    5

    Since MAPCAR allows you to map a function onto a list, we can take the square root of each element of a list by passing SQRT to MAPCAR.

    Example 12.2.2
    ? (mapcar #'sqrt '(25 36 81))
    (5 6 9)

    Notice the syntax of MAPCAR. The function passed to MAPCAR is preceded by a #'. The argument to the passed function is a quoted list.

    Recall in Chapter 9, Example 9.2.1, we wrote a user-defined function that used COND to determine if a midi-note is within the range of the MIDI specification.

    Example 12.2.3
    ? (defun range (note)
    (cond ((< note 0) 'too-low)
    ((> note 127) 'too-high)
    (t 'in-range)))

    Using MAPCAR, we can determine if an entire list of MIDI notes is in range.

    Example 12.2.4
    ? (mapcar #'range '(0 -5 129 127 54))
    (IN-RANGE TOO-LOW TOO-HIGH IN-RANGE IN-RANGE)

    Since returning a list that describes the range status of list of MIDI notes may not be helpful in composing music, we alter our function in Example 12.2.3. Any MIDI note outside the range of the MIDI Specification, is recalculated to fall in range. The new function, called RANGIFY, is defined in Example 12.2.5.

    Example 12.2.5
    ? (defun rangify (note)
    (cond ((< note 0) (+ note (abs note)))
    ((> note 127) (- note (- note 127)))
    (t note)))

    The function RANGIFY uses an algorithm that returns 0 for all MIDI notes less than 0 and 127 for all MIDI notes greater than 127.RANGIFY is one of many algorithms that may be used to correct for values that fall outside of the range of the MIDI Specification.

    In Example 12.2.6, we map the function RANGIFY onto a list of values. Notice all values less than 0 return 0 and all values greater than 127 return 127.

    Example 12.2.6
    ? (mapcar #'rangify '(0 -5 129 127 54))
    (0 0 127 127 54)

    So far, the functions that we've passed to MAPCAR only have one argument. It is possible to pass MAPCAR functions that have more than one argument. Consider the function definition and function call in Example 12.2.7.

    Example 12.2.7
    ? (defun my-transpose (note interval)
    (+ note interval))
    ? (my-transpose 60 -12)
    48
    ? (my-transpose 72 6)
    78
    The function MY-TRANSPOSE requires two arguments: a note and an interval.MY-TRANSPOSE transposes the note by the specified interval. In Example 12.2.8, we use MAPCAR to transpose a list of notes by a list of specified intervals.MAPCAR returns a list of the transposed notes.
    Example 12.2.8
    ? (mapcar #'my-transpose '(20 30 40 50) '(-5 5 -10 10))
    (15 35 30 60)

    12.3 Lambda Expressions

    Sometimes, you may have a procedure that you'd like to call on-the-fly. Such a procedure may be one that is only used once so you don't want to bother giving it a name. Lambda expressions are unnamed or anonymous functions.

    The template for a Common LISP lambda expression looks very much like that of DEFUN. Table 12.3.1 compares and contrasts DEFUN and LAMBDA expressions.

    Table 12.3.1

    Named Function (DEFUN )

    Anonymous Function (LAMBDA )

    (DEFUN FUNCTION-NAME(OPTIONAL-ARGUMENT-LIST)
    FUNCTION DEFINITION))
    (LAMBDA(OPTIONAL-ARGUMENT-LIST)
    FUNCTION DEFINITION))
    Example 12.3.1
    (DEFUN MY-TRANSPOSE(NOTE INTERVAL)
    (+ NOTE INTERVAL))
    Example 12.3.1
    (LAMBDA(NOTE INTERVAL)
    (+ NOTE INTERVAL))

    When a LAMBDA expression is encountered, the computer identifies it as an anonymous function as seen in Example 12.3.2.

    Example 12.3.2

    ? (lambda (note interval) (+ note interval))
    #<Anonymous Function #x63D3C0E>

    Recall in Example 12.2.8 we used MAPCAR to transpose a list of MIDI notes by a list of intervals. In Example 12.3.3, we use MAPCAR with a LAMBDA expression to perform the same task.

    Example 12.3.3
    ? (mapcar #'(lambda (note interval)
    (+ note interval)) '(20 30 40 50) '(-5 5 -10 10))
    (15 35 30 60)

    Example 12.3.4 demonstrates the use of MAPCAR and LAMBDA expressions in Common Music.

    Example 12.3.4: mapcar.lisp
    (generator mapcar midi-note (length 30 channel 0)
    (vars (6-Z36 (make-item-stream 'items 'cycle '(0 1 2 3 4 7)))
    (5-1 (make-item-stream 'items 'cycle '(0 1 2 3 4)))
    (5-4 (make-item-stream 'items 'cycle '(0 1 2 3 6)))
    ;;;
    ;;; Make item streams from sets 6-Z36, 5-1 and 5-4

    (5-4-list '(0 1 2 3 6)))

    (cond ((< count 10)
    (setf note (invert (item 6-Z36) 30)))
    ((and (>= count 10) (< count 20))
    (setf note (transpose (item 5-1) 64)))
    (T (setf note (invert (item 5-4) 50))))
    #|
    The pitch material of the generator is divided into three sections. Section 1 uses set 6-Z36 that is inverted around MIDI keynumber 30. Section 2 uses set 5-1 that is transposed up 64 half steps. Section 3 uses set 5-4 that is inverted around MIDI keynumber 50. invert and transpose are Common Music functions.
    |#
    (let* ((index (mod count 5))
    (amp-list (mapcar #'(lambda (N) (+ (* N .1) .1)) 5-4-list))
    (amp (nth index amp-list)))
    #|
    The Common LISP primitive MOD accepts two inputs and returns the remainder after dividing the first argument by the second argument.
    |#
    (setf amplitude amp))
    #|
    Create an index to read into the list. Because the list has five elements, we calculate the index as the current count mod which cyclically returns integers 0, 1, 2, 3, 4.
    We map a lambda expression onto the 5-4-list to scale each amplitude value by .1. We use nth with our index to return each item in the list. The item returned from the list is assigned to the amplitude slot.
    |#
    (let ((rhy (float (/ count length))))
    (if (< rhy .1)
    (setf rhythm .1) (setf rhythm rhy)))
    (setf duration .5))

    audio file mapcar.mp3

    12.4 List Filtering

    There are many Common LISP primitives that help filter lists by mapping a predicate onto each member of a list. The template for using a list-filtering primitives is:
    (PRIMITIVE #'PREDICATE LIST)

    Notice that the predicates passed to the primitive are proceeded by #'.

    We begin by defining a predicate function that determines whether or not its input is a c-major triad.
    Example 12.4.1
    ? (defun c-majorp (chord)
    (not (set-difference chord '(c e g))))

    The predicate function C-MAJORP takes the SET-DIFFERENCE (or set subtraction) between the function input chord and the list '(C E G). If all of the elements of the chord are found in '(C E G),SET-DIFFERENCE returns NIL. We take the NOT of NIL and the predicate function returns T

    If some of the elements in the input chord are remaining after set subtraction, SET-DIFFERENCE returns those elements as a list. Recall that any non-NIL value evaluates to T. We take the NOT of a non-NIL value and the predicate function returns NIL.

    The Common LISP primitive COUNT-IF returns the number of occurrences a predicate function evaluates to T.

    Example 12.4.2
    ? (count-if #'c-majorp '((g e c) (f a c)
    (c e g b-flat) (g b d)))
    1

    Notice that in Example 12.4.2 the predicate C-MAJORP finds one occurrence of the list '(C E G).

    Another Common LISP list-filtering primitive is FIND-IF. As we know from Example 12.4.2, COUNT-IF counts the number of occurrences a predicate evaluates to T.FIND-IF actually returns the elements that return a T evaluation.

    Example 12.4.3
    ? (find-if #'c-majorp '((g e c) (f a c)
    (c e g b-flat) (g b d)))
    (G E C)

    The Common LISP primitives FIND-IF and REMOVE-IF-NOT have similar functionality. The result returned by REMOVE-IF-NOT includes another level of parentheses.

    Example 12.4.4
    ? (remove-if-not #'c-majorp '((g e c) (f a c)
    (c e g b-flat) (g b d)))
    ((G E C))

    The logical complement of FIND-IF is the Common LISP primitive REMOVE-IF.REMOVE-IF returns every object that the predicate evaluates as NIL.

    Example 12.4.5
    ? (remove-if #'c-majorp '((g e c) (f a c)
    (c e g b-flat) (g b d)))
    ((F A C) (C E G B-FLAT) (G B D))

    The Common LISP primitive EVERY also expects a predicate and list as input.EVERY returns T if every element mapped to the predicate evaluates to T.

    Example 12.4.6
    ? (every #'c-majorp '((c e g) (e g c) (c g e)))
    T

    In Example 12.4.7, we use a LAMBDA expression to see if every note in a list is within the range of the MIDI specification.

    Example 12.4.7

    ? (every #'(lambda (note) (and (>= note 0)
    (<= note 127))) '(12 25 48 57))
    T

    12.5 Using Applicative Forms in Common Music

    In Example 12.5.1 (applicative.lisp), we apply what we've learned about applicative programming in Common LISP to Common Music. We use pc sets 4-4 (0 1 2 5), 4-5 (0 1 2 6), and 4-6 (0 1 2 7) as a means of unifying the pitch content. These three sets are assigned using vars. The three sets are combined to create a nested list. We create a list of all of the elements found in 4-4, 4-5 and 4-6 using APPEND. An amplitude-set is assigned by applying FIND-IF to the nested sets to search for set 4-4 using the user-defined predicate 4-4P. A rhythm set is assigned by applying REMOVE-IF-NOT to the nested sets searching for a set that is not 4-5. We have to take the FIRST of what is returned by REMOVE-IF-NOT because REMOVE-IF-NOT returns a nested list. We assign a duration set from the appended sets. All sets are converted into item streams. The note slot is conditionally assigned depending on how many events have been generated. If the rhythm or duration slot generates a zero value, the rhythm and duration slots are assigned a value of .3.

    Example 12.5.1: applicative.lisp

    (defun 4-4p (pitch-class)
    "a predicate function that returns T is its input is set 4-4"
    (not (set-difference pitch-class '(0 1 2 5))))
    (defun 4-5p (pitch-class)
    "a predicate function that returns T is its input is set 4-5"
    (not (set-difference pitch-class '(0 1 2 6))))
    #|
    Generator applicative uses vars to assign 3 sets: 4-4, 4-5 and 4-6. These three sets are combined to create a nested list. We then create a list of all of the elements found in 4-4, 4-5 and 4-6 using append. We assign an amplitude-set by applying find-if to the nested sets. We assign a rhythm set by applying remove-if-not to the nested sets. We have to take the first of what is returned by remove-if-not because a nested list is returned. We assign a duration set from the appended sets. These sets are converted into item streams. The note slot is conditionally assigned dependent on how many events have been generated. If the rhythm or duration slot is equal to zero, they are assigned a value of .3.
    |#
    (generator applicative midi-note (length 30)
    (vars (4-4 '(0 1 2 5))
    (4-5 '(0 1 2 6))
    (4-6 '(0 1 2 7))
    (nested-sets (list 4-4 4-5 4-6))
    (appended-sets (append 4-4 4-5 4-6))
    (amplitude-set (make-item-stream 'items 'cycle
    (find-if #'4-4p nested-sets)))
    (rhythm-set (make-item-stream 'items 'cycle
    (first (remove-if-not #'4-5p nested-sets))))
    (duration-set (make-item-stream 'items 'heap
    appended-sets)))
    (cond ((< count 10) (setf note
    (transpose (nth (random 4) 4-4) 36)))
    ((< count 20) (setf note
    (transpose (nth (mod count 4) 4-5) 47)))
    (t (setf note (transpose (nth (random 4) 4-6) 58))))
    (setf amplitude (+ .2 (* (item amplitude-set) .1)))
    (if (zerop (item rhythm-set))
    (setf rhythm .3)
    (setf rhythm (* (item rhythm-set) .2)))
    (if (zerop (item duration-set))
    (setf duration .3)
    (setf duration (* (item duration-set) .3))))

    audio file applicative.mp3