CS 331 Spring 2025 > Reflection in Programming
CS 331 Spring 2025
Reflection in Programming
This is a very short introduction to reflection in computer programs and its relationship to properties of programming languages.
What It Is
Reflection in a computer program refers to the ability of the program to deal with its own code: examining the code, looking at its properties, possibly modifying it, and executing the modified code.
In practice, reflection is largely a matter of support from the programming language and associated runtime environment. If these allow a program to examine and modify its code, and then execute the modified code as part of the execution of the same program, then that programming language will probably offer good support for reflection.
Thus, an important property of a programming language is whether, and how well, it supports reflection.
History
The earliest programming languages were akin to today’s machine code (or its human-readable form, assembly language). These programming languages make little distinction between executable code and data; both are simply sections of memory. Thus they usually allow a program to examine all of its own code and transform it in arbitrary ways.
Code that takes advantage of this capability said to be self-modifying. Self-modifying machine-code has popped up from time to time; nowadays, it is generally frowned on in production software development.
In contrast, when high-level programming languages first became available, most of them had no support for reflection at all. The major exception was the programming language Lisp, first developed in the late 1950s. We will say more about Lisp shortly.
Reflection was first named and studied in the early 1980s. Since then, various programming languages have included some support for reflection—often in very limited ways.
Support in Programming Languages
In today’s programming world, programming languages like C and C++ offer essentially no support for reflection. Haskell is similar.
However, dynamic programming languages usually have at least some reflection support.
Consider Lua.
It allows for examination of types at runtime.
In particular,
the built-in function type
returns a string giving the type of its argument.
[Interactive Lua]
> n = 42 > type(n) number > if type(n) == "string" then print("Yes") else print("No") end No
Furthermore, when we write the equivalent of an object or class in Lua, we can list all of its members at runtime; we can also add and delete members at runtime.
On the other hand, Lua does not allow for runtime examination of a function’s code.
Forth (1994 ANSI Standard) does allow such examination,
albeit in a limited manner.
The Forth word see
prints the code of the word that follows it.
[Interactive Forth]
see .s : .s .\" <" depth 0 .r .\" > " depth 0 max maxdepth-.s @ min dup 0 ?DO dup i - pick . LOOP drop ; ok
The above shows the Forth source code
for the built-in Forth word .s
.
However, Forth still does not give us facilities
for transforming this code at runtime.
Now we return to Lisp. Since its introduction in the late 1950s, Lisp has evolved into a family of related programming languages: Common Lisp, Scheme, EMACS Lisp, Clojure, Logo, and others. This family is the gold standard for reflection support.
The primary data structure used by Lisp-family programming languages is a kind of binary tree. Executable code can be stored in this structure. This allows code to be made available to a program in the form of an abstract syntax tree, which can be transformed arbitrarily and then executed. In Lisp-family programming languages, writing these code transformations is a normal part of programming; constructs called macros make such programming convenient.
We will see examples of Lisp-family reflection support when we cover the programming language Scheme.