LISP - Calling Function using apply
Lisp - Invoking Functions Dynamically with apply and funcall
Section titled “Lisp - Invoking Functions Dynamically with apply and funcall”Lisp is renowned for its homoiconicity (code is data), which allows for powerful metaprogramming. A key part of this is the ability to call functions dynamically. The apply and funcall functions are the primary tools for this job. They allow you to invoke a function that is determined at runtime.
funcall vs. apply: The Core Difference
Section titled “funcall vs. apply: The Core Difference”Both funcall and apply call a function, but they differ in how they accept arguments:
funcall: Takes a function object followed by its arguments, one by one.apply: Takes a function object, any number of initial arguments, and a list containing the remaining arguments.
Side-by-Side Comparison
Section titled “Side-by-Side Comparison”;; The goal is to compute (+ 10 20 30)
;; Using FUNCALL: Arguments are passed individually.(funcall #'+ 10 20 30);=> 60
;; Using APPLY: Arguments are passed in a list.(apply #'+ '(10 20 30));=> 60
;; APPLY can also take initial arguments before the list.(apply #'+ 10 '(20 30));=> 60
;; A common mistake is to give funcall a list.; (funcall #'+ '(10 20 30)) ; This would be an ERROR!The key takeaway: Use apply when your arguments have already been collected into a list.
Practical Use Case: A Dynamic Calculator
Section titled “Practical Use Case: A Dynamic Calculator”Imagine you need a function that takes an operation (like + or *) and a list of numbers, and returns the result. This is a perfect scenario for apply, because the arguments for the operation are already in a list.
main.lisp
Section titled “main.lisp”(defun calculate (operation numbers) "Applies a given operation to a list of numbers." (unless (listp numbers) (error "'numbers' must be a list.")) (apply operation numbers))
;; --- Using the calculate function ---
;; Calling with the symbol for the addition function(print (calculate '+ '(10 20 30)))
;; Calling with a lambda function(print (calculate (lambda (x y) (- x y)) '(100 25)))
;; Calling with a predefined function (works for any arity)(print (calculate #'max '(1 8 5 9 2)))Output
Section titled “Output”When you execute the code, it returns the following result:
60759Advanced Use Case: Argument Forwarding
Section titled “Advanced Use Case: Argument Forwarding”apply is essential for writing wrapper functions or macros that need to pass their arguments along to another function. This is often done in combination with &rest parameters in a function definition.
(defun logging-wrapper (function-to-call &rest args) "A wrapper that logs before and after calling a function with its arguments." (format t "--> Calling ~s with arguments ~s~%" function-to-call args)
;; 'args' is a list, so we must use APPLY to call the function. (let ((result (apply function-to-call args))) (format t "<-- Function ~s returned ~s~%" function-to-call result) result))
;; Using the wrapper(logging-wrapper #'+ 1 2 3)Output
Section titled “Output”--> Calling #<FUNCTION +> with arguments (1 2 3)<-- Function #<FUNCTION +> returned 6Key Considerations
Section titled “Key Considerations”#'Notation: Using#'(sharp-quote) is the modern, preferred way to get a function object (e.g.,#'+). It’s more explicit than using a bare symbol like'+.- Dynamic Dispatch:
applyandfuncallare the heart of dynamic dispatch in Lisp. They allow you to write flexible, generic code that decides which functions to call based on runtime conditions. - Error Handling: Be mindful that calling
applyorfuncallwith the wrong number or type of arguments for the target function will result in a runtime error, just like a direct call would.