Arc Forumnew | comments | leaders | submitlogin
1 point by akkartik 4513 days ago | link | parent

The key quote seems to be: "because environments are immutable, this has exactly the same shadowing effect as a nested $let!". Most interesting indeed.


1 point by Pauan 4513 days ago | link

Yup. Immutable data + mutable variables is quite amazing, it solved so many different problems I was having, all with a very simple and elegant system.

-----

2 points by Pauan 4513 days ago | link

By the way... I mentioned a few times that my namespace system is "faster" but I didn't really explain why. The short answer is because all environments are immutable.

---

Now for the long answer...

Imagine a language like Scheme/Racket/JavaScript which uses pervasive lexical scope. A good optimizing Scheme compiler will be able to look at a function's lexical scope and determine what might change and what might not. The things that won't change could be inlined.

Now, that's great and all, but that property is completely destroyed by Kernel because of its pervasive (mutable) first-class environments. This not only makes it more difficult for humans to reason about programs, but it also means the compiler basically gives up, because it has almost no information about which names are used and which aren't. And because almost anything can change at any time, anywhere, it's much harder to do things like inlining.

So making a language with first-class vaus/environments pretty much forces you to run your code in an interpreter/JIT, because the cost of implementing a compiler probably isn't worth it (too much work for too little speed gain). It's also a large part of the reason why Lisps with vau were seen as "too slow" and why they were seen as "too powerful" because they had no fixed semantics.

---

But in Nulan, the environments are even more lexical than in Scheme. In particular, Scheme allows for forward references, but Nulan doesn't. That means that this code won't work...

  $def! foo: -> (bar)
  $def! bar: -> (foo)
...because the name `bar` doesn't exist in the lexical scope for the function `foo`. This means that functions can only depend on names that appear before them in the source code. I like this property already because it makes the control flow easier to understand.

This means the lexical scope of a vau never changes. Ever. So once a vau/function has been created, you know exactly what names are in its lexical scope, and in addition, if those names aren't vars, you also know exactly what their value is.

Not only that, but the dynamic scope of a function call never changes either. This means that you can actually execute/inline vaus at compile-time, just like macros, without removing their first-classedness.

And so the compiler/interpreter can use that extra knowledge to generate really fast code, because it knows precisely which names are in the lexical scope or not, and also knows precisely whether a name is a variable or not. If it's not a variable, it cannot change, thus it can always be safely inlined, and the default is to use immutability with variables being opt-in. This is like a compiler-writer's dream.

---

Now, you might be sitting there thinking I'm crazy for not allowing forward references, but that's not quite right. Nulan does allow for forward references, you just have to explicitly use a var:

  $var! bar
  $def! foo: -> (bar)
  $def! bar: -> (foo)
Now there's a (mutable) variable `bar` in `foo`s lexical scope. Then the later definition of `bar` will mutate that variable, so there's no problem. If you don't want `bar` to change at a later date, you can reset it to a normal non-var like so:

  $set! bar: bar
If this pattern is common enough, I can provide a vau that does it for you:

  $prevar! bar
    $def! foo: -> (bar)
    $def! bar: -> (foo)
This means that Nulan can do everything other languages can do with mutation, you just need to be explicit about it. This makes mutation obvious in the language itself, which in turn makes the code easier to reason about, AND gives compilers a lot of wiggle room to make optimizations.

---

Note: I didn't choose to use immutability because of performance, by the way. I discovered immutable environments because I was trying to solve the namespace problem and only afterwards I realized it also had the side benefit of making code potentially much faster. So this wasn't a case of premature optimization.

As for why I was thinking about immutability in the first place... I'll be honest, it was because of Clojure. I saw a video of Rich Hickey describing the concurrency model in Clojure and I was instantly hooked. Immutable data + mutable variables is sooooooo good for concurrency.

And even though Nulan isn't concurrent (yet) I figured I might as well add it in. Even in a single-threaded program, I hoped it would lead to clearer code, while also making it forwards-compatible with multi-core. It turns out my hope was right, because I discovered the immutable environment idea, which has more than paid for itself.

-----