The Meta-Level
What happens when a program can see itself? Not just execute, but examine its own structure---inspect its functions, query its types, modify its behavior at runtime. This is reflection: the ability of code to turn inward and contemplate its own nature.
We've traveled far in this book. We learned to manipulate data with functions. We learned to treat functions as data. Now we take another step up the ladder of abstraction: we write code that manipulates code.
Operating On vs Operating Within
Consider a simple function:
function double(x):
return x * 2 We can call it: double(5) returns 10. We can pass it: map(numbers, double). But can we examine it?
name = functionName(double) // "double"
params = parameters(double) // ["x"]
body = sourceCode(double) // "return x * 2"This is the meta-level. We're no longer operating within the program---we're operating on the program. The code itself becomes data.
Examining Objects
The most common reflection: asking an object about its structure.
knight = {
type: "knight",
color: "white",
position: "g1",
hasMoved: false
}
// What fields does this object have?
fields = keys(knight) // ["type", "color", "position", "hasMoved"]
// What are their values?
for field in fields:
print(field + ": " + get(knight, field))Output:
type: knight
color: white
position: g1
hasMoved: falseThis seems trivial, but notice: we printed all fields without knowing what they were in advance. The same code works for any object:
function describe(obj):
for field in keys(obj):
print(field + ": " + get(obj, field))
describe(knight) // works
describe(pawn) // works
describe(gameState) // works for anythingDynamic Field Access
Reading fields dynamically unlocks generic programming:
// Get a specific field by name (as a string)
fieldName = "position"
value = get(knight, fieldName) // "g1"
// Set a field by name
set(knight, "position", "f3")
// Check if a field exists
if hasField(knight, "hasMoved"):
print("This piece tracks movement")Why is this useful? Consider serialization---converting an object to JSON:
function toJSON(obj):
result = "{"
fields = keys(obj)
for i, field in enumerate(fields):
value = get(obj, field)
result += '"' + field + '": ' + valueToJSON(value)
if i < length(fields) - 1:
result += ", "
result += "}"
return result
toJSON(knight)
// '{"type": "knight", "color": "white", "position": "g1", "hasMoved": false}'The Power of Not Knowing
There's something paradoxical here. We write code that works precisely because it doesn't know the specific structure it's operating on. The ignorance is the feature.
function copyObject(obj):
result = {}
for field in keys(obj):
set(result, field, get(obj, field))
return resultThis function copies any object. It doesn't know about knights or pawns or game states. It operates at the meta-level, where all objects are just collections of fields.
Levels of Reflection
Languages offer different depths of introspective power:
Structural reflection: Examine the shape of data.
keys(obj) // what fields exist?
typeOf(value) // what type is this?
isFunction(f) // is this callable?Behavioral reflection: Examine and invoke behavior.
methods = getMethods(obj) // what can this do?
invoke(obj, "moveTo", ["e4"]) // call method by nameGenerative reflection: Create new structures and behaviors.
newType = createClass("Knight", {
fields: ["position", "color"],
methods: { move: moveFunction }
})Modificational reflection: Change existing structures.
addMethod(Knight, "castle", castleFunction)
replaceMethod(Knight, "move", newMoveFunction)The Mirror's Edge
Reflection creates a peculiar situation: the observer and the observed are part of the same system. When you examine a function's source code, the examining code is itself source code that could be examined.
function inspector(f):
return sourceCode(f)
// What is the source code of inspector?
inspector(inspector)
// "function inspector(f): return sourceCode(f)"This self-reference is the mirror of the chapter's title. Code gazing at itself, potentially infinitely deep.
We've glimpsed the meta-level---code examining code. But examination is just the beginning. In the next chapter, we'll put reflection to work: transforming objects, filtering fields, comparing structures. The mirror becomes a tool.