Arc Forumnew | comments | leaders | submitlogin
2 points by rocketnia 4540 days ago | link | parent

If every definition form actually causes a new environment to be created and placed in some file-wide (or global?) variable behind the scenes, how often is that variable accessed? If I say (foo ($set! bar ...) bar), will I already get the new value for 'bar?

That's the kind of question that's tripped me up along this path. I like your system a lot.



1 point by Pauan 4540 days ago | link

The environment variable is implicit, but it's passed to vaus:

  $vau Env ...
In the above code, the local name Env is a variable that points to the dynamic environment[1].

I'll note this is a lot like call-cc, which gives you access to the normally-implicit continuation:

  call-cc: $fn [C] ...
---

"If I say (foo ($set! bar ...) bar), will I already get the new value for 'bar?"

Well, let's see... the above will:

  1. eval foo in the dynamic env
  2. if foo is a $fn then...
  3. eval ($set! bar ...) passing it the dynamic env
  4. $set! will update the dynamic env with the new binding
  5. eval bar in the dynamic env
So, yes, the second usage of "bar" will return the new binding, but only because functions evaluate their arguments left-to-right in sequential order. Theoretically, if they didn't evaluate them left-to-right, then that property might not hold. But for now I'm mandating left-to-right order for simplicity.

---

* [1]: That means a vau can mutate the environment variable directly, which is what $set! and $def! and friends do.

This doesn't offer any more capabilities because you can always just say:

  eval Env [$set! ...]

-----

1 point by rocketnia 4540 days ago | link

I asked about (foo ($set! bar ...) bar) because I thought maybe each symbol would be looked up in the immutable environment that existed at the beginning of the expression. That is, I thought you might pass an immutable environment, not a ref, to 'eval.

---

"That means a vau can mutate the environment variable directly, which is what $set! and $def! and friends do."

Yeah, that makes sense. The thing passed to 'eval needs to be a ref for the same reason.

Wait. I thought $set! worked by looking something up in the immutable environment, expecting that thing to be a ref, and then mutating that ref. Why does the environment need to be in a ref too?

(Sorry, I'd rather say "ref" than "var", since this discussion potentially involves environments which bind certain symbols (aka variables) to non-refs (aka non-variables?).)

-----

1 point by Pauan 4540 days ago | link

"That is, I thought you might pass an immutable environment, not a ref, to 'eval."

Well, I don't see why you can't directly pass in an immutable data structure to eval. It's just that environments are usually a var, that's all.

Of course, if you DID pass in a non-var data structure, then things like $set! would break because they expect a var. So it'd be read-only.

---

"Wait. I thought $set! worked by looking something up in the immutable environment, expecting that thing to be a ref, and then mutating that ref. Why does the environment need to be in a ref too?"

Allow me to explain... look at this piece of code:

  $def! foo 1
  $def! bar 2
  prn! foo bar
In the above, when we evaluate "$def! foo 1" we want "foo" to be bound to "1" in all expressions evaluated after it, and when we evaluate "$def! bar 2" we want "bar" to be bound to "2" in all expressions after it. That way, when we evaluate "prn! foo bar" it will print out "1 2".

In order to accomplish this, we need some form of mutation. This mutation is handled by an implicit environment variable. Let's rewrite the above to make the environment variable explicit:

  $let Env: var: make-env
    eval Env ($quote ($def! foo 1))
    eval Env ($quote ($def! bar 2))
    eval Env ($quote (prn! foo bar))
Here we have an empty environment which is stored in a var. We then sequentially evaluate expressions in that variable.

Now, when we evaluate "($def foo 1)" it will pass the Env variable to the $def! vau:

  $vau Env [X Y]
    var-set! Env: set (Env) X: eval Env Y
That local "Env" name refers to the exact same variable that is defined in the $let. It will then create a new environment which is just like the existing Env but with the name "foo" bound to "1". It will then set the Env variable to point to this new environment.

This means that when the second $def! expression is evaluated, it's called with the same environment variable, but the environment variable was mutated by the first $def! so that "foo" is bound to "1". This is then repeated again, binding "bar" to "2".

This variable mutation is how Nulan achieves a linear style of bindings that can change over time while maintaining immutability. This normally happens under-the-hood, but, just like call/cc lets you grab a hold of the implicit continuation, $vau lets you grab a hold of the implicit environment variable.

---

"(Sorry, I'd rather say "ref" than "var", since this discussion potentially involves environments which bind certain symbols (aka variables) to non-refs (aka non-variables?).)"

My terminology hasn't been very clear. In Nulan, an "environment" is really just a dictionary that stores symbol/value pairs. As shown above, this dictionary is usually wrapped in a var, which allows for environment "mutation"[1]. I usually call this combination of "dictionary + var" an "environment" but sometimes I might use "environment" to refer only to the dictionary part.

---

As for symbols vs. variables...

A symbol is never a variable. A variable is a separate data type, which is a mutable single-celled box. An environment dictionary has symbols as keys[2], and anything (including variables) as values.

But it doesn't usually make sense to talk of an environment binding a variable directly. Instead, an environment binds a symbol to a variable, and the variable can contain anything.

When discussing Nulan with you earlier, I used the word "reference" to refer to these mutable boxes. I decided to change the name to "variable" since they're the only thing in Nulan that actually varies: everything else is immutable. Instead, I've consistently used the word "name" for symbols that refer to variable/non-variable values.

So, if I say "an environment binds the name foo to the variable 5" that would mean this:

  set Env ($quote foo) (var 5)
That is, creating a new environment that has the symbol "foo" as its key, and a variable as its value, which contains the value 5.

---

I'll note that I plan for "eval" to automatically unwrap a variable when looking up a symbol in an environment, which gives the illusion that a variable is bound directly to an environment. As an example... this code here...

  $var! foo 5
...is equivalent to this code here[3]:

  $set! foo: var 5
That is, we're creating a variable which contains "5", and we're then binding the name "foo" to that variable.

But when eval looks up a symbol in an environment, if it happens to be a variable, it will automatically unwrap it, so that this...

  foo
...will return "5" rather than the variable itself. If you want to get at the variable itself, there's a little trick. You can create a vau which will do the lookup manually, bypassing eval:

  $def! $get: $vau Env [X]: (Env) X
Now you can say "$get foo" to return the actual variable itself. Most of the time you want the value of the variable, not the variable itself, so having to explicitly use "$get" to retrieve the variable seems like a good compromise to me.

---

* [1]: All mutation in Nulan must use vars, which is why any concept of "bindings changing over time" must use vars as well. That's why the environment dictionary is wrapped in a var, to allow for bindings to change over time.

* [2]: Technically speaking, you could bind non-symbols into an environment which would provide you with a special hiding spot that's invisible to eval, since eval only looks things up by symbol. This is very esoteric and unusual, though. Normal code just uses symbol keys in environment dictionaries.

* [3]: One difference: if the symbol is already bound to a variable, then $var! will update the existing variable rather than making a new one.

-----

1 point by rocketnia 4539 days ago | link

"Here we have an empty environment which is stored in a var. We then sequentially evaluate expressions in that variable."

"All mutation in Nulan must use vars, which is why any concept of "bindings changing over time" must use vars as well."

That's the technique you're using to define new things, yes. But I don't think 'eval and $vau, so core to the language's computation semantics, should necessarily be coupled to refs just to make definitions work.

For an alternative, you could have a procedure that sets what the next top-level command's environment will be, and you could implement definition in terms of that.

For another alternative, you could have each top-level command be an expression which returns the context to use for executing the next command. This is an option even if you eschew side-effectful procedure calls entirely.

---

"I decided to change the name to "variable" since they're the only thing in Nulan that actually varies: everything else is immutable."

You forget parameters. Those vary without mutation.

Local variables also vary, by nature of being calculated from parameters.

---

"A symbol is never a variable."

In this particular case, I do think there's a conceptual overlap between "symbol" and "variable" in our everyday use, but I don't care either way.

My point is, I find it confusing to redefine "variable."

In your examples, Env is a variable (in the everyday sense). The Env variable refers to a ref, and that ref holds an immutable environment. I don't want to say "the Env variable refers to a variable," and I'd rather not resort to workarounds like "the Env name refers to a variable."

I'll accept your terminology if you insist though.

-----

1 point by Pauan 4539 days ago | link

"That's the technique you're using to define new things, yes. But I don't think 'eval and $vau, so core to the language's computation semantics, should necessarily be coupled to refs just to make definitions work."

Why not? Immutability and variables are just as core to Nulan as anything else. I don't see why you would see variables as being somehow "less core" than vau/eval.

---

"For an alternative, you could have a procedure that sets what the next top-level command's environment will be, and you could implement definition in terms of that."

And why have a primitive that uses internal hidden mutation that only works in the one special case of global names when I could instead use vars which can be stored in global names, or local names, or inside data structures. It's a much more general and powerful system than a special-cased "set-the-next-global-env" primitive.

And how would you handle things like multiple namespaces? Or are you planning to always use a single namespace all the time? Vars have a sane semantic when it comes to multiple namespaces, and in fact vars can be used to create new namespaces in constant time with no overhead.

---

"For another alternative, you could have each top-level command be an expression which returns the context to use for executing the next command. This is an option even if you eschew side-effectful procedure calls entirely."

Sure, that's an interesting idea, but that still won't help with times where you really DO want mutation, like with local mutation, or dynamic bindings.

I chose vaus because they accomplish the same awesomeness as macros, except BETTAR. I chose immutability + mutable vars because they're very practical: immutability is basically a necessity if you want to do any sort of concurrency, but you still want some mutability for the sake of practicality.

And even in a single-threaded program, vars are still useful because:

1) You get very flexible multiple-namespace-like semantics for free. This includes being able to selectively mark some names as mutable/immutable and being able to include/exclude names.

Not only that, but thanks to immutability + vars you can create multiple namespaces in constant-time with essentially 0 overhead. This isn't normally needed, but the $include! vau does use it. Without vars it would have to be yet-another-special-cased-primitive, but with vars you can use them for everything.

2) Kernel's restricted mutable environment system makes dynamic bindings (along with other things) a pain in the butt. Nulan's freeflowing immutable environment system makes dynamic bindings trivial and also helps with many of the other problems that Kernel's environments have.

3) It works consistently for all data types, all the way from the core upwards.

I'll admit that I'm currently struggling to define a sane, practical, AND consistent semantic for variables, and I may very well ditch them for a better idea, but I don't think either of your two ideas are good enough to replace them.

I'll note this isn't anything new: I've struggled with many of the ideas in Nulan before finally finding a semantic that I'm happy with, or finding a better idea. I have my doubts about vars, but I'm not convinced yet that they're inherently flawed, just that my current semantic for them is.

I'm using vars quite bluntly because it's the best idea I have found. If you don't like vars, come up with a better idea. You'll need to solve all the problems above, or, even better, provide a system that makes the above problems no longer problems. In the meantime, I'll be doing my own thinking.

---

"You forget parameters. Those vary without mutation."

I didn't forget, I just prefer to call them "names". I see them as being more static because even though they can "change" by calling the vau with different parameters, they can't actually change. That is, you can't use $set-var! on them.

This distinction is obvious to me: functions can be trivially inlined, but vars are harder. Thus I see variables as being "more variable" than ordinary names.

---

"My point is, I find it confusing to redefine "variable.""

Well, I can understand that, but I've renamed a lot of "traditional" things in my language.

I know this is against the mathematical definition, but then again, using "sum" for "foldl" is against tradition too.

---

"and I'd rather not resort to workarounds like "the Env name refers to a variable.""

Why not? It's shorter than the word "variable" and closer to how we use the word "name" in everyday life. I'm not terribly fond of unnecessary words like "reference" and "lambda" and "parameter". If there's a simpler/shorter word, I'll use it.

After using Arc for so long, I figured you would be used to names like "fn" and "=" which are against the normal traditions. I don't care for tradition. I thought that would be obvious given some of the crazy ideas I'm cramming into Nulan.

---

"I'll accept your terminology if you insist though."

One of my goals is to be as internally consistent as I can be (while remaining practical enough for my own tastes). External consistency is just a nice bonus.

I'm making the language for myself, so I don't really expect other people to actually use my language, so call it what you want. What's important is that we can understand eachother, the rest is irrelevant.

-----

2 points by rocketnia 4538 days ago | link

"What's important is that we can understand eachother, the rest is irrelevant."

I'll use a combination of "name" and "ref" instead of "variable" to minimize confusion.

---

"Immutability and variables are just as core to Nulan as anything else. I don't see why you would see variables as being somehow "less core" than vau/eval."

Without '$vau or 'eval, you don't have much of an fexpr language anymore.

Without refs, it's still possible to write a program in pure FP style. If your top-level design depends on refs, a program without refs might have to take the form of a single gigantic expression, but it's still reasonably maintainable that way.

---

"And why have a primitive that uses internal hidden mutation that only works in the one special case of global names when I could instead use vars which can be stored in global names, or local names, or inside data structures."

I'm not saying to get rid of refs entirely! I'm suggesting to untangle them from '$vau and 'eval. You can think of 'set-the-next-global-env as an output stream instead of a ref if it makes it look more like a whole feature.

(In fact, I'm actually not suggesting to untangle refs from 'eval entirely. When 'eval processes a variable reference, finds a ref, and auto-unwraps it, I don't mind that. I'm only concerned about the fact that 'eval takes a ref to the namespace, rather than simply taking the namespace.)

I'll add another alternative to the fray. Instead of 'set-the-next-global-env, you can have a (global-env) syntax that gives you a ref which contains the environment that will be used for the next top-level command. By setting this ref, you influence future commands, but you don't influence the current one.

---

"And how would you handle things like multiple namespaces? Or are you planning to always use a single namespace all the time? Vars have a sane semantic when it comes to multiple namespaces, and in fact vars can be used to create new namespaces in constant time with no overhead."

You can still coordinate multiple namespaces using any of the alternatives. That's the reason the features 'set-the-next-global-env, (global-env), or returning-a-new-context-to-the-top-level would even exist.

Refs are pretty orthogonal to namespaces.

---

"Sure, that's an interesting idea, but that still won't help with times where you really DO want mutation, like with local mutation, or dynamic bindings."

You can use refs for that. I repeat, I'm not saying to get rid of refs entirely, just to untangle them from '$vau and 'eval.

---

"I chose immutability + mutable vars because they're very practical: immutability is basically a necessity if you want to do any sort of concurrency, but you still want some mutability for the sake of practicality."

If refs are tangled with the core language semantics, then arguably that's not just "some" mutability. :) Of course, it depends on what you compare it to.

---

"1) You get very flexible multiple-namespace-like semantics for free. This includes being able to selectively mark some names as mutable/immutable and being able to include/exclude names.

Not only that, but thanks to immutability + vars you can create multiple namespaces in constant-time with essentially 0 overhead. This isn't normally needed, but the $include! vau does use it. Without vars it would have to be yet-another-special-cased-primitive, but with vars you can use them for everything."

To "selectively mark some names as mutable/immutable," you can use refs for individual names in the namespace, rather than a single ref holding the whole namespace.

To "include/exclude names" or "create multiple namespaces," you can pass a namespace to a pure function that gives you a new namespace. Currently you store that new namespace in a ref to install it, but I'm suggesting alternatives: You can pass it to 'set-the-next-global-env, or you can return the new namespace to the top level. (My (global-env) alternative doesn't count here, because it still stores the namespace in a ref.)

As far as "special-cased" goes, if the namespace is passed around in a ref, I consider that just as arbitrary as these alternatives.

---

"2) Kernel's restricted mutable environment system makes dynamic bindings (along with other things) a pain in the butt. Nulan's freeflowing immutable environment system makes dynamic bindings trivial and also helps with many of the other problems that Kernel's environments have."

I think you're trying to talk about auto-unwrapping here, which I don't mind. Are you actually talking about something else too?

---

"3) It works consistently for all data types, all the way from the core upwards."

I think any of these styles could be implemented in terms of refs. Is that enough?

---

"I have my doubts about vars, but I'm not convinced yet that they're inherently flawed, just that my current semantic for them is."

What issue is it? Something to do with auto-unwrapping? We should talk about this, because it seems workable to me.

---

"After using Arc for so long, I figured you would be used to names like "fn" and "=" which are against the normal traditions."

Those mesh with traditions just fine! They're just not lisp traditions. ^_^

These days I might call an Arc (fn ...) a "procedure" rather than a "function," but that's just because I like the way "procedure" is more overt about the presence of side effects.

-----

2 points by Pauan 4538 days ago | link

"Without refs, it's still possible to write a program in pure FP style."

And without vau it's still possible to use macros, etc. Nulan has many things in it, all of which I consider to be important. I don't see vau as being more important to Nulan than immutable data structures.

If anything, I see immutability as being more important. The only major benefit of vaus over macros in Nulan is that they're hygienic by default, which, frankly, isn't a big deal because of... (wait for it...) immutable environments.

Ironically, this means that now that Nulan has immutable envs, vaus lose a lot of their power and aren't actually that much more attractive than macros. Or, to put it in another perspective, immutable environments make macros more powerful, almost to the point of vaus.

And because practical programs usually need at least a little mutation, I think using variables to provide that mutation is a reasonable thing to do, so I consider variables to be pretty important too (but less important than immutability). I'm still open to better ideas on how to handle mutation, but, I think it'll be pretty hard to beat the raw simplicity of variables.

---

"By setting this ref, you influence future commands, but you don't influence the current one."

Ah, I see the problem now! You're worried about influencing the current environment. I've only used environment mutation at the top level of a global/local environment, never within an expression like with your (foo ($set! bar ...) bar) example, so I don't really care one way or the other.

---

"Refs are pretty orthogonal to namespaces."

Sure, but the fact they make it so trivial to create new namespaces in addition to their other awesome features is certainly a plus.

---

"You can use refs for that. I repeat, I'm not saying to get rid of refs entirely, just to untangle them from '$vau and 'eval."

Oh, well, in that case I don't have much of an opinion one way or the other.

Manual global environment mutation isn't something you're supposed to be doing that frequently anyways, since you're expected to use the built-in "$set!", "$def!", etc.

How environment mutation occurs under the hood isn't a big deal from the programmer's perspective.

---

"If refs are tangled with the core language semantics, then arguably that's not just "some" mutability. :) Of course, it depends on what you compare it to."

Well, in Clojure, you can freely mutate global variables, like functions.

In Nulan, globals are by default immutable, so you have to explicitly use "$var!" to turn on mutability.

So, yeah, even with mutable environments, Nulan is still significantly less mutable than Clojure, which is the language I'm using as a comparison for immutability.

---

"To "selectively mark some names as mutable/immutable," you can use refs for individual names in the namespace [...]"

Right, that's what Nulan does already. It has to do that because even though the whole environment may be in a var, the environment dictionary itself is static.

And once a function has been created, it uses the unwrapped static environment dictionary as its lexical scope. So changing the environment variable only affects future expressions anyways, it can't change previous lexical scopes.

---

"As far as "special-cased" goes, if the namespace is passed around in a ref, I consider that just as arbitrary as these alternatives."

Sure, but right now I get to use a single system (variables) for everything, as opposed to adding in a second primitive (like set-the-next-global-env or whatever). I'd like to keep the core primitives as low as possible, so reusing variables for environment mutation makes sense to me since the whole point of variables is to manage mutation.

---

"I think you're trying to talk about auto-unwrapping here, which I don't mind [...]"

Nope! I'm talking about the fact that Kernel requires you to have an explicit reference to an environment in order to mutate it. So let's suppose I had a "$let-var" vau which is like "$let" except with a dynamic variable:

  ($let-var ((bar 5))
    ...)
That will work fine at the top level, but it won't work in this case...

  ($let ((foo 1))
    ($let-var ((bar 5))
      ...))
...because the $let-var can only mutate things in its immediate parent environment. This also means that the common practice (in Arc) of binding a local and then binding a global function doesn't work...

  ($let ((foo 5))
    ($define! bar
      ($lambda ...)))
...because it's assigning the name "bar" into the scope of the "$let", not the global scope like you wanted. So instead you have to do contortions like this:

  ($let ((env  (get-current-environment))
         (foo  1))
    ($let-var-in env ((bar 5))
      ...))

  ($let ((env  (get-current-environment))
         (foo  5))
    ($set! env bar
      ($lambda ...)))
Kernel went out of its way to define two versions of various vaus: one that mutates the current environment, and one that is passed an explicit environment as an argument. I get the feeling that was done in part to make situations like the above more palatable.

Contrast that with Nulan, which would work like so:

  $var! bar
  $let foo 1
    $let-var bar 5
      ...

  $predef! bar
    $let foo 5
      $def! bar
        $fn ...
Variables not only give you mutation, but they also let you control where a name is set. If there's a var in the scope chain, it'll assign to it (regardless of where the var is), but if not, it'll make a local binding. This is okay because environments are immutable, so it'll only do it if a variable is defined before the assignment.

---

"I think any of these styles could be implemented in terms of refs. Is that enough?"

I'm not sure what you mean. I was talking about how it works for all data types (environments, dictionaries, lists, etc.) which is nice because it's consistent.

---

"What issue is it? Something to do with auto-unwrapping? We should talk about this, because it seems workable to me."

Yeah, that's the thing that's been bugging me the most about vars. But I think the way to handle that is to have two kinds of variables: one that is auto-unwrapped, and another that isn't. Most of the time you'd use the auto-unwrapped one for convenience, but the other one would still be available in case that gets messy.

-----

1 point by rocketnia 4538 days ago | link

Whoops. You mean "var" in the Clojure or Common Lisp 'defvar sense, don't you? I've been using "ref" in what I thought was the Clojure sense, but was actually just the Haskell sense. :-p So much for avoiding confusion.

---

"And without vau it's still possible to use macros, etc."

I was about to make a frequency argument, but yeah, I guess macros would still let you do most of what you're doing (as you say).

---

"I don't see vau as being more important to Nulan than immutable data structures."

Did you mean to say "mutable" there instead of "immutable"? I've been talking about mutability being farther from the core.

---

"Ironically, this means that now that Nulan has immutable envs, vaus lose a lot of their power and aren't actually that much more attractive than macros."

Ah, right. Fexprs are nice at the REPL when they're late-bound, but now they're not.

With Fexpress I plan to sacrifice late binding too. I don't have practicality in mind for Fexpress, but I do have at least these goals:

1) Give an existence proof that at least one design for an fexpr language is ahead-of-time compilable.

2) See if this enables programming patterns that make heavy use of eval. (If it turns out there is a cool technique enabled this way, it'll be a practical solution... in search of a problem.)

---

"Ah, I see the problem now! You're worried about influencing the current environment."

Yep. But I haven't really said why yet....

I'm heavily influenced by the desire to partially evaluate fexprs. If a single mutable container is passed through every corner of the code, even one uncompilable expression will make it difficult to know the contents of that container, which will make it difficult to compile any of the expressions which follow that one.

---

"I'd like to keep the core primitives as low as possible, so reusing variables for environment mutation makes sense to me since the whole point of variables is to manage mutation."

Hmm, if Nulan's variables weren't just mutable boxes but had support for something like Clojure's STM, then your goals for that side system might justify using variables in this case, and also in various other cases where just having a random mutable box wouldn't even make sense otherwise. :)

Maybe the interactions with continuations and concurrency could persuade me. Do you intend to have '$let-var be just like Racket's 'parameterize, or would you just use mutate-and-mutate-back?

---

"If there's a var in the scope chain, it'll assign to it (regardless of where the var is), but if not, it'll make a local binding."

I find that unsettling, but I don't have a reason why. Not even an impractical one. :-p

---

"I'm not sure what you mean. I was talking about how it works for all data types (environments, dictionaries, lists, etc.) which is nice because it's consistent."

Given mutable boxes, people are going to create their own stateful interfaces on top of them, so you're going to get stateful interfaces different from boxes anyway. I think 'set-the-next-global-env is consistent with that language experience.

Anyway, the top-level namespace doesn't have to be stored in a data structure. The language itself can hold onto it.

---

"But I think the way to handle that is to have two kinds of variables: one that is auto-unwrapped, and another that isn't."

Oh, I'd take a more syntax-side approach: Define a new expression type that holds a symbol. When that expression is evaluated, it's like evaluating the symbol, but without auto-unwrapping. That way the programmer always has the option to write any part of the code in a more precise style, at the cost of brevity.

-----

1 point by Pauan 4537 days ago | link

"Whoops. You mean "var" in the Clojure or Common Lisp 'defvar sense, don't you?"

Yup, the Clojure sense.

---

"Did you mean to say "mutable" there instead of "immutable"? I've been talking about mutability being farther from the core."

Nope. I see immutability as being more important than vau. But for practicalities sake, you do need some mutability, so I also see mutability as being important (just less than immutability).

You're right in the sense that it's possible to define the language with $vau, eval, and immutability, but without mutability... but I think the end result would be a massive pain and very impractical for actually getting things done, so I still consider mutability important.

My point was that I don't really see these things in some sort of rigid hierarchy where "mutability is frivilous and vau is central". They're used for different purposes and they're both important, for different reasons.

---

"I'm heavily influenced by the desire to partially evaluate fexprs."

Yeah, I get that. But I'm not going to contort my language to conform to some notion of purity, especially not to enable some technique like partial evaluation which may or may not work and may not be beneficial even if it does work. Sure, it might be great if you could partial evaluate Nulan, but if you can't, that's fine too.

I already decided that it's just fine if Nulan can't be compiled and must be interpreted. Until I run into some major roadblocks, I'm just going to do the simplest/best thing I can. I'll worry about speed and purity later. I already said my idea about immutable environments is about practical benefits, not speed. The speed is just a nice bonus, that's all.

---

"If a single mutable container is passed through every corner of the code, even one uncompilable expression will make it difficult to know the contents of that container, which will make it difficult to compile any of the expressions which follow that one."

This is true of vars in general. I'm not exactly sure how that situation is made worse for mutable environments, but you're the one with the partial evaluation ideas. I haven't thought about this much.

---

"Maybe the interactions with continuations and concurrency could persuade me. Do you intend to have '$let-var be just like Racket's 'parameterize, or would you just use mutate-and-mutate-back?"

I'm currently using mutate-and-mutate-back because it's dirt-simple. And so, how $let-var interacts with threads will depend on how vars themself interact with threads. I haven't really given that much thought to multicore yet: right now I'm focusing on just getting the damn thing working at all, so all my talk about multicore is about the future.

---

"I find that unsettling, but I don't have a reason why. Not even an impractical one. :-p"

Yeah it disturbs me too, but like you I can't find any real reason why. It's not any less dangerous than what Arc/Scheme/JavaScript/etc. do, and in fact it's actually far safer because it's selective and you can use various constructs to control mutability.

---

"Given mutable boxes, people are going to create their own stateful interfaces on top of them, so you're going to get stateful interfaces different from boxes anyway. I think 'set-the-next-global-env is consistent with that language experience."

Yeah, but I still need to think about what the core of the language is and what the idioms are. Saying, "mutable boxes make such and such thing possible, so eventually somebody will add it in, so you might as well add it in now" doesn't convince me because languages can and do influence what people do and also how easy it is to do things. My view can be summed up as: the default experience matters.

---

"Anyway, the top-level namespace doesn't have to be stored in a data structure. The language itself can hold onto it."

And how would the language itself hold onto it? Presumably in a data structure. And $vau already reifies the dynamic environment, so I can't just pretend that the environment is some closed-box that isn't accessible to the programmer.

I don't currently make a distinction between top-level or local-level environments: they're the exact same from both the implementation's perspective and $vau's perspective. I like that consistency, so I'd need to see a good incentive to make them different.

---

"Oh, I'd take a more syntax-side approach: Define a new expression type that holds a symbol. When that expression is evaluated, it's like evaluating the symbol, but without auto-unwrapping. That way the programmer always has the option to write any part of the code in a more precise style, at the cost of brevity."

So you'd be wrapping a symbol rather than a var? Well, okay, but I can't think of any situation where I'd want to auto-unwrap something other than a var, so tieing it to var seems okay to me.

Wait... there is one thing... $lazy creates a special data structure which is auto-evaled, and currently there's no way to disable that. So I guess I could use the same auto-unwrap mechanism for both $lazy and vars.

-----

1 point by rocketnia 4537 days ago | link

"Nope. I see immutability as being more important than vau."

Then we're talking past each other here. I don't care to compare these two.

---

"This is true of vars in general. I'm not exactly sure how that situation is made worse for mutable environments, but you're the one with the partial evaluation ideas. I haven't thought about this much."

I'm not sure what you're saying here, but I'll explain again. Passing a mutable box to every part of the code means any part could clobber that box, inhibiting our ability to predict the behavior of the remaining code.

The box you're passing around every part of the code is the one that contains the namespace.

---

"And how would the language itself hold onto it? Presumably in a data structure."

That's an implementation detail. It doesn't make a difference to the users of the language.

To implement a language that "holds" something without a data structure, you can implement it in a language that can already do that. ^_-

We already have lots and lots of languages with that feature. Any language with non-first-class variables can qualify (e.g. Haskell, Java, or Arc). Some languages might let you hold values in event queues (e.g. JavaScript or E) or in a language-wide stack (e.g. Forth, Factor, or assembly language).

---

"And $vau already reifies the dynamic environment, so I can't just pretend that the environment is some closed-box that isn't accessible to the programmer."

All it needs is the immutable part. You're putting a box around it, and I consider that to be harmful, unnecessary complexity.

---

"I don't currently make a distinction between top-level or local-level environments: they're the exact same from both the implementation's perspective and $vau's perspective. I like that consistency, so I'd need to see a good incentive to make them different."

Hmm, why are you even wrapping a local environment in a box? Why not use '$let for every locally scoped definition?

-----

1 point by Pauan 4537 days ago | link

"I'm not sure what you're saying here, but I'll explain again. Passing a mutable box to every part of the code means any part could clobber that box, inhibiting our ability to predict the behavior of the remaining code."

Yeah, but only certain functions are capable of clobbering that box, and because environments are immutable... it should be possible to statically determine at compile-time whether such a function occurs or not. Racket can already do this: it statically determines whether a module uses "set!" or not. It should be a piece of cake to do the same in Nulan, at compile-time.

As I already said, when a vau is created, the lexical scope is the current value of the environment variable, not the environment variable itself. So you know exactly what bindings are present within the vau's body when the vau is called, even with environment mutation. That should give you plenty to reason about, right?

And because the dynamic environment is really just the lexical environment of the call site, that should be immutable as well... which means fexpr inlining should be possible even though the environment is stored in a variable. Unless I'm missing something. I haven't really thought this through, so I wouldn't be surprised.

---

"All it needs is the immutable part. You're putting a box around it, and I consider that to be harmful, unnecessary complexity."

Yet clearly there needs to be some kind of mutability... and I don't see your environment-mutability schemes as being any less complex than variables, since I've already decided to include variables as part of the language, might as well use 'em. Now, if my language didn't have variables to begin with, then I can agree with you that they'd be more complicated than your ideas.

---

"Hmm, why are you even wrapping a local environment in a box? Why not use '$let for every locally scoped definition?"

That's to allow $def! and $set! to create local bindings. Though you're right that if I gave up on internal-definition forms I could probably remove var from local environments.

I'm not convinced that's a net win, but it would solve the problem of $def! creating a local binding sometimes and mutating a var in other cases... so that's pretty nice.

---

But I get the distinct impression you're only being so anti-var because they interfere with your whole partial evaluator scheme... maybe it would be better to figure out a way to deal with vars in your partial evaluator? Unless you're saying that's impossible?

In particular, languages like Scheme/Racket/JavaScript seem to do just fine with mutable variables. Yeah they could be even faster with immutable everything, but I wouldn't exactly call them slow languages...

I know Racket/JS use JIT rather than AoT. I'm okay with that. I'm even okay with Nulan staying interpreter-only, until it becomes too slow to be usable. Python/Ruby seem fast enough to me and they're interpreted (albeit written in C... or is it C++?)

I haven't even gotten to the point of porting Nulan from pure-Python to PyPy, so I have no clue how fast PyPy will make it run. I still have high hopes that PyPy will give it Good Enough(tm) speed for the kinds of things I want to do. That's enough for me.

-----

1 point by Pauan 4537 days ago | link

"So I guess I could use the same auto-unwrap mechanism for both $lazy and vars."

Okay, I got a semantic I'm reasonably satisfied with... there will be three built-in primitives: `implicit`, `$explicit`, and `implicit?`.

* implicit accepts a function as its argument and returns a wrapped structure that when evaluated will call the function argument.

* $explicit evaluates its argument and if it's implicit, it returns the underlying function.

* implicit? just tells you whether the data structure is implicit or not.

This can be used to implement auto-unwrapping behavior for vars and also laziness.

---

By the way, if "eval" were mutable, I could actually implement the whole implicit/explicit thing in Nulan itself. The downside is that every call to eval would have extra overhead because it would have to unwrap the variable. I'm also not convinced it's a good idea to let the language change evaluation in arbitrary ways, but it's an idea I'll have to think about.

-----

1 point by rocketnia 4537 days ago | link

Your implicit wrappers sound like they would be too slippery. First I'd try (all implicit? lst)--sorry, I'm using Arc syntax here--and I'd find out all the implicit wrappers were unwrapped before they got to 'implicit?. Then I'd try something like (all [implicit? ($explicit _)] lst), but no dice; the implicit wrappers are unwrapped before they get to [implicit? ($explicit _)]. Either I'd have to pass an fexpr (which 'all shouldn't even support, for API simplicity's sake), or I'd have to hand-roll a new version of 'all. (EDIT: Or maybe 'all would just have to use '$explicit in the first place.)

I think aw's implicit global variables are somewhat better. Things are only unwrapped once--when a symbol is evaluated--and after that they're first-class values, without the evaluator oppressing them at every turn.

---

"This can be used to implement auto-unwrapping behavior for vars and also laziness."

I don't think it can model laziness (since it forces at every step). I recommend to model laziness by way of a doppelganger standard library that builds promises out of promises. Eager utilities would still see these promises as first-class promise objects. I explored this not too long ago as a way to port Haskell code to JavaScript... but I used a different technique than this one, and I grew to dislike it.

The code which uses that technique I now dislike is at (https://github.com/rocketnia/underreact/blob/865ccdb1a2c8dc0...), if you're willing to trudge through its boilerplate. (I'm linking to a specific commit because I plan to delete it for being too long out of date and too hard to maintain.)

---

"I'm also not convinced it's a good idea to let the language change evaluation in arbitrary ways, but it's an idea I'll have to think about."

It might be a challenge to keep things efficient and modular. But it's just the kind of challenge you'd looking forward to, I'm sure. :-p

There's nothing wrong with pursuing this kind of thing. A minimalistic Kernel-style 'eval is already user-extensible in a way, since whenever it evaluates a cons cell, it executes user code (an operative or applicative).

-----

1 point by Pauan 4537 days ago | link

"Your implicit wrappers sound like they would be too slippery."

Yeah, I've already changed it since then. It's in a lot of flux right now!

---

"I think aw's implicit global variables are somewhat better. Things are only unwrapped once--when a symbol is evaluated--and after that they're first-class values, without the evaluator oppressing them at every turn."

Not true. aw's implicit system is the same as Arc/Nu and Nulan except that ar hardcodes symbol names while Arc/Nu and Nulan use first-class mutable thunks, that's all.

aw's implicit global system works by having a table of symbols. The compiler will look at this table and if it finds a symbol in it, it will wrap it as a function call. In other words... in ar, if 'foo is in the implicit table, this:

  (foo bar qux)
Would be compiled into this:

  ((foo) bar qux)
And then foo is a function that returns the value. This is just like Arc/Nu and Nulan except both Arc/Nu and Nulan handle it in much cleaner ways:

In particular, aw's system hardcodes the symbols, which means it isn't late-bound and works poorly with multiple namespaces. Because Arc/Nu uses values rather than symbols, both of those work perfectly.

As for Nulan... it doesn't have late-bindedness or multiple namespaces (by default), so that's not a problem either way. :P

---

"I don't think it can model laziness (since it forces at every step)."

Why not? It may call the implicit thunk repeatedly, but the $lazy vau caches it so the expression is only evaluated once:

  $defv! $lazy Env; X ->
    $lets: U:      uniq
           Saved:  var U
      implicit; ->
        $if Saved = U
          $set-var! Saved: eval X
          Saved
The only issue I see with this is efficiency, which I'm not really worried about right now. That particular problem could be solved by giving a way for the implicit wrapper to mutate itself... then, when evaluating the thunk, it would mutate itself to point directly to the evaluated expression. It would still be slightly inefficient because it would still have to unwrap the implicit, but hey, I can only do so much.

---

"There's nothing wrong with pursuing this kind of thing."

There's nothing wrong with any choices. The question is whether you like them or not, whether they support your goals or not.

-----

1 point by Pauan 4538 days ago | link

By the way... one interesting idea that I would be open to is to change $vau so that it returns a list of two elements, the environment and the return value. In other words, rather than saying this:

  $vau Env Foo
    ... Foo ...
You'd instead say:

  $vau Env Foo
    ... [Env Foo] ...
And then it's trivial to write a wrapper that automatically returns the environment, so there's no loss in convenience. I'm not entirely convinced this is actually better than using a mutable var, but it might solve the problem (?) of assigning to an env while in the middle of an expression, like with (foo ($set! bar ...) bar)

-----

1 point by rocketnia 4538 days ago | link

That's a more pure-FP approach to things, but what do you plan to use the returned environment for?

If you're just going to thread it into the next subexpression, then it's still hard to process this in parallel. (As I say in the other comment I just posted, "even one uncompilable expression will make it difficult to know the contents of that container, which will make it difficult to compile any of the expressions which follow that one." In this case the series of values isn't in a container, but it's still hard to follow.)

If you ignore the environment result everywhere but at the top level, and you use it as the environment for the next top-level command, that could be pretty nice.

-----

1 point by Pauan 4537 days ago | link

"That's a more pure-FP approach to things, but what do you plan to use the returned environment for?"

Well, here's how I figured it might work... a $vau evaluates its body left-to-right in its lexical environment, right? So I figured it would take the result of evaluating the top-level expression and then use that as the environment for the next top-level expression.

Like I said, I'm not really terribly worried about parallelism, at least, not that kind of parallelism. Being able to evaluate sub-expressions and stuff in parallel is cool and all, but I get the impression it'll be hard no matter what system I come up with, because of mutation and side effects. It's probably better to make concurrency explicit, like it is in Clojure.

---

"If you ignore the environment result everywhere but at the top level, and you use it as the environment for the next top-level command, that could be pretty nice."

Yeah that pretty much sums up my idea. I'm still not convinced this is actually the right way to do it, but it is interesting enough to have mildly piqued my interest.

-----