Arc Forumnew | comments | leaders | submitlogin
1 point by Pauan 4440 days ago | link | parent

  ($let ((env1 (get-current-environment)))
    ...
    ($vau foo env2
      ...
      ($let ((env3 (get-current-environment)))
        ...))
In the above, env1 is the lexical scope where the $vau is defined. It does not include bindings for foo, env1, env2, or env3, but contains all the bindings on the outside of the $let, where the $vau is defined.

env2 is the dynamic scope where the $vau is called, it does not include bindings for foo, env1, env2, or env3.

env3 is the environment of the $vau itself. This environment inherits from env1, and thus is lexical just as env1 is. And so, it contains all the bindings of env1, in addition to bindings for foo, env1, and env2, but not env3.

So, by using this combination of lexical and dynamic scope, you can express a huge variety of different things in Kernel.

---

Let me explain via a more elaborate example:

  ($let ((global (get-current-environment)))
    (display (eval 'x global))
    
    ($set! global $foo
      ($vau (x) env
        (display (eval 'x env))
        (display (eval 'env env))
        
        ($let ((inner (get-current-environment)))
          (display (eval 'x inner))
          (display (eval 'env inner))
          (display (eval 'global inner))))))
          
  ($let ((x 10))
    ($foo 20))
First, we use $let to bind the variable 'global to the global environment using get-current-environment. Then we try to evaluate the symbol 'x in that environment. The variable 'x of course does not exist in the global environment, so it throws an error.

Then, we create a $vau form and assign it to the variable $foo in the global environment. This is the same as saying ($define! foo ...) at the top level, but is necessary when inside a $let.

Now, at the bottom, we use $let to bind the variable 'x to 10 and then call $foo with the single argument 20. First, $foo tries to evaluate the variable 'x in the dynamic environment "env". This should display 10, because it's using the binding in the $let where $foo is called.

We then evaluate the variable 'env in the $vau's dynamic environment. But the variable 'env does not exist in the dynamic environment, so it throws an error.

Then, we use a $let inside the $vau to grab a hold of the $vau's inner environment and bind it to the variable 'inner. We then evaluate the variable 'x in the $vau's inner environment. This should display 20, because it's using the binding inside the $vau, which is bound to the $vau's argument 'x which was 20 when calling the $vau.

We then evaluate the variable 'env in the $vau's inner environment, which evaluates to the dynamic environment, because inside the $vau, the variable 'env is bound to the dynamic environment.

Lastly, we evaluate the variable 'global in the $vau's inner environment. This evaluates to the global environment because the $vau's inner environment inherits from the top-level $let where the variable 'global is bound.

--

Here is a picture showing the environments in the above code:

http://img42.imageshack.us/img42/1870/vauenvironments.png

The global environment is pink, the first blue environment is the $let's environment that inherits from the global environment, plus a binding for the variable 'global. The green is the $vau's environment which inherits from the $let's environment, plus a binding for the variables 'x and 'env. And the yellow is the $let's environment that inherits from the $vau's environment.

Lastly, the second blue environment is the dynamic environment that is bound to the 'env variable inside the $vau when it is called.



1 point by akkartik 4440 days ago | link

Thanks. So what Kernel calls the dynamic environment is identical to caller-scope, right?

I think I find this confusing because when I hear 'dynamic environment' I keep thinking of dynamic scope (special variables in common lisp). If we only create bindings using let and combiner parameters, the dynamic environment is just the caller's static environment, is that right?

-----

1 point by Pauan 4440 days ago | link

Yes, it is identical to caller-scope.

In Kernel, the dynamic environment is exactly analogous to dynamic scope. The difference is that lexical scope is the default, and you have to explicitly ask to evaluate things with dynamic scope, rather than earlier Lisps which used dynamic scope by default and didn't even have lexical scope.

Special variables in Common Lisp are similar to dynamic scope, except you're flagging only certain variables to be dynamic, whereas the dynamic environment in Kernel lets you evaluate any variable with dynamic scope.

---

"If we only create bindings using let and combiner parameters, the dynamic environment is just the caller's static environment, is that right?"

Yes. In fact, in Kernel, $let is defined just like it is in Arc, using $lambda. And $lambda uses $vau, and $vau is the only way to create new environments in Kernel, as far as I know. So, environments are all about $vau.

---

By the way, I just edited the post you're replying to, and added a picture.

-----

2 points by Pauan 4440 days ago | link

In fact, I like to think of environments in a similar way as continuations. Consider this, for instance:

  ($define! $foo
    ($vau (x) env
      (list x env)))
      
  ($define! $bar
    ($vau (x) env
      ($foo x)))
      
  ($bar 10)
The above should return a list containing two elements: the number 10 and $bar's environment. The way I think of it is like this:

  ($define! $bar
    ($vau (x) env
      ($foo x env)))
That is, the $bar fexpr implicitly passes its own internal environment to the $foo fexpr. And $let's work this way as well, so that this:

  ($let ((x 10))
    ($bar x))
Would be equivalent to this:

  ((wrap ($vau (x) env
           ($bar x env)))
   10)
So it's really all about $vau's passing their own internal environment whenever they call something else, similar to the idea of all functions implicitly passing their own continuation when calling something else.

-----

1 point by akkartik 4440 days ago | link

Very cool. While struggling with the $vau concept I thought about whether I could build lazy eval directly using $vau, without first building force and delay (SICP 4.2). But it's not quite that powerful, because lazy eval also needs the ability to stop computing the cdr of a value. Your intuition tying $vau to continuations is a similar you-see-it-if-you-squint-a-little correspondence.

-----

1 point by Pauan 4440 days ago | link

I somewhat remember either the Kernel Report or John's dissertation mentioning the similarities between continuations and environments, but I might be mistaken on that...

---

"I thought about whether I could build lazy eval directly using $vau"

Directly? Not sure, but ยง9.1.3 of the Kernel Report does show how to build promise?, memoize, $lazy, and force as a library using existing Kernel features.

-----

1 point by akkartik 4440 days ago | link

"Special variables in Common Lisp are similar to dynamic scope, except you're flagging only certain variables to be dynamic, whereas the dynamic environment in Kernel lets you evaluate any variable with dynamic scope."

Ah! I considered this approach when I was building dynamic variables in wart, but it seemed less confusing to attach the scope to the binding. Wart is unlike common lisp in this respect. SBCL:

  * (defvar foo 34)
  * (defun bar() (foo))
  * (bar)
  34
  * (let ((foo 33)) (bar))
  33 ; foo is still dynamic. WEIRD.
But:

  wart> = foo 34
  wart> def bar() foo.
  wart> bar.
  34
  wart> let foo 33 bar.
  34 
  wart> making foo 33 bar.
  33 ; dynamic scope
I'm starting to appreciate that $vau and first-class environments give the programmer a lot more power (aka rope to hang himself with :) than caller-scope.

-----

1 point by Pauan 4440 days ago | link

"foo is still dynamic. WEIRD."

The reason for this is that Common Lisp explicitly states that if you use "let" on a dynamic variable, it dynamically changes that variable for the scope of the "let". So Common Lisp combines both "let" and Racket's "parameterize" into a single form, rather than keeping them separate.

-----

2 points by akkartik 4440 days ago | link

Yes, of course. I prefer racket's approach in this case.

-----

1 point by akkartik 4440 days ago | link

I'm struggling to wrap my head around the idea that you can define variables to have dynamic scope using just $vau or caller-scope..

-----

1 point by Pauan 4440 days ago | link

You don't define variables to have dynamic scope. They automatically have dynamic scope when you evaluate them in the dynamic environment:

  ($vau () env
    (eval 'x env))
The above, when called, will evaluate the variable 'x in whatever scope it was called in. Thus, the above $vau has just "made" the variable 'x dynamic. But of course this ability to evaluate things in the dynamic scope is only available to fexprs.

---

If you're talking about something similar to Racket's parameterize, you should be able to build that using dynamic-wind. Then something like this...

  ($parameterize ((foo 10))
    ...)
...would be equivalent to this:

  ($let ((env   (get-current-environment))
         (orig  foo))
    (dynamic-wind ($lambda ()
                    ($set! env foo 10))
                  ($lambda ()
                    ...)
                  ($lambda ()
                    ($set! env foo orig))))
---

In fact, I'm gonna write a $parameterize form right now:

  ($define! $parameterize-redirect
    ($vau (target tree . body) env
      ($let ((inner (get-current-environment))
             (env   (eval target env)))
        (dynamic-wind
          ($lambda ()
            ($set! inner orig
              (map ($lambda ((var expr))
                     (list var (eval var env)))
                   tree))
            (for-each ($lambda ((var expr))
                        (eval (list $define! var expr) env))
                      tree))
          ($lambda ()
            (eval (cons $sequence body) env))
          ($lambda ()
            (for-each ($lambda ((var expr))
                        (eval (list $define! var expr) env))
                      orig))))))
                      
  ($define! $parameterize
    ($vau (tree . body) env
      (eval (list* $parameterize-redirect env tree body) env)))
Warning: untested and may not work. However, if it does work then it will work for all variables, global or local, without declaring them as dynamic before-hand. So this will work:

  ($let ((x 5))
    ... x is 5 ...

    ($parameterize ((x 10))
      ... x is 10 in here ...
      )

    ... x is 5 again ...
    )
The catch is that in order to parameterize global variables, the $parameterize form itself needs to be at the top level, because it's mutating the immediate ancestor environment.

If you want to parameterize something other than the immediate environment, use $parameterize-redirect which accepts an environment as the first argument, and then evaluates in that environment instead.

-----