Reynolds' observation was that a given program contains only finitely many function abstractions, so that each can be assigned and replaced by a unique identifier.
In such situations, defunctionalization must be preceded by closure conversion (lambda lifting), so that any free variables of a function abstraction are passed as extra arguments to apply.
In addition, if closures are supported as first-class values, it becomes necessary to represent these captured bindings by creating data structures.
Alternatively, the target language may support indirect calls through function pointers, which may be more efficient and extensible than a dispatch-based approach.
Besides its use as a compilation technique for higher-order functional languages, defunctionalization has been studied (particularly by Olivier Danvy and collaborators) as a way of mechanically transforming interpreters into abstract machines.