"Arc uses ar-nil-terminate and ac-niltree to do the Arc->Racket->Arc conversion. ar-nil-terminate does a shallow copy, but ac-niltree does a deep copy.
However, the Arc compiler could be changed so that + uses a shallow version of ac-niltree."
That's the essence of the bug, as I see it. This fix is much shallower than the other fixes discussed, so this fix would make the most sense in a minimally updated variant of pg's Arc.
Should Anarki go for shallow or deep improvement? I've advocated shallow in the past, but now I'm thinking Arc oughta follow through on its promise to "break all your code," which of course means the entire network of hacks, libraries, help resources, and alternate language implementations we've built up so far. It would be nice to see Anarki become a fork of Arc/Nu and undergo deep improvements, without losing the Arc flavor.
Hmm, I'm losing track of my point. I prefer writing code so it models all errors explicitly, so I end up with that kind of verbosity in JavaScript too. I only get code like foo(bar(qux(x))) when I'm using lots of function calls that have no errors (or whose arguments can be errors).
In other words, you usually won't see this in C, right?
foo(bar(qux(1, 2, 3)))
Instead, you'd write it more like this:
x = qux(1, 2, 3)
if (x == ERR) {
...
}
x = bar(x)
if (x == ERR) {
...
}
x = foo(x)
if (x == ERR) {
...
}
Because you don't have nested expressions, there's only a single way to indent the code. But in languages like JavaScript, method chaining and nested function calls mean that there's now multiple ways to indent the same code.
Thus, C's syntax isn't actually more uniform than Lisp, it only seems that way because of C's way of handling errors.
akkartik: "I finally understood Pauan's diagnosis, that it was arc copying nil vs () terminated lists. I've been against that 'feature' for a long time."
Yes, but Arc/Nu manages to implement that Arc feature without conversion, so I don't consider it a problem with the feature, but a problem with the implementation.
---
akkartik: "+ and join on a single arg is just the identity function, right?"
Yes, but Arc does the conversion even if you pass in a single argument. So another possible solution to the problem is to change Arc to return the first argument unchanged when passed in a single argument.
---
zck: "If you call (+ '(((((((1))))))) '(((((((2)))))))) , you shouldn't have to change fifteen conses, just one or two."
Yes, you shouldn't have to. Arc uses ar-nil-terminate and ac-niltree to do the Arc->Racket->Arc conversion. ar-nil-terminate does a shallow copy, but ac-niltree does a deep copy. Simply changing ac-niltree wouldn't work, because I'm pretty sure other stuff depends on the deep copy.
However, the Arc compiler could be changed so that + uses a shallow version of ac-niltree. That's an interesting idea: how much stuff in the Arc compiler is currently using the deep ac-niltree when it could instead use a shallow ac-niltree?
Yes, the outer conses all have to change, except for the last one. But the inner conses don't, and they are changed right now. That, specifically, is my problem with '+ . If you call (+ '(((((((1))))))) '(((((((2)))))))) , you shouldn't have to change fifteen conses, just one or two.
Sorry, I think we've both managed to confuse each other. + and join on a single arg is just the identity function, right? Aren't these the cases we care about?
arc> (+ '(1 2) '(3 4))
(1 2 3 4)
My argument was that in the presence of mutable conses all the conses would have to change. After your response I'm correcting that to "all the outer conses (except in the last arg)".
Does this seem right? I was reading your previous comment without one wrapping set of parens.
So the first of your element conses has to change too.
But you are right that not all the conses need to change. I finally understood Pauan's diagnosis, that it was arc copying nil vs () terminated lists. I've been against that 'feature' for a long time. My private fork just stays with () terminated lists: http://github.com/akkartik/arc
If the language used immutable conses, it still shouldn't have to change every cons. The issue here arises not because the list you pass in has different conses than the list returned, but because the elements of the list you pass in have different conses than the elements of the list returned.
Let's say we call (+ '((1) (2))). I'll draw the list this way:
To my brain, there are two "kinds" of conses in the list: those that make up the top-level list itself, and those that make up the elements.
top-level ( , . , )
conses | `-------> ( , . nil)
| |
| |
| |
element v v
conses (1 . nil) (2 . nil)
And here's what I think should happen in the call to +
these conses ( , . , )
can change | `-------> ( , . nil)
| |
| |
| |
these conses v v
shouldn't (1 . nil) (2 . nil)
Similarly, if you call
(+ (list (obj 1 2)))
Would you expect the hash table to have its elements copied into a new hash table? No, the same hashtable should be shared between the calling list and the returned list, even if the list itself has different conses. And indeed, this is the case (thanks again, evanrmurphy, for tryarc):
"The nice thing about indenting in C is simply that it falls out naturally, so you don't have to think about it."
In my experience with Java, Groovy, and JavaScript, the indentation gets just as idiosyncratic when it comes to nested function call expressions, long infix expressions, and method chaining.
I've rarely used C, but does its error code idiom obscure this drawback? I imagine error codes force programmers to put many intermediate results into separate variables, whether they like it that way or not.
After about a week of work, I got a JS version of Nulan working well enough that I'm willing to show it off. For a taste of how it looks, check out NULAN.macros:
# function and infix syntax works
((-> a b c (a + b * c)) 1 2 3)
# some pattern matching works, some doesn't
((-> 5 10) 5)
((-> 5 10) 1)
((-> @a a) 1 2 3)
((-> @a b {a b}) 1 2 3)
# operator precedence works
(and (or 1 2) (or 3 4))
# almost anything can be used as an expression
-> (var foo 5)
# Arc-style "in"
(in 1 2 3 4 5)
And some more stuff too, like namespaces and the capability to eval things at compile time. I'll keep adding in things and fixing things as I come across them.
Yeah, definitely. The reason for the bug is because the Arc compiler uses Racket's "append" to do the join, which means it first needs to convert the Arc list to a Racket list, do the append, then convert back to an Arc list.
So there's two ways to fix this:
1) Change Arc to be like Arc/Nu, so that it doesn't need to do the conversion. I don't expect this to be too hard.
2) Write + in Arc, and have it call "join" when the first argument is a list. Then there's no discrepency between the two.
I actually dislike how the overloaded + is written in Arc, so I think having it call join instead would be great.
From what I understand, the "canonical way" is basically "whatever Emacs does", but I don't use Emacs. How I indent code (and how I have seen most other people indent code) is as follows:
2 spaces for indentation. Use indentation after any "block" expression:
(def foo (x)
(bar x))
(let foo 5
(bar foo))
Non-block expressions should be like so:
(foo 1 2 3 4)
(foo 1
2
3
4)
(foo 1
2
3
4)
That last one is generally rare and considered somewhat bad form, from what I understand.
Aside from "if", the Lisp code I've seen is generally very uniform, much more so than the JavaScript code I've seen, which can vary substantially from one person to another. For instance, when reading Racket code, it is usually very readable, and the only difference I've noticed is that in some situations they use 1 space rather than 2.
I think the reason Lisp doesn't have a "standard indentation style" is because it doesn't need one. There's no curly braces or distinction between statements and expressions, so people naturally tend to converge on a single style.
And even in the case of the different styles for "if", I at least choose whichever style works the best for me on a case-by-case basis, so that it looks the best. My general heuristic is as follows:
Use this style when there's only 3 arguments:
(if 1
2
3)
Use this style when there's more than 3 arguments:
(if 1
2
3
4)
Rarely use this style, only when all the arguments are short:
(if 1 2
3 4)
For Nulan, I haven't yet worked out the "if" idioms, but thus far I've used this form exclusively:
It's pretty rare that I don't have syntax highlighting. Another thing about the parens that is annoying is that there doesn't seem to be a single, canonical way for them to interact with indentation, like K&R style for C.
I haven't used ruby, but I think terseness and readability tend to go hand in hand, and ruby looks terse.
Having a sugar -> S-expressions transform is great. It's the best of both worlds.
After I wrote that comment it seemed to me that variadic is more fundamental, because you can always convert lists using apply. But you're right that it's not hard to bounce between the two.
Thanks. Your language looks like one I'd like to try. Getting rid of the outermost set of parens is a good idea; the fewer parens that you have to balance, the better. I haven't tried doing any serious programming in a whitespace-sensitive language. I've always found C very readable -- I think C hit a syntactic sweet spot. A lot of people must agree, judging by how many languages have copied C's style.
PS. The one whitespace-sensitive language I use often is make, if it counts. I like make's syntax.
There'll totally be places where it matters whether something conses or not. But that should reflect in the (longer) name, IMO. cons-minimal-append, mayhap?
Well, essentially we already have 'avg:list as the variadic version. In fact, it looks like we can shave off two characters:
(map(fn e avg.e)r t)
(map avg:list r t)
I don't remember ever using 'avg except maybe to average some time measurements at the REPL, and I think the idiom (avg:accum ...) comes in handy for that. But I generally don't use arithmetic operators in my programs, probably due to the subject matter I choose.
While I agree in general, I've found that S-expressions are easier to read than C syntax when you don't use syntax highlighting because the parens naturally group things together. With syntax highlighting, the situation changes so that syntax-rich languages can be significantly more readable.
I also find Ruby to be extremely readable, with or without syntax highlighting. Of course, I can only imagine how contorted and crazy Ruby's parser must be...
In any case, I tend to agree with Paul Graham on the principle that syntax should just be sugar for S-expressions. For instance, in Nulan:
-> a b c (a + b * c)
(&fn (&list a b c) (&add a (&mul b c)))
foo.bar
(&get foo "bar")
[ "foo" 1 "bar" 2 ]
(&dict "foo" 1 "bar" 2)
!(foo bar @qux)
(¬ (foo bar (&splice qux)))
All the syntax expands to plain old S-expressions which can be manipulated by macros.
It took me a while to understand why this should be. cartesian calls mem which calls testify, and testify uses is.
(def testify (x)
(if (isa x 'fn) x [is _ x]))
I think it should use iso. Can y'all think of any reasons that's a bad idea?
If we make that change, I'm not too concerned about whether + is like join. A caller of either shouldn't typically care whether they create new conses or not.