Arc Forumnew | comments | leaders | submit | Pauan's commentslogin
1 point by Pauan 5041 days ago | link | parent | on: Newbie at programming

Well, my primary experience is in JavaScript, which is all the rage these days. It's pretty much your only option if you want client-side scripting, though there are plenty of languages that compile down to JavaScript in one way or another.

I suppose Arc would also be a decent starting point, given how Arc was built around its webserver. Unfortunately, I don't use Arc for its webserver, so I can't really help you with that. There are a few other people floating around on this forum who have more experience with that sort of thing.

Like akkartik said, feel free to ask any questions you have, whether related to Arc or not. We'll do our best to answer them.

-----


I would first like to give a very big thank you to waterhouse for their post on AVL trees[1]. I had been looking at a variety of trees (AA, finger, scapegoat, and splay, mostly) but had run into some difficulty.

You see, most information on trees assumes that they're going to be mutable. It seems good information on immutable trees is a bit hard to come by, at least in my experience.

So, even though I hadn't liked AVL trees at first, I really warmed up to them. Waterhouse's post demonstrates a very simple way to create persistent immutable AVL trees, which is exactly the kind of thing I was looking for.

I was able to easily copy the algorithm mostly word-for-word into Python, and here's the results. It supports:

* Dense lists (sorted by index, can't have empty spots in the middle of the list[2])

* Dictionaries which map keys to values

* Sets which are dictionaries with only the keys, no values

If you want a sparse list, you can just use a dictionary with numbers as the keys.

---

All three data structures have:

* O(1) time to retrieve the number of elements (the length)

* O(n) time to iterate through all the elements, which is the same as a linked list (though there's additional overhead which makes it slower)

* O(n) time to iterate through all the elements in reverse order[3]

* O(log2(n)) lookup and (immutable) insertion

* Not sure about the performance of deletion... I'd expect it to be roughly O(log2(n)) but it doesn't matter because deleting should be rare

* Ability to get/insert/delete items by index (this may change, though)

---

This is part of my desire to get rid of cons and use some form of binary tree for everything in Nulan, and it's largely thanks to waterhouse that it became a reality.

I have tested waterhouse's algorithm to verify that it works: it seems solid enough to me, but I have no clue how fast it is or how good the rebalancing is. Still, it's a very solid start, which I can improve upon over time. That's what's most important at this stage: just doing the simplest thing that can possibly work.

---

* [1]: http://www.arclanguage.org/item?id=14181

* [2]: Linked lists can't have empty spots in the middle either.

* [3]: Just commited: https://github.com/Pauan/nulan/blob/master/nu_tree.py

-----

1 point by akkartik 5044 days ago | link

AVL trees are just binary trees. Did you really mean http://en.wikipedia.org/wiki/B-tree?

-----

2 points by Pauan 5044 days ago | link

My mistake, I said B-tree when I meant "binary tree"[1]. It has been corrected.

AVL is a particular kind of self-balancing binary search tree[2], which is what I want. Which kind of tree isn't so important, just so long as it's simple and fast enough.

---

* [1]: B-trees are a self-balancing n-ary search tree, and judging by the Wikipedia article, I probably won't use them in Nulan, at least not for a while.

* [2]: Not every binary tree is a search tree, and not every search tree is self-balancing.

I want all three properties because of the good worst-case performance and because I believe that it will be easier to parallelize if the trees are roughly balanced.

As for why I chose binary over n-ary, that's simply for the sake of simplicity. Since it'll be a core datatype, I don't want the programmer API being too complicated.

-----

1 point by Pauan 5046 days ago | link | parent | on: Namespaces in Nulan

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 5046 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 5045 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 5045 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 5045 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 5044 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 5044 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 5043 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 5043 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 5043 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 5043 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 5043 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 5043 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 5043 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 5044 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 5043 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 5043 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.

-----

2 points by Pauan 5046 days ago | link | parent | on: Namespaces in Nulan

You get used to it (I hope). Anyways, it's actually like this:

  ($lets (Top  (get-current-env))
         (N    ($quote nou))
         (Old  ((Top) N))
    (eval Top ($quote $use! foo))
    (set! Top N Old))
...Which just caused me to see a bug, woohoo. Anyways, the reason it calls Top is because (as subtly pointed out in the article) environments are variables. Calling a variable returns whatever the variable is pointing to[1]. So we first call Top which returns an immutable data structure, and we then call that data structure to return the value at the key.

Basically, the reason why we're doing this is, a data structure might contain a variable or a value, so we're grabbing the variable-or-value from the current environment, and then assigning it back again after the call to `$use!`

---

* [1]: This isn't set in stone yet, and I'm still thinking about the right semantics for variables.

-----

1 point by rocketnia 5046 days ago | link

Ah, so (get-current-env) returns the variable. I like that. ^_^

I wasn't thinking of it as "environments are variables"; I was thinking of it as "environments are immutable, and variables often hold them."

-----

1 point by Pauan 5046 days ago | link

Right, but all the built-in things that deal with environments usually wrap them in a var. There's no special "environment data type", it's just an ordinary dictionary that is wrapped in a var and treated like an environment[1]. So you could pass in your own custom dictionary to eval, for instance. At least, that's the plan.

---

* [1]: Actually, I was thinking that it might be a good idea to have a special environment data type, which is exactly like a normal dictionary except that if you try to set a key that isn't a symbol, it throws an error. Not sure about that yet.

-----

1 point by Pauan 5046 days ago | link | parent | on: Namespaces in Nulan

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 5046 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.

-----

2 points by Pauan 5046 days ago | link | parent | on: Namespaces in Nulan

Yes, but it's not ready yet. The parser is pretty rock solid, but the evaluator is broken. This is largely because I'm moving from using cons to using B-trees and the transition has only just started.

-----


"Possibly: you could only make the first body argument to let the first odd item in the arguments to let that has parens, and not simply the first item in the arguments that has parens, but that prevents this from being working code:"

There's actually a very sane way of handling this... You just say that "let" has only a single body expression, so as soon as you find a mismatched pair, that must be the body. That's what my language does with "$lets":

  $lets: a 1
         b 2
         c 3
    foo bar
The above is equivalent to this Arc code:

  (withs (a 1
          b 2
          c 3)
    (foo bar))
If you want to have multiple expressions in the body, you just use "$do":

  $lets: a 1
         b 2
         c 3
    $do: foo bar
         qux corge
This is like how Arc requires "do" if you want to evaluate multiple expressions in an "if".

---

In any case, I have to agree with you. The syntax rules seem somewhat convoluted and confusing. Naturally I'm biased, but I prefer the way my language handles the situation: it has very few parens, but it does so in a much more flexible, and I believe simpler way. I've put quite a bit of work into making the syntax work well, and will continue to do so.

-----

2 points by Pauan 5051 days ago | link

By the way... certain user conventions in my language mostly obviate the need for the syntax highlighting scheme that he came up with. In my language:

  1. Vaus are prefixed with $ (like in Kernel)
  2. Predicates end with ? (like in Kernel)
  3. Non-referentially-transparent things end with ! (similar to Kernel)
  4. Local variables start with a capital letter (like in Shen)
These rules combined means that it's trivial to write a syntax highlighter for my language, and it means the information is available even if you don't have syntax highlighting. To demonstrate, here's a 1-to-1 translation of the "factorial" function (shown in the video) into my language:

  $def factorial; N ->
    $loop: Cnt  N
           Acc  1
      $if: is? Cnt 0
        Acc
        $recur; --Cnt; Acc * Cnt
Naturally I wouldn't define it that way in my language, but... you get the idea.

Certainly there are some nice ideas in there, like giving special colors to external variables and such forth... but I think languages should look nice and be usable even without any syntax highlighting at all, with syntax highlighting as just an extra convenience, nothing more.

---

By the way... here's the idiomatic way to write "fact" in my language:

  $def fact
    0 -> 1
    X -> X * (fact X - 1)
Inefficient (because it isn't tail recursive), but even so, it's simple. If you want a tail-recursive version...

  $def fact-acc
    0 Acc -> Acc
    X Acc -> fact-acc X - 1; X * Acc

  $def fact: X -> fact-acc X 1
Or maybe you want to be like Haskell[1] and define it as...

  $def fact: X -> sum 1; range 1 X; mul
---

* [1]: http://www.willamette.edu/~fruehr/haskell/evolution.html

-----

1 point by akkartik 5051 days ago | link

Here's the highlighting I currently use: http://imgur.com/UCaMZ

The colors on this machine aren't quite right, but it shows what I care about:

a) Comments since they're never evaluated

b) Literals since they eval to themselves

c) Parens and ssyntax -- mostly as delimiters, but with backquotes distinguished

Everything else is unhighlighted. If the language does its job I really shouldn't be thinking about whether something's a macro. And local variables ought to be the default, so why add a little salience to Every Single One?

---

Wart comes with the vim settings for this highlighting: http://github.com/akkartik/wart/blob/2e01126102/vimrc.vim. It's very smart about ssyntax. The colors really indicate precedence. Notice in the second statement how some colons are colored like ssyntax, but not others. Or how the exclamation in mac! at the bottom isn't colored like ssyntax.

But after all that I don't want to make too many assumptions about how a new reader will view one's code. It needs to be visually balanced even without highlighting. Your typography rules remind me a little of early wart. See the if macro at the end of http://www.arclanguage.org/item?id=15137 -- and your comment on http://www.arclanguage.org/item?id=15140 :)

(Here's how if looks today, for comparison: http://github.com/akkartik/wart/blob/2e01126102/033check.war...)

-----

2 points by Pauan 5050 days ago | link

"If the language does its job I really shouldn't be thinking about whether something's a macro."

This is the same argument we had before... it's just not true. Macros/vaus behave fundamentally different from functions, they are not the same thing. By making them stand out, it gives your eyes something to grab onto.

Humans are wonderfully good at noticing patterns, but only if there's enough information there to pattern match on. If you don't provide this information in the syntax, it adds additional mental overhead.

You now have to memorize whether something is a vau or not (for common things like $let this isn't a problem, but for things less commonly used it can be a pain). The same goes for locals: if it isn't apparent in the syntax whether a variable is local or not, you have to mentally scan up the scope chain every single time you glance at code.

One of the problems with Lisp is that due to its lack of syntax, there's very few patterns that your mind can pick up on, so you have to do a full-blown mental parse of the source code just to determine whether something is a local or a vau or whatever.

It might not seem like much, but all the tiny extra mental overheads do add up. I believe that once you get used to my syntax, it's easier to read source code, because just by glancing at it your mind can notice all the little patterns.

Of course, there might be better criteria other than fn/vau/global/local/predicate/mutation... if so, I'd be interested in hearing it[1]. But I do think, whatever criteria you choose, it's important to have it be visually apparent so our poor human brains don't have to work so hard to parse our code. We are visual creatures, let's give our minds some visual feedback to chew on.

---

"And local variables ought to be the default, so why add a little salience to Every Single One?"

You forget that most functions/vaus are global. In fact, in my language, roughly 1/2 of the variables are globals, with the other 1/2 being locals. Out of those globals, roughly 1/3 are vaus, with the other 2/3 being fns.

---

"and your comment on http://www.arclanguage.org/item?id=15140 :)"

You're right, there is a fine line between adding syntax to make the source code more readable, and adding syntax so it ends up looking like Perl. I've tried to add in syntax only when I feel there's a significant benefit from doing so.

I'll note that my language doesn't have that particular problem mentioned in that particular post because my language doesn't have quasiquote/unquote/unquote-splicing, so that example would just be `@Body`

---

By the way, I used to be really off-put by how Kernel uses `$?!` in symbols to give them special meaning, and I also really disliked how Shen has local variables start with a capital letter... but after trying it out for a while, I got used to it and found that it actually wasn't that bad after all. Now I think it's an overall net win.

---

For comparison, here's how my syntax highlighting for my language currently looks:

http://img684.imageshack.us/img684/7873/screenshot0712201209...

---

* [1]: In particular, I just realized that it might be better to use $ for constructs that introduce additional binding names. This might be more useful than a general vau/fn distinction.

Then again, after looking through the source code, there were only a handful of vaus that didn't introduce new bindings: and, catch, hook, if, or, and quote

So, given how most vaus apparently exist for name binding, I think it's best to just use the general vau/fn distinction.

-----

1 point by akkartik 5050 days ago | link

"Macros/vaus behave fundamentally different from functions, they are not the same thing. ..it adds additional mental overhead."

Functions, macros, they're just ways to get certain behavior in the most readable way possible. Perhaps they add mental overhead in kernel because it's concerned about hygiene and such abstract matters.

Wanting to track your macros is OCD like wanting to avoid namespace pollution is OCD. Just relax, use what you need, remove what you don't need, and the function/macro distinction will fade into the background.

I guess we have to agree to disagree :)

-----

3 points by Pauan 5050 days ago | link

"Functions, macros, they're just ways to get certain behavior in the most readable way possible."

Sure. And their behavior is different: macros/vaus don't evaluate their arguments, functions do. That's because they're used for different purposes, so distinguishing between them is important and/or useful.

---

"Perhaps they add mental overhead in kernel because it's concerned about hygiene and such abstract matters."

I don't see what hygiene has to do with it... we're discussing about making it easy to tell at a glance whether a particular variable is a function or a vau, that's all. That's true regardless of whether the vau is hygienic or not.

I'll also note that I have not actually programmed in Kernel, so all my talk about "mental overhead" is actually referring to Arc, which is a distinctly unhygienic language.

In any case, my gut says that making a distinction between vaus and functions is important, so that's what I'm doing.

---

"Wanting to track your macros is OCD like wanting to avoid namespace pollution is OCD. Just relax, use what you need, remove what you don't need, and the function/macro distinction will fade into the background."

I do indeed worry about namespaces, which is why my language is going to have fantastic namespace support, most likely built on top of first-class environments.

Not only does this allow people to write solid libraries that don't need to worry about collisions, but it also has the massively major benefit that you know exactly what a variable refers to, because each module can be studied in isolation. You can't do that when everything is in one namespace.

So this has the same benefits that lexical scope and referential transparency give you: you can study different subparts of the system in isolation without worrying about what another part is doing.

Incidentally, that's why dynamic scope is so bad: it's not enough to understand what a single function is doing, you also need to understand what the rest of the program is doing, because some other random part of the program might change the dynamic variable.

That's why "lexical by default, marking certain variables as dynamic" is superior to "dynamic by default": it increases locality because you don't need to jump around everywhere trying to figure out what everything does, you can just focus on one part of the system at a time.

That's the whole point of functional programming, and my language is intentionally designed as a functional language. In fact, I plan for all the built-in data types to be immutable as well, for the exact same reasons. This should also help immensely with concurrency, similar to Clojure.

---

"I guess we have to agree to disagree :)"

I guess so.

-----

1 point by akkartik 5050 days ago | link

"I'll also note that I have not actually programmed in Kernel, so all my talk about "mental overhead" is actually referring to Arc, which is a distinctly unhygienic language."

That is really interesting, that our respective experiences are so different.

I'm with you on "lexical by default" -- I'm not totally crazy :) But the simplest possible mechanism that provides the similar advantages of namespaces is to just warn when a variable conflict is detected, when a global is defined for a second time.

I'm trying hard to introspect here, and I think the difference between lexical scope and namespaces for me is that when I'm programming by myself I don't need a second namespace, but I do still find dynamic scope to be error-prone. My entire belief system stems from that, that one should program as if one was working alone. Everything that helps that is good, anything that isn't needed is chaff.

-----

1 point by akkartik 5050 days ago | link

I like the highlighting; the color makes the typography less jarring. But you're right, it's one of those things one should familiarize oneself with before judging.

-----

1 point by akkartik 5051 days ago | link

Infix?! I'm curious to see how that translates to sexps.

-----

2 points by Pauan 5051 days ago | link

Oh it's very simple. The operators + - * / < > <= >= are the only infix operators (for now). They have the usual precedence rules that other languages use.

How they work is, they take one expression on the left, and one expression on the right, and then wrap em in a list, so that `X + Y` becomes `(add X Y)`, and then "add" is the actual add function.

So they're just syntax sugar for common infix operations, that's all. That's why the last example passed "mul" to "sum" rather than "*".

-----

2 points by Pauan 5052 days ago | link | parent | on: Another language called Nu

Yes indeedy. And as already mentioned in that thread, I liked the name enough to keep it. I figured that since Arc/Nu is a compiler for the Arc language, rather than a full blown Objective-C/whatever Lisp, the confusion should be fairly minimal...

Incidentally, my new language that I've been steadily working on[1] is called "Nulan" which is short for "Nu language" :P

---

* [1]: It's worked for a while now but I still need to get the parser into a good state... turns out, syntax is hard!

-----


Interesting timing... A few days ago I started work on a language of my very own, designed from the ground up. Here's Arc's `alref` implemented in my language:

  $def! ref: type list? any?
    [[X Y] | R] X -> Y
    [~     | R] Y -> ref R Y
Alternatively, if you want to build it on top of `assoc`:

  $def! assoc: type list? any?
    [X | R] Y -> $if: is (car X) Y
                   X
                   assoc R Y

  $def! ref: type list? any?
    X K -> let R: assoc X K
             if R: car: cdr R
I'm still working on the parser and evaluator... once it gets into a semi-decent state I plan to post it on GitHub along with more information, including a tutorial.

-----

1 point by Pauan 5069 days ago | link

Oh yes, and I should mention that I hadn't seen that link until just now, so I wasn't stealing their ideas about pattern matching or syntax. Instead, my language has taken ideas from Kernel, Shen, a tiny bit of Arc, and a little splash of my own ideas thrown in for good measure.

-----

2 points by Pauan 5146 days ago | link | parent | on: Ask Arc tut: what is the trick in this?

"They're both printed to the console, but "arc>" is printed before each command is read, so the output looks quirky."

Not to be a bother, but Arc/Nu and Lite-Nu don't have this problem:

  > car x
  #<fn:car>
  5
I still highly recommend either of them to anybody who wishes for a simple, working, no-nonsense implementation of Arc 3.1.

-----

More