Thanks! I woulda missed that if you hadn't posted this. It got me to finally post a long-gestating comment on arc's continuation-based webapp framework: https://news.ycombinator.com/item?id=5032739
"This process allows [many] continuations captured by your servlet to be serialized. This means they may be stored on the client’s browser or the server’s disk."
Oh, I was particularly responding to "It also turns out that there's at least one company trying to scale continuation-based servers," which you said afterward. Guess you chose the example you chose, no worries. ^_^
Ah, good point! I didn't think of it then (I think I even had the racket link in the textarea in a different context before I ended up dropping it to the floor.)
Well, at least you got a response from pg regarding Arc. Now, about that outdated Arc home and install page ... :)
EDIT
More seriously:
pg "In retrospect YC would have taken over my life no matter what. It has pushed out essays too, mostly.
I don't consider Arc to have died, incidentally. You used it to say that, and I'm using it to reply. If I ever retired from YC I'd probably start working on it more actively again." [1]
The thread was quite revealing. Or perhaps it just said what we all probably already knew.
My favorite comment on the HN thread[1] so far points out a new design tradeoff rather than retreading tired aesthetic preferences:
"..indentation is not affected by the lengths of identifiers. It’s silly to format code in such a way that you need to re-indent just because you renamed a function."http://news.ycombinator.com/item?id=4993847
"..indentation is not affected by the lengths of identifiers. It’s silly to format code in such a way that you need to re-indent just because you renamed a function."
That silliness is hard to avoid. If we don't use word wrap, we have to re-indent code when an identifier changes just so things stay on the screen. To avoid that re-indentation, we should probably redesign our text editors, diff tools, and languages so word wrap isn't a pain to work with. I think I'd rather do a little re-indentation for now.
---
It's not quite the same topic, but for a long time,[1] I've considered indentation an enemy to brevity. All that wasted whitespace! :) Arc's a:b ssyntax is great at eliminating levels of indentation.
I don't quite know why, but I like to be pretty strict. I keep every line at 70 spaces long or less, with tab stops every 4 spaces, except for the following scenarios:
- Commented-out lines of code.
- A line of code that just can't be broken up any further. But first I try to move this line to a less indented part of the code, and then I remove other indentation on the line until it fits the best it can.
- Code that someone else has already made too long by my judgment. I try not to change someone else's style if my cosmetic change would obscure the meaningful changes I'm making, or if it would introduce inconsistency in the codebase. In this case the codebase includes any part of the revision history, branches, forks, and related projects that might be reintegrated.
In college I used 80 spaces, but that turned out to be too long to fit on my Wordpress layout without scrolling.
---
I thought it was also too long for Arc Forum, but it seems that's not true. Looks like Arc Forum supports 83 characters, at least on my user agent.
When I switch from Chrome to Firefox, it looks like the 84-character line doesn't scroll anymore. In general, I bet it depends on the browser's default fonts and such.
"Why not just use actual pattern matching for the function arguments, like Nulan does? You can still use function-name-based lookup for calls. Then it would only be the arguments of the function that are pattern matched."
Yeah, the combination of permitting arbitrary things in function position and haskell-style pattern-matching allows us to emulate full-scale pattern matching. I hadn't noticed that before. I wonder if there's a cleaner way to integrate it completely with the operator-precedence parser.
"I wonder if there's a cleaner way to integrate it completely with the operator-precedence parser."
I assume you're talking about Nulan since as far as I know, wart doesn't have a full-blown parser. In which case it works just fine in Nulan, because the parser runs before everything else (including macros).
Patterns aren't first-class in haskell. Are they first-class in Nulan? If not, I was thinking aloud that Nulan's parser might be able to subsume patterns somehow.
They might be, once I make them user-customizable. At least, as "first-class" as macros.
---
"If not, I was thinking aloud that Nulan's parser might be able to subsume patterns somehow"
Well, the way I designed it, Nulan's parser is stand-alone: you can run it without any of the other Nulan stuff.
That's because the parser is really very simple. It's only concerned about lists, symbols, numbers, and strings. It has no knowledge of boxes, or macros, or anything like that.
This is by design. Although it would be possible to integrate the parser more closely into Nulan, I like the clean separation of concerns.
Patterns are currently executed during the macro expansion phase, so that's already long after the parser has run. In addition, while the parser operates everywhere, patterns can only be used in certain places.
How they work is pretty simple. Using the not-yet-created $pattern-rule macro:
$pattern-rule foo -> {_ pat} val body
'val + pat + body
Let's look at this program:
(-> (foo 5) 10) 20
We're creating a function "-> (foo 5) 10" and then calling it with the argument 20.
Now, it will call the "foo" pattern with the following arguments: (foo 5), a unique variable, and 10. These represent the pattern, the argument to the function, and the body, respectively. Whatever the pattern returns is used as the body of the function.
After running the pattern, the end result is this:
(-> u (u + 5 + 10)) 20
Let's look at another custom pattern:
$pattern-rule and -> {_ @pat} val body
pat.reduce
-> x y
pattern-match y val x
body
This time we're calling the "pattern-match" function, which allows the pattern matching to be recursive.
Using this system, it's easy to do, for instance, list destructuring:
$pattern-rule list -> {_ @pat} val body
w/box i = -1
pat.reduce
-> x y
pattern-match y ('val[,(++ i)]) x
body
And now this program:
-> (list a b c)
prn a b c
Will get translated into this:
-> u
| box a = u.0
| box b = u.1
| box c = u.2
| prn a b c
Though, the above "list" pattern doesn't support the @ splicing syntax, which is much more complicated to implement.
Thanks. It sounds like the basic question is: is selective evaluation useful enough to merit using up quotes inside param lists? Because if we didn't have it, we'd be able to use quotes for pattern matching.
I'm not sure what you mean. Wart already uses macro-like vaus so it already uses implicit evaluation rather than explicit like in Kernel. And what else could quote mean in an argument list other than to distinguish variables from literal symbols?
"if-box is a kinda contrived example. Perhaps we can find something better?"
If you have a better suggestion, I'm all ears. But I settled on that because A) it's simple, B) it's short, C) it's something that does actually come up in practice, so it's not too contrived.
---
"What's the difference between w/uniq and w/box? Both seem to create boxes."
Well, to explain this... unique variables are actually implemented as anonymous boxes. So implementation-wise, there's not much difference.
The difference is in the actual macros "w/box" and "w/uniq". Here's the definition for them:
$mac w/box -> @args body
'w/new-scope
| var ,@args
| body
$mac w/uniq -> @args body
'w/box ,@(args.map -> x 'x = make-uniq;)
body
The answer is that "quote" inserts boxes for global symbols, but values for local symbols. That is, this macro...
$mac foo ->
w/box bar = 5
'bar + 2
...returns "5 + 2", not "#<box bar> + 2". This is an unfortunate inconsistency which happens because I'm using JavaScript variables for the sake of speed.
I thought about having it throw an error for local symbols, which would have required you to write the macro like this:
$mac foo ->
w/box bar = 5
',bar + 2
But I decided that it wasn't worth it. Now that I think about it, I wonder if it would be possible to hack something up so you no longer need w/uniq...
---
"I tried running this and got the following"
It works fine for me. And in fact, the code snippet you posted seems exactly the same as what is already in the tutorial. Maybe you intended to paste something else?
As for the error... yeah, there are still some cryptic errors. I plan to make them more meaningful later.
"Now that I think about it, I wonder if it would be possible to hack something up so you no longer need w/uniq..."
After thinking about it some more, I realized it won't work very well. It would require me to make all variables global, which would slow things down and be more verbose.
I've learned through bitter experience to treat all C warnings as errors, and more. The presence of a single uninitialized local variable somewhere in your program makes the entire program undefined. Where undefined means "segfaults in an entirely random place."
I think that's a good practice in general. But when you are experimenting and debugging, it can be useful to eliminate chunks of code by expedient means, which often generates warnings that you don't care about.
I find programming to fractally involve debugging all the time. So if I allowed warnings when debugging I'd be dead :)
You're right that there are exceptions. I think of warnings as something to indulge in in the short term. The extreme short-term; I try very hard not to ever commit a patch that causes warnings. It really isn't that hard in the moment, and the cost rises steeply thereafter.
Incidentally, I'm only this draconian with C/C++. Given their utterly insane notions of undefined behavior I think it behooves us to stay where the light shines brightest. Whether we agree with individual warning types or not, it's easier to just say no.
But with other languages, converting errors to warnings is a good thing in general. Go, for example, goes overboard by not permitting one to define unused variables.
Second shameless plug: I've changed Nulan significantly since I last showed it off here. Here's a page describing Nulan's syntax system, which I believe is superior to all existing Lisp syntax systems:
If I follow everything on that page, this allows you to programmatically control where lists are segmented, and to generalize infix, resolving function/macro calls to be anywhere in the list. In other words, macro foo doesn't have to look like
(foo ...)
it can also look like
(... foo ...)
Does this make the grammar context-sensitive? Does it often introduce ambiguity? Have you run into non-terminating parsing? Can you talk about how it compares with oMeta? (Are you sure you aren't greenspunning it? :)
"If I follow everything on that page, this allows you to programmatically control where lists are segmented [...]"
Something like that, yes.
---
"[...] resolving function/macro calls to be anywhere in the list. In other words, macro foo doesn't have to look like"
Not quite. The parser is very simple: it just pushes symbols around. Which means that when you use a syntax rule like this:
$syntax-infix "foo"
Then the parser will rewrite this:
1 foo 2
Into this:
foo 1 2
This all happens before macros are expanded, so macros receive a uniform list representation. In this way, it's similar to wart's system, except that it's much more flexible and powerful.
---
"Does this make the grammar context-sensitive? Does it often introduce ambiguity? Have you run into non-terminating parsing?"
I have no idea, no, and no. It's all just simple list transformations, similar to what macros do.
---
"Can you talk about how it compares with oMeta? (Are you sure you aren't greenspunning it? :)"
I haven't use oMeta, but from what I understand, it uses something like PEG parsing, which is completely different.
Think of my system as being like macros: you take this list of symbols and that list of symbols and return this list of symbols.
The difference with macros is that my system has left/right associative, prefix/infix/suffix, precedence, and a slew of other options too.
The key insight is that unlike most syntax systems which return a single expression, Nulan's syntax system returns a list of expressions.
And then the syntax rules operate on this list, which effectively lets them look-behind and look-ahead arbitrarily many tokens, but only within the list.
PEG parsing lets you look-ahead as many tokens as you like, but not look-behind. Nulan's system supports both, but the amount of look-ahead/behind is controlled by the indentation, so everything is handled in a consistent way.
The - syntax rule has a precedence of 70, which is equal to 70, so it stops parsing. It now calls the action function for * with the arguments left, symbol, and right, which returns {* 50 20}
Now we go back to the + syntax rule, which looks like this:
It calls the action function for + with the arguments left, symbol, and right, which returns {foo {+ bar {* 50 20}}}:
left: {foo {+ bar {* 50 20}}}
remaining: {- 30}
Now it continues parsing with a precedence of 0. - has a precedence of 70 which is greater than 0, so it recursively calls the parser with a precedence of 70: