This is not true. You can have first-class function which are not closures: every dynamically-scoped Lisp works that way, see Emacs Lisp without `lexical-binding: t` and the `lexical-let` implementation.
(setq dx .0001)
(defun deriv (f)
(lambda (x)
(/ (- (funcall f (+ x dx)) (funcall f x))
dx)))
(defun cube (x) (* x x x))
(setq cube-deriv (deriv #'cube))
(funcall cube-deriv 2)
Error: Symbol's value as variable is void: f
Setting lexical-binding to t, so that deriv returns a closure, fixes the problem.The blog post author marveled that the compiler could optimize all tail-calls, which was a new emphasis of Scheme (earlier Lisps only did a best-effort job), and which required e.g. changing the function calling conventions.
He appreciated how the definition of derivatives looks like the mathematical definition; but it only looks this clean because of Scheme's choice to have a single namespace for functions and values (1-Lisp versus 2-Lisp), so we get rid of elisp's setq/funcall/#' cruft.
And he wondered if the substitution model (which lets you think about programs like high school algebra, instead of modelling the execution in detail) could really always work. But it only works because of Scheme's choice to use lexical binding and closures.
All of these were difficult design problems at the time, and different Lisps explored different choices. We really have Scheme to thank for a lot of programming language concepts which we take for granted nowadays.
Are you saying that in general, closures are essential to first-class functions? Or are you saying that, in the derivative example, there's something going on with closures?
You may say that the difference is that in Lisp, deriv-cube is a partial application, whereas in C you really can't do that. And you'd be right. But for this example, I don't see what difference it makes.
Note: I'm not saying that C could express closures and first-class function objects anywhere near as neatly as Lisp. But the idea that only Lisp allowed you to use them, and C was the land of straightforward procedural code... does not represent what I saw back then.
Just as you can write FORTRAN code in any language, you can transfer ideas from other languages to C. What's great about Lisp is that it made the ideas easily accessible.
#define DX (double)0.0001
double f_prime(f, x)
register double (*f)();
double x;
{
return ((*f)(x + DX) - (*f)(x)) / DX;
}
#define DERIV(F, X) f_prime((F), (double)(X))
double cube(x)
double x;
{
return x * x * x;
}
#define DERIV_CUBE(X) DERIV(cube,(X))
main()
{
printf("%f\n%f\n%f\n", DERIV_CUBE(2), DERIV_CUBE(3), DERIV_CUBE(4));
} #include <stdio.h>
double deriv(double (*f)(double), double x)
{
static const double dx = 1e-8;
return (f(x + dx) - f(x)) / dx;
}
static double cube(double x)
{
return x * x * x;
}
double deriv_cube(double x)
{
return deriv(cube, x);
}
int main(void)
{
printf("%f\n%f\n%f\n", deriv_cube(2), deriv_cube(3), deriv_cube(4));
return 0;
}