What is dynamic scoping

Dynamic scoping is a programming language paradigm that you don’t typically see. The scoping that most programmers are used to is called lexical scoping. It’s found in Lua and many other languages. Lexical scoping is the dominant choice for a reason: it’s easy to reason about and understand just by looking at the code. We can see what variables are in scope just by looking at the structure of the text in our editor. Scoping controls how a variable’s value is resolved.

Dynamic scoping does not care how the code is written, but instead how it executes. Each time a new function is executed, a new scope is pushed onto the stack. This scope is typically stored with the function’s call stack. When a variable is referenced in the function, the scope in each call stack is checked to see if it provides the value.

An example

Using the syntax dynamic(var) to represent a dynamic scope variable lookup:

local function make_printer () local a = 100 return function () print ( "Lexical scoping:" , a ) print ( "Dynamic scoping:" , dynamic ( a )) end end local function run_func ( fn ) local a = 200 fn () end local print_a = make_printer () run_func ( print_a )

In this example we're priting a variable named a with each of the scoping styles. With lexical scoping it’s very easy to see that we've created a closure on the variable a . That variable is bound to the scope of print_a because the way the code blocks have been written nest the scopes.

With dynamic scoping things are a bit different. Each entry in the callstack represents a different scope to check for the variable a . Because there is no a defined in the function referencing it, we traverse up the call stack to find a declared variable. It’s found in the body of run_func , where the value is 200 .

The usefulness of this scoping may not be immediately clear. It may seem very error prone because the value of the variable we're requesting can come from any caller’s stack, even code that we haven’t event written.

The power of dynamic scoping is that we can inspect the calling context to control the behavior of our functions.

Implementing dynamic scoping

We can implement dynamic scoping fairly easily in Lua through the debug library. We'll mimic the example above with a function called dynamic that takes the name of a variable, as a string, to look up dynamically.

In Implementing setfenv in Lua 5.2, 5.3, and above we discovered how we could use debug.getupvalue to implement setfenv . For dynamic scoping we'll rely on the debug.getlocal function.

The signature of getlocal is debug.getlocal ([thread,] level, local) . In this example we're not concerned with the thread so we'll focus on level and local .

level is an integer that represents how many levels up the call stack we want to look for the variable we're searching for.

is an integer that represents how many levels up the call stack we want to look for the variable we're searching for. local is the index of that local variable we want to resolve, starting at 1.

The return value of this function is either nil if nothing was found, or the name and value of the variable.

To find a local variable in an higher up scope, we just need to keep incrementing level and querying each local variable by its numeric index until we find the matching name. Here’s the implementation:

function dynamic ( name ) local level = 2 while true do local i = 1 while true do local found_name , found_val = debug.getlocal ( level , i ) if not found_name then break end if found_name == name then return found_val end i = i + 1 end level = level + 1 end end

Now we can rewrite the example from the top of the post to use this function:

local function make_printer () local a = 100 return function () print ( "Lexical scoping:" , a ) print ( "Dynamic scoping:" , dynamic ( "a" )) end end local function run_func ( fn ) local a = 200 fn () end local print_a = make_printer () run_func ( print_a )

When to use dynamic scoping

In the general case, it’s probably best to avoid dynamic scoping since it makes code harder to understand at a glance. In any case, there are some situations where dynamic scoping is useful.