Dynamic scoping is a programming language paradigm that you don’t typicallysee. The scoping that most programmers are used to is calledlexicalscoping. It’s found in Lua and many other languages. Lexical scoping is thedominant choice for a reason: it’s easy to reason about and understand just bylooking at the code. We can see what variables are in scope just by looking atthe structure of the text in our editor. Scoping controls how a variable’svalue is resolved.
Dynamic scoping does not care how the code is written, but instead how itexecutes. Each time a new function is executed, a new scope is pushed onto thestack. This scope is typically stored with the function’scall stack. Whena variable is referenced in the function, the scope in each call stack ischecked to see if it provides the value.
Using the syntaxdynamic(var) to represent a dynamic scope variable lookup:
localfunctionmake_printer()locala=100returnfunction()print("Lexical scoping:",a)print("Dynamic scoping:",dynamic(a))endendlocalfunctionrun_func(fn)locala=200fn()endlocalprint_a=make_printer()run_func(print_a)-- prints:-- Lexical scoping: 100-- Dynamic scoping: 200In this example we're priting a variable nameda with each of the scopingstyles. With lexical scoping it’s very easy to see that we've created a closureon the variablea. That variable is bound to the scope ofprint_a becausethe way the code blocks have been written nest the scopes.
With dynamic scoping things are a bit different. Each entry in the callstackrepresents a different scope to check for the variablea. Because there is noa defined in the function referencing it, we traverse up thecall stack tofind a declared variable. It’s found in the body ofrun_func, where the valueis200.
The usefulness of this scoping may not be immediately clear. It may seem veryerror prone because the value of the variable we're requesting can come fromany caller’s stack, even code that we haven’t event written.
The power of dynamic scoping is that we can inspect the calling context tocontrol the behavior of our functions.
We can implement dynamic scoping fairly easily in Lua through thedebuglibrary. We'll mimic the example above with a function calleddynamic thattakes the name of a variable, as a string, to look up dynamically.
InImplementing setfenv in Lua 5.2, 5.3, and abovewe discovered how we could usedebug.getupvalue to implementsetfenv. For dynamicscoping we'll rely on thedebug.getlocal function.
The signature ofgetlocal isdebug.getlocal ([thread,] level, local). Inthis example we're not concerned with the thread so we'll focus onlevel andlocal.
level 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 eithernil if nothing was found, or thename and value of the variable.
To find a local variable in an higher up scope, we just need to keepincrementinglevel and querying each local variable by its numeric indexuntil we find the matching name. Here’s the implementation:
functiondynamic(name)locallevel=2-- iterate overwhiletruedolocali=1-- iterate over each local by indexwhiletruedolocalfound_name,found_val=debug.getlocal(level,i)ifnotfound_namethenbreakendiffound_name==namethenreturnfound_valendi=i+1endlevel=level+1endendNow we can rewrite the example from the top of the post to use this function:
localfunctionmake_printer()locala=100returnfunction()print("Lexical scoping:",a)-- notice we pass in "a" hereprint("Dynamic scoping:",dynamic("a"))endendlocalfunctionrun_func(fn)locala=200fn()endlocalprint_a=make_printer()run_func(print_a)In the general case, it’s probably best to avoid dynamic scoping since it makescode harder to understand at a glance. In any case, there are some situationswhere dynamic scoping is useful.
DSLs, where terseness is important, canbenefit from dynamic scoping by using the implicit context of function calls tomake arguments available that haven’t been explicitly passed. A basic examplewould be removing the need to passself as an argument if it can be fetchedfrom the containing scope.
leafo.net · Generated Sun Oct 8 13:02:35 2023 bySitegenmastodon.social/@leafo