If a function accepts and/or returns a function, it is called a higher-order function (HOF). For inexperienced programmers, coming from C, C++, or Java, higher-order functions sound like magic, but they are very simple. Imagine a simple function that returns the result of 2 + 3:
(define (foo) (+ 2 3)) ;; (foo) => 5
That's a boring function, it always adds 2 to 3. What if we generalize it, so that it adds 2 not only to 3, but to any user-provided number?
(define (foo n) (+ 2 n)) ;; (foo 10) => 12
When a language doesn't support higher-order functions, you are forced to think that functions and values (e.g. numbers, booleans, lists) are 2 distinct things. But functional programming (FP) blurs distinction between them. Imagine that the only difference between a function and a value is that a function can be called, other than that you can do to a function whatever you could to a 2
or #t
or '(a b c)
: you could give it as an argument, or return from a function, or store in a variable, or put it in a list. For example, let's generalize our little function further, so not only it could add 2 to n
, but multiply 2 by n
, or apply any other function that would accept two numbers:
(define (foo f n) (f 2 n))
;; (foo + 10) => 12
;; (foo * 10) => 20
;; (foo expt 10) => 1024
When you realize that a function can be treated the same way a number or a string is treated, anonymous functions (called “lambdas” in FP jargon) make complete sense. Anonymous functions are actually more basic and “normal” than ordinary named functions, named functions are just anonymous functions put into a variable, just like we put a number into a variable to use it multiple times.
(+ 2 2) ;; is no different from:
(let ((a 2)) (+ a a))
(lambda (x y) (* x y)) ;; is no different from:
(define (foo x y) (* x y)) ;; which is an abbreviation for:
(define foo (lambda (x y) (* x y))).
So HOFs allow us to generalize our functions to make them super-flexible. If you look at your function, see the logic behind it, you can realize, that if something operates on your data, then something else could probably too. If you add 2 numbers together, you could probably multiply them, or subtract, or exponentiate one to another, or whatever. Instead of writing a new function for every case each time, you could just accept an additional parameter, which has to be a function.
In FP we use HOFs all the time, for example, when manipulating lists. 3 functions are bread and butter of FP: map
, filter
and foldl
. map
accepts a function with 1 argument, applies this function to every element of a list, and returns a new list with changed elements. filter
accepts a predicate (function that returns a boolean) with 1 argument, applies the predicate to every element of a list, and returns a new list with elements that do not satisfy the predicate removed.
(map (lambda (n) (+ n 1)) '(1 2 3 4 5) ;; '(2 3 4 5 6)
(define (foo n) (+ n 1))
(map foo '(1 2 3 4 5)) ;; '(2 3 4 5 6)
(filter (lambda (n) (> n 3)) '(1 2 3 4 5)) ;; '(4 5)
(define (bar n) (> n 3))
(filter bar '(1 2 3 4 5)) ;; '(4 5)
Imagine, you have a list of 1-arity functions—again, you can do whatever you want with a function, and store it in a data structure too—and you want to apply all of them to the same number, and get a list of results.
(let ((xs (list (lambda (x) (+ x 1))
(lambda (x) (* x 2))
(lambda (x) (- x)))))
(map (lambda (f) (f 10)) xs)) ;; => (11 20 -10)
Conclusion: when a programming language properly supports functional programming concepts, higher-order functions allow flexibility and generality, which makes your code both more powerful (you can use the same function for various use-cases) and concise (no need to write 10 versions of one function). Some higher-order functions are used heavily in functional programming, so you get rid of low-level and verbose for-loops and write one-liners that do everything instead.
Note: foldl
, which is the same as “left fold” or “left reduce”, is even more powerful. If you're really interested and have time, please read the first half of my answer using reduce. While it isn't written for Scheme/Racket, but instead for Common Lisp/Emacs Lisp, you can still understand the idea behind fold/reduce.