LISP - Functions as Arguments
Lisp - Functions as First-Class Citizens
Section titled “Lisp - Functions as First-Class Citizens”In Lisp, functions are first-class citizens. This is a cornerstone of functional programming and means that functions are treated just like any other data type, such as numbers or strings.
- Assignable to Variables: A function can be stored in a variable.
- Passed as Arguments: A function can be passed as an argument to another function.
- Returned from a Function: A function can be the return value of another function.
Core Concepts
Section titled “Core Concepts”Higher-Order Functions
Section titled “Higher-Order Functions”A function that either takes another function as an argument or returns a function is called a Higher-Order Function (HOF). This capability allows for creating highly abstract and reusable code. Common Lisp has many built-in HOFs, such as mapcar, reduce, find-if, and sort.
Lambda Expressions (Anonymous Functions)
Section titled “Lambda Expressions (Anonymous Functions)”A lambda expression creates an anonymous function—a function without a name. This is extremely useful for defining a simple, one-off function to be passed as an argument to a HOF, without the need to formally define it with defun.
Passing Functions as Arguments
Section titled “Passing Functions as Arguments”To pass a function, we must refer to the function object itself, not its name. The special operator #' (a shorthand for (function ...)), is the standard way to do this.
Example: Using a Named Function
Section titled “Example: Using a Named Function”;; A higher-order function that takes a function and a number.(defun operate-on-five (op) (funcall op 5))
(defun square (x) (* x x))(defun cube (x) (* x x x))
;; Pass the `square` function object to `operate-on-five`(format t "Square of 5 is ~d~%" (operate-on-five #'square))
;; Pass the `cube` function object(format t "Cube of 5 is ~d~%" (operate-on-five #'cube))Output
Section titled “Output”Square of 5 is 25Cube of 5 is 125Here, operate-on-five is a HOF. funcall is used to invoke the function object op with the argument 5.
Example: Using a Lambda Expression
Section titled “Example: Using a Lambda Expression”mapcar is a classic HOF that applies a function to each element of a list, returning a new list of the results.
;; `mapcar` takes a function and one or more lists.(let ((numbers '(1 2 3 4)))
;; Use a lambda expression to define a function on the fly ;; that doubles its argument. (let ((doubled (mapcar (lambda (x) (* x 2)) numbers))) (format t "Doubled list: ~a~%" doubled)))Output
Section titled “Output”Doubled list: (2 4 6 8)funcall vs. apply
Section titled “funcall vs. apply”Two essential HOFs are used to invoke functions dynamically:
funcall: Calls a function with arguments provided individually.(funcall #'+ 1 2 3)apply: Calls a function with its arguments supplied as a list.(apply #'+ '(1 2 3))
Example
Section titled “Example”(let ((args '(10 5))) ;; Using funcall, we have to extract arguments from the list. (format t "funcall: ~d~%" (funcall #'- (first args) (second args)))
;; Using apply, we can pass the list of arguments directly. (format t "apply: ~d~%" (apply #'- args)))Output
Section titled “Output”funcall: 5apply: 5Why This Matters: Key Benefits
Section titled “Why This Matters: Key Benefits”- Abstraction and Reusability: HOFs let you write generic functions for mapping, filtering, and reducing data, which can then be customized with specific functions. For example, a single
filterfunction can be used to find even numbers, long strings, or active users just by passing a different predicate. - Clean and Expressive Code: Using HOFs like
mapcarandreduceoften leads to more concise and declarative code compared to writing manual loops. - Callbacks and Event Handling: A common pattern where a function is registered (passed as an argument) to be called later when a specific event occurs, such as a button click or a network response.