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:

javascript
    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?

python
    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.

python
    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:

python
    type: knight
    color: white
    position: g1
    hasMoved: false

This seems trivial, but notice: we printed all fields without knowing what they were in advance. The same code works for any object:

python
    function describe(obj):
        for field in keys(obj):
            print(field + ": " + get(obj, field))
    
    describe(knight)      // works
    describe(pawn)        // works  
    describe(gameState)   // works for anything

Dynamic Field Access

Reading fields dynamically unlocks generic programming:

python
    // 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:

javascript
    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.

python
    function copyObject(obj):
        result = {}
        for field in keys(obj):
            set(result, field, get(obj, field))
        return result

This 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.

python
    keys(obj)              // what fields exist?
    typeOf(value)          // what type is this?
    isFunction(f)          // is this callable?

Behavioral reflection: Examine and invoke behavior.

python
    methods = getMethods(obj)           // what can this do?
    invoke(obj, "moveTo", ["e4"])       // call method by name

Generative reflection: Create new structures and behaviors.

    newType = createClass("Knight", {
        fields: ["position", "color"],
        methods: { move: moveFunction }
    })

Modificational reflection: Change existing structures.

python
    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.

javascript
    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.