Applicative Programming
June 8, 2008 | Filed Under Common Lisp - Touretzky
———————————————————
Chapter 7
———————————————————
FUNCALL calls a function on some inputs:
(FUNCALL #’cons ‘a ‘b) -> (a . b)
Can also map a function to a symbol:
(SETF fn #’cons)
(FUNCALL fn ‘a ‘b) -> (a . b)
only ordinary functions should be quoted with #’ - macros, symbols or special functions will generate an error (#’IF -> ERROR)
(DEFUN SQUARE (n)
(* n n))
(SQUARE 3) -> 9
(SQUARE ‘(1 2 3 4 5)) ->ERROR
(MAPCAR #’SQUARE ‘(1 2 3 4 5)) -> (1 4 9 16 25)
MAPCAR passes each element of a list to a function.
Pass multiple args by separate lists:
(DEFUN mult (x y)
(* x y ))
(MAPCAR #’mult ‘(2 3 4) ‘(5 6 7)) -> (10 18 28)
(SETF nums
‘( (1 one)
(2 two)
(3 three)))
(MAPCAR #’FIRST nums) -> (1 2 3)
(MAPCAR #’SECOND nums) -> (one two three)
(MAPCAR #’REVERSE nums) -> ((one 1)….)
Lambda functions:
functions defined in-line - can be passed (i.e. to MAPCAR) without having to DEFUN first:
(MAPCAR #’(lambda (n) (* n n)) ‘(1 2 3 4)) -> (1 4 9 16)
FIND-IF returns the FIRST element which matches the predicate/lambda function:
(FIND-IF #’oddp ‘(2 4 7 8 9)) -> 7
(FIND-IF #’(lambda (x) (> x 3)) ‘(1 2 3 4 5)) ->4
(REMOVE-IF #’(lambda (x) (plusp x)) ‘(1 -2 3 -4)) -> (-2 -4)
(REMOVE-IF-NOT #’(lambda (x) (plusp x)) ‘(1 -2 3 -4)) -> (1 3)
; can simply use #’PLUSP X ????
REDUCE applies a function to a list and returns a single result - must be a function that takes two arguments:
(REDUCE #’+ ‘(1 2 3)) -> 6
(REDUCE #’* ‘(2 4 5)) -> 40
Takes the result of each loop of the function and uses the resultant list as one of the input arguments for the next loop.
Can also be used on a list of lists:
(REDUCE #’APPEND ‘(1 2 3) ‘(4 5 6)) -> (1 2 3 4 5 6)
(REDUCE #’UNION ‘(1 2 3) ‘(1 4 5)) -> (1 2 3 4 5)
EVERY applies a predicate to a list, and returns T if every element matches the predicate:
(EVERY #’oddp ‘(1 3 5)) -> T
(EVERY #’(lambda (x) (> x 3)) ‘(4 5 6)) -> T
(EVERY #’(lambda (x) (< x 2)) '(1 2 3)) -> NIL
EVERY on a NIL list will always return T
*** EVERY can also be used on multiple lists, matching each pair in turn:
(EVERY #’> ‘(20 10 5) ‘(18 8 3)) -> T
———————————————————
Notes on Chapter 7 exercises
Look up a value in a paired list with ASSOC:
(SETF tbl ‘((1 one) (2 two)))
(DEFUN LOOK-UP (num)
(assoc num tbl))
(LOOK-UP ‘1) -> (1 one)
Run a test on a list and remove those that pass/don’t pass the test, use REMOVE-IF and REMOVE-IF-NOT:
(DEFUN GET-ODD-NUMS (assoc-list)
(REMOVE-IF-NOT #’(LAMBDA (x) (ODD-P (FIRST x))) assoc-list))
(GET-ODD-NUMS tbl) -> ((1 one))
Run a test and return the first element that matches, use FIND-IF
Does one element of a list come before another? Use MEMBER twice:
(DEFUN cards ‘(1 2 3 4 5))
(DEFUN C1-COMES-FIRST-P (c1 c2)
(MEMBER c2 (MEMBER c1 cards)))
(C1-COMES-FIRST-P ‘1 ‘2) -> (2 3 4 5)
(C1-COMES-FIRST-P ‘2 ‘1) -> NIL
TO run a test on a list where you are comparing each element of a list, and carrying the “winner” forward, use reduce:
(DEFUN high-card (hand)
(REDUCE #’(LAMBDA (card1 card2)
(if (higher-rank-p card1 card2)
card1
card2))
hand))
this runs a test on two cards, returning the higher one and feeding this back into the Lambda as card1 with the next card from hand.
———————————————————
MAPCAR can operate on 2 lists:
(MAPCAR #’* ‘(1 2 3) ‘(4 5 6)) -> (4 10 18)
(MAPCAR #’(LAMBDA (x y) (LIST x ‘gets y))
‘(John Jim Jol) ‘(Job1 Job2 Job3)) -> ((John gets Job1)(Jim gets Job2)..)
FUNCALL works the same as #’
‘CONS -> CONS
#’CONS -> #
#’(LAMBDA (x) (+ x 2)) -> #
Function objects are stored as data, and can be assigned:
(SETF g #’(LAMBDA (x) (* x 10))) -> #
(FUNCALL g 12) -> 120
(G 12) -> error (G is not a function)
Keyword Arguments:
use FROM-END with a non-NIL argument with FIND-IF, REMOVE-IF, REDUCE, etc, to parse from right to left:
(FIND-IF #’ODD-P ‘(1 2 3 4 5) :FROM-END T) -> 5
Especially useful with REDUCE:
(REDUCE #’CONS ‘(a b c d e)) -> ((((A . B) . C) . D) . E)
(REDUCE #’CONS ‘(a b c d e) :FROM-END T) -> (a b c d . e)
Applicative operators:
(DEFUN inalienable-rights (fn)
(funcall fn
‘(life liberty and the pursuit of happiness)))
Any function applied to inalienable-rights will act on the list starting ‘(life..
(inalienable-rights #’length) -> 7
(inalienable-rights #’first) -> life
inalienable-rights will only accept a function that works on a list. CONS requires two lists, so this will not work - would need to wrap in a lambda:
(inalienable-rights #’(lambda (x) (cons ‘high x))) -> (high life liberty…)
alternatively:
(DEFUN func-test (fn)
(funcall fn ‘(1 2 3) ‘(4 5 6)))
(func-test #’cons) -> ((1 2 3) 4 5 6)
(func-test #’(lambda (x y) (mapcar #’* x y))) -> (4 10 18)
By passing back a lexical closure (lambda function) we can make a function that returns a function:
(defun make-greater-than-predicate (n)
#’(lambda (x) (> x n)))
(setf pred (make-greater-than-predicate 3)) -> now have a custom predicate that will take a value x and pass to the lambda function, and returning T if greater than 3.
(find-if pred ‘(1 2 3 4 5 6)) -> 4
Comments
Leave a Reply