scopelanguage-agnosticlexical-scopedynamic-scope

Why is there an error in this Dynamic Scoping example?


I have been sifting through the web to understand how dynamic scoping works and I feel like I had got it down. But when I was asking Bing AI about its definition, I got the following pseudocode example where it stated that the following pseudocode for dynamic scoping should print x = 30.

x = 10
function f() {
  x = 20
  function g() {
    print x
  }
  g()
}
function h() {
  x = 30
  f()
}
h()

Now I think it should be 20 because in dynamic scoping, you look at the call stack for the value of the variable. In this case, the call stack should have been h()->f()->g() and the value of x is most recently defined in f() which is 20. Therefore, it should have printed 20. However, bing ai is adament that is should be 30 no matter what I state. Now I am doubting myself and wondering if I have misunderstood dynamic scoping. Where am I doing wrong in this example?


Solution

  • So far, you have input from two "AI"s (Bing and ChatGPT) that disagree with each other.

    I agree with your belief that the result should be 20.

    One way to confirm this is to translate your code into a language that supports dynamic scoping, and then run it to see what you get.

    Common Lisp usually uses lexical scoping, but you can create dynamically scoped variables with defvar. So, here's about as close as I can get to a verbatim translation of your pseudo-code to Common Lisp syntax:

    (defvar x 10)
    
    (defun f()
        (setf x 20)
        (defun g()
            (print x)
        )
        (g)
    )
    
    (defun h()
        (setf x 30)
        (f)
    )
    
    (h)
    

    And yes, when you run this, it prints out 20, as expected.

    Personally, I think the easiest way to look at dynamic scoping is to just think of a dynamically scoped variable as being like a global variable in something like C. Anything that reads from it simply sees the value that was most recently written. The usual explanations about looking at the call stack to find the most recently assigned value can lead to rather deceiving results. In particular, after you've returned from a function, so there is no longer any call stack to look at, it will still retain the last value assigned to it. For example, consider modifying h to print out the value after the call to f returned:

    (defun h()
        (setf x 30)
        (f)
        (print x)
    )
    

    If we looked at the call stack at the point of this call to print, the most recent assignment we'd see would be the setf x 30, so if we looked at the call stack, we'd assume this should print out 30. But in fact, it prints out 20, because that was the value most recently assigned. We could get the right answer if we thought of it less like a call stack than a simple log of assignments though.