2

I was trying to understand the concept of dynamic/static scope with deep and shallow binding. Below is the code-

(define x 0)
(define y 0)
(define (f z) (display ( + z y))
(define (g f) (let ((y 10)) (f x)))
(define (h) (let ((x 100)) (g f)))
(h)

I understand at dynamic scoping value of the caller function is used by the called function. So using dynamic binding I should get the answer- 110. Using static scoping I would get the answer 0. But I got these results without considering shallow or deep binding. What is shallow and deep binding and how will it change the result?

Noober
  • 1,516
  • 4
  • 22
  • 48
  • is this question, [Dynamic Scoping - Deep Binding vs Shallow Binding](http://stackoverflow.com/q/1753186/1281433) useful? It was the first hit from a Google search for "shallow binding deep binding", by the way. The second was [Shallow & Deep Binding - What would this program print?](http://stackoverflow.com/q/15550648/1281433) – Joshua Taylor Dec 02 '15 at 22:18
  • 1
    I've heard of dynamic and lexical scope, but I don't recall ever hearing of "shallow" and "deep" binding. And I've been around for a good while, so I can't imagine I'm the only one. Maybe the question can provide some reference to where the concepts are coming from? – Luis Casillas Dec 03 '15 at 00:55
  • @LuisCasillas I actually had a bit of the same reaction, but a quick Google search does bring up some meaningful results pretty quickly. I don't think that rehashing those concepts is really OP's task here. (I say this after having had to look up these terms, too. I just think that they're mentioned in enough course notes, etc., that OP doesn't need to explain them any more than OP needs to explain lexical and dynamic binding to us.) – Joshua Taylor Dec 03 '15 at 13:50
  • Your nesting in `(define (f z) ...` is a little off. It's supposed to end after `(display (+ z y))`, right? – Joshua Taylor Dec 03 '15 at 14:19
  • @JoshuaTaylor that answer is wrong, as are others. – Will Ness Dec 17 '15 at 16:41
  • @LuisCasillas that's the stuff from the 70-s and 80-s. see http://www.cs.umd.edu/~hjs/pubs/SameJCL79.pdf for example. Deep or shallow, is implementation techniques, producing equivalent results when correctly implemented. cf [my old answer](http://stackoverflow.com/a/15014943/849891). – Will Ness Dec 17 '15 at 16:42
  • a bunch of relevant results show up when searching for "rerooting scheme" – Will Ness Dec 17 '15 at 16:48
  • @WillNess It wasn't a term I'd heard about much before (especially since one of the options is so uncommon in actual programming languages). I don't think the answers to the other questions were great, but did I get it right in [my answer](http://stackoverflow.com/a/34068222/1281433)? – Joshua Taylor Dec 17 '15 at 16:56
  • @JoshuaTaylor haven't read it yet, it's long. My linked answer is short, maybe you could read it and tell me if it's the same. :) and the 20 upvotes answer is plain wrong, too. – Will Ness Dec 17 '15 at 16:57
  • @WillNess In your answer, you say that a program won't be able to observe the difference between shallow binding and deep binding, but an abundance of resources online say that there *are* observable differences. As far as I can tell, the difference is that when a lambda expression is evaluated to produce a function object, deep binding gives the function object a permanent reference to the current dynamic environment (for resolving dynamically scoped variables), whereas in shallow binding (the more common option), the function object references the dynamic environment that is active when... – Joshua Taylor Dec 17 '15 at 17:05
  • they mean lexical and dynamic. their use of "shallow and deep" terminology is just wrong. "deep binding" is when you have a chain on env. frames. shallow is when you have just one frame. – Will Ness Dec 17 '15 at 17:08
  • ...that function object is invoked. So there'd definitely be observable differences. E.g., if you do (assuming that `let` is establishing dynamic bindings here) `(let ((x 10)) (let ((y (lambda () x))) (let ((x 12)) (display (y)))))`. With shallow binding, you'll see 12, but with deep binding, you'll see 10, because the dynamic environment that was active when the lambda expression is evaluated bound x to 10. – Joshua Taylor Dec 17 '15 at 17:09
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/98259/discussion-between-will-ness-and-joshua-taylor). – Will Ness Dec 17 '15 at 17:09

1 Answers1

2

There's an example in these lecture notes 6. Names, Scopes, and Bindings: that explains the concepts, though I don't like their pseudo-code:

thres:integer
function older(p:person):boolean
  return p.age>thres
procedure show(p:person, c:function)
  thres:integer
  thres:=20
  if c(p)
    write(p)
procedure main(p)
  thres:=35
  show(p, older)

As best I can tell, this would be the following in Scheme (with some, I hope, more descriptive names:

 (define cutoff 0)                      ; a 

 (define (above-cutoff? person)
     (> (age person) cutoff))

 (define (display-if person predicate)
     (let ((cutoff 20))                 ; b
       (if (predicate person)
           (display person))))

 (define (main person)
     (let ((cutoff 35))                 ; c
       (display-if person above-cutoff?)))
  • With lexical scoping the cutoff in above-cutoff? always refers to binding a.
  • With dynamic scoping as it's implemented in Common Lisp (and most actual languages with dynamic scoping, I think), the value of cutoff in above-cutoff?, when used as the predicate in display-if, will refer to binding b, since that's the most recent on on the stack in that case. This is shallow binding.
  • So the remaining option is deep binding, and it has the effect of having the value of cutoff within above-cutoff? refer to binding c.

Now let's take a look at your example:

(define x 0)
(define y 0)
(define (f z) (display (+ z y))
(define (g f) (let ((y 10)) (f x)))
(define (h) (let ((x 100)) (g f)))
(h)

I'm going to add some newlines so that commenting is easier, and use a comment to mark each binding of each of the variables that gets bound more than once.

 (define x 0)                           ; x0
 (define y 0)                           ; y0 
 (define (f z)                          ; f0 
     (display (+ z y)))
 (define (g f)                          ; f1
     (let ((y 10))                      ; y1
       (f x)))
 (define (h)
     (let ((x 100))                     ; x1
       (g f)))

Note the f0 and f1 there. These are important, because in the deep binding, the current environment of a function passed as an argument is bound to that environment. That's important, because f is passed as a parameter to g within f. So, let's cover all the cases:

  • With lexical scoping, the result is 0. I think this is the simplest case.
  • With dynamic scoping and shallow binding the answer is 110. (The value of z is 100, and the value of y is 10.) That's the answer that you already know how to get.
  • Finally, dynamic scoping and deep binding, you get 100. Within h, you pass f as a parameter, and the current scope is captured to give us a function (lambda (z) (display (+ z 0))), which we'll call ff for sake of convenience. Once, you're in g, the call to the local variable f is actually a call to ff, which is called with the current value of x (from x1, 100), so you're printing (+ 100 0), which is 100.

Comments

As I said, I think the deep binding is sort of unusual, and I don't know whether many languages actually implement that. You could think of it as taking the function, checking whether it has any free variables, and then filling them in with values from the current dynamic environment. I don't think this actually gets used much in practice, and that's probably why you've received some comments asking about these terms. I do see that it could be useful in some circumstances, though. For instance, in Common Lisp, which has both lexical and dynamic (called 'special') variables, many of the system configuration parameters are dynamic. This means that you can do things like this to print in base 16 (since *print-radix* is a dynamic variable):

(let ((*print-radix* 16))
  (print value))

But if you wanted to return a function that would print things in base 16, you can't do:

(let ((*print-radix* 16))
  (lambda (value)
    (print value)))

because someone could take that function, let's call it print16, and do:

(let ((*print-radix* 10))
  (print16 value))

and the value would be printed in base 10. Deep binding would avoid that issue. That said, you can also avoid it with shallow binding; you just return

(lambda (value)
  (let ((*print-radix* 16))
    (print value)))

instead.

All that said, I think that this discussion gets kind of strange when it's talking about "passing functions as arguments". It's strange because in most languages, an expression is evaluated to produce a value. A variable is one type of expression, and the result of evaluating a variable is the expression of that variable. I emphasize "the" there, because that's how it is: a variable has a single value at any given time. This presentation of deep and shallow binding makes it gives a variable a different value depending on where it is evaluated. That seems pretty strange. What I think would make much more sense is if the discussions were about what you get back when you evaluate a lambda expression. Then you could ask "what will the values of the free variables in the lambda expression be"? The answer, in shallow binding, will be "whatever the dynamic values of those variables are when the function is called later. The answer, in deep binding, is "whatever the dynamic values of those variables are when the lambda expression is evaluated."

Then we wouldn't have to consider "functions being passed as arguments." The whole "functions being passed as arguments" is bizarre, because what happens when you pass a function as a parameter (capturing its dynamic environment) and whatever you're passing it to then passes it somewhere else? Is the dynamic environment supposed to get re-bound?

Related Questions and Answers

Community
  • 1
  • 1
Joshua Taylor
  • 84,998
  • 9
  • 154
  • 353
  • 1
    I've found [a *1997* book on Perl](https://books.google.com/books?id=RPJloNVPQAAC&dq=python+deep+binding&q=deep+binding#v=snippet&q=deep%20binding&f=false) that uses this terminology too. the real problem is *closure creation time*; choosing one discipline makes the other unavailable to a programmer; a real solution would be making it explicit, as `close_over((x,y,...),proc_name)` or something -- so a programmer would pass either *that* or just the naked `proc_name` as a funarg (functional argument). As we talked in chat, in LISP this was done with `(function (lambda(...` or just `lambda`. – Will Ness Dec 29 '15 at 20:42
  • 1
    and [another book](https://books.google.com/books?id=jM-cBAAAQBAJ&dq=michael+scott+programming+languages+shallow+deep+binding&q=shallow+deep+binding#v=snippet&q=shallow%20deep%20binding&f=false) from 2015 (1st ed. 2005), which appears to have the pseudocode you refer to! :) (on pp 152-153). ((I still think this is just wrong and a misuse of terminology. :) )) – Will Ness Dec 29 '15 at 20:58