9
$\begingroup$

In dynamic scoping, if a function accesses a variable in a higher scope, it does so relative to the location it's called rather than the location it's defined. For example:

function a() {
    var b = 1;

    return () -> b;
}

{
    var b = 2;
    var c = a();

    return c();
}

In a typical lexically scoped language, c would be a closure, returning 1. However, in a dynamically scoped language, it instead returns 2, the value of b in the scope where c is called.

What are the pros and cons of dynamic scoping?

$\endgroup$
5
  • $\begingroup$ Do you know any concrete languages that use dynamic scoping? $\endgroup$
    – mousetail
    May 17, 2023 at 7:17
  • 2
    $\begingroup$ Sure thing @mousetail ! :D Dynamic Variables in Common Lisp, "parameter objects" in Scheme and Racket, also Clojure I vaguely remember had something similar too. :) $\endgroup$ May 17, 2023 at 8:53
  • 1
    $\begingroup$ Also this in javascript is dynamically scoped. $\endgroup$ Jun 1, 2023 at 17:31
  • 1
    $\begingroup$ Tcl has upvar. $\endgroup$
    – user23013
    Jul 3, 2023 at 12:54
  • $\begingroup$ @user23013 This better not be the setup to an updog joke :p $\endgroup$ Jul 8, 2023 at 1:06

7 Answers 7

12
$\begingroup$

Dynamic scoping is fun and weird! That's about it for the pros. Well, also it's relatively easy to implement in an interpreter, because you don't have to do lexical resolution of names; names are resolved by walking the call stack instead of the source tree, and you already have a call stack. In practice this means your stack frames just each have a dictionary of names instead of a more complicated parent-pointer-tree of dictionaries of names. My first language, fffff, had dynamic scopes partly for this reason.

The cons are that dynamic scoping is weird and nobody is used to writing programs with it. There are almost no mainstream programming languages which use dynamic scoping; even Wikipedia only lists Logo, Emacs Lisp, LaTeX and some shell languages, and none of these are general-purpose programming languages.

Dynamic scoping makes it pretty difficult to reason about programs, particularly if you have higher-order functions. Consider the following example:

def call_me_maybe(f):
    p = random()
    if p < 0.5:
        f()

x = 23
call_me_maybe(() => print(x));

This program has a 50% probability of printing the number 23. But if we refactor the call_me_maybe function to change the name of the local variable p to x, then suddenly the program instead has a 50% probability of printing a floating-point number between 0 and 0.5. This is because the local variable in the call_me_maybe stack frame shadows the one from the root stack frame.

So the behaviour of a piece of code like this can't be determined without knowing even the names of the local variables of all the functions that might be called! This is a pretty severe violation of encapsulation, the principle that one piece of code shouldn't depend on the irrelevant internal details of another piece of code.


So this is pretty much why sensible languages don't use dynamic scoping. That said, there is some benefit in having something resembling dynamic scope for individual variables on an opt-in basis, in a language which is otherwise lexically scoped. Scala, for example, has this for function parameters, as does my language Papyri:

@fn greet($greeting: implicit str) $name: str -> {
    $greeting, $name!
}

@fn say_hi $name: str -> {
    @implicit(greeting=`Hi`)
    @greet $name
}

@say_hi `Andrew`
# result: Hi, Andrew!

An implicit parameter will be passed automatically when the function is called, with the argument value taken from a (lexically-scoped) variable of the same name, as long as that variable is also labelled as implicit. So this is still lexical scoping because the name $greeting in the first function refers to a different variable than the same name in the second function, but it behaves similar to dynamic scoping because the value is passed automatically along the call stack.

$\endgroup$
7
$\begingroup$

Dependency Injection

Things like logging become incredibly easy with dynamic scoping, since if a log function is in scope for your main function, anything your main function calls will automatically have access to log. This has some pros and cons itself.

Pros:

  • No need to pass tons of arguments to functions, or create unnecessary closures, in order to do dependency injection
  • Saves typing

Cons:

  • There's no easy way to know what dependencies a function expects it will have
    • Error messages resulting from this could be difficult to read, since it would be unclear whether it's a bug in the library, or a failure on your part to define a variable in an enclosing scope
$\endgroup$
5
$\begingroup$

The use of dynamic scoping makes code less self-contained; with lexical scoping all uses of a variable are logically kept together whereas with dynamic scoping it's hard to tell what uses what, making it more difficult to refactor code (such as by renaming a variable).

$\endgroup$
3
$\begingroup$

Universal dynamic scope has a lot of well-travelled drawbacks, but in limited circumstances, especially when it's opt-in, it can be a powerful tool for flexibly extending the language for specific purposes.

Dynamic scoping can enable the easy creation of embedded domain-specific languages when the language has suitable syntax and semantics otherwise: define names, constructs, and functions of the language within a scope, and allow a piece of code provided from outside as a function pointer or lambda to run in the context of that scope. In fact, it can enable exactly the kind of thing this site is about!

For example, while not generally dynamically scoped, Ruby allows evaluating a block of code within the scope of a given object using the instance_eval method. This is used for very widely-used DSLs, like the configuration in every Rails project and RubyGem, Chef, and Rake. When evaluated, the block of code will see the methods of that object, and any unqualified names will be looked up there first:

class DSL
    def hello
        puts "Hello"
    end
    def world
        puts "world"
    end
end
DSL.new.instance_eval { hello; world }

This is an opt-in dynamic scoping behaviour, and it's extremely widely used. Ruby provides dynamic overriding of most object features, so these DSLs can be quite powerful and somewhat different in semantics from the host language.

I wouldn't want to use a language that was all dynamic scoping, all the time, but the ability to run this bit of code in this outer scope is really useful, and points in the design space in between Ruby and full dynamic scoping seem like they would be useful in some circumstances too.

$\endgroup$
2
$\begingroup$

Let's say we're mapping over an array in an interpreted language, calling a function on each element. With lexical scoping, we'd have to define the function inside the loop, incurring an overhead each time around the loop. A dynamic language could define the function outside the loop and still have it access parameters set up inside the loop.

$\endgroup$
1
$\begingroup$

Several good answers already, but I would put it even more strongly. Dynamic scoping makes it impossible to tell what a program means at compile time.

I don't want to discourage anyone from dynamic scoping if that's what your dynamic language needs, but it makes things much harder for compilation, including JIT compilation.

$\endgroup$
1
$\begingroup$

Memory Usage

With dynamic scoping, variables would still be available out of 'scope,' so they would be kept in memory for longer, potentially leading to memory issues depending on what is stored in them. With lexical scoping, variables are more effectively deleted once their are no longer needed.

$\endgroup$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .