You'll need to elaborate more, apologies in advance to your thumbs. Some of the features you describe I don't understand (ability to read, maybe others). Others seem to already be in Arc (library support, iteration, destructuring bind; [_ 'user] doesn't seem much worse than :user). Nil values feels like the only definitely missing feature, and it feels more like a fork in the road with different trade-offs rather than one alternative being always superior to the other.
Reading that output is beyond a headache. It should be against the law! But more importantly, when writing code/troubleshooting you can't copy the evaluated output make a modification and then call the function again. I know you can bind it and apply functions to modify it, but often that's a real hassle compared to copy/paste.
Clojure has DOZEN's of helper functions (and important traversal functions) that don't exist in Arc. Yeah I could write them, but I still have to. I was highlighting the ease with 'separate', but here's even 'deep-merge'....
(defn deep-merge
"Recursively merges maps. If keys are not maps, the last value wins."
[& vals]
(if (every? map? vals)
(apply merge-with deep-merge vals)
(last vals)))
> (deep-merge players {:joe {:weapon "LongBow"} :jane {:weapon "Lance"}})
It's not even these sample cases that expose the issues. It's about working with REAL data. I have many files containing hash-maps that are 15 levels deep. If I were to attempt pretty much anything, but the most simple cases, in Arc I would hit problem after problem after problem. Which I did. So fine... really I should use a DB, right? ok let me just grab that DB library in Arc....
-- Nil values --
I'd even be fine with any falsey values like boolean false. But Arc will not even store an #f value. To suggest I can't pass in REAL data with falsey values is really limiting. I can't pass in an options table argument. For every operation I would have to add more data or nuanced data or nuanced operations to support "this value was set, but it's set to 'NO' I don't want that option.".
The Y combinator itself is more cumbersome, having an extra currying step or two. I prefer the form hjek is using—which is a function that expects to take "itself" as an extra parameter, like this:
(fn (f i)
(aif i!parent
(+ 1 (f f (item it)))
0))
So the recursive call, "(<self> (item it))", is implemented as "(f f (item it))". And then usage is very simple: actually give it itself as an extra argument.
The Y combinator works with a different function signature:
That is, the function takes "something that's not quite itself" as an argument, and returns a function which does one step of computation and may do a "recursive" call using the thing that was passed into it. The implementation would therefore like to be:
(def fix (f) ;aka Y
(fn (i)
((f (fix f)) i)))
But, if we're doing the entire thing with anonymous recursion, we can (laboriously) implement fix like this:
Every recursion step involves creating multiple lambdas. Eek. (It's even worse if you use the general, n-argument Y combinator, in which case you must use "apply" and create lists.) Whereas with hjek's non-curried approach, only a constant number of lambdas have to be created at runtime. (Optimizing compilers might be able to cut it down to 0.)
If you want to create a macro like afn or rfn, and want the user to be able to act like the function is named F and accepts just the parameter i, you can put a wrapper into the macroexpansion, like this:
(rfn F (i)
(aif i!parent
(+ 1 (F (item it)))
0))
->
(fn (i)
((fn (f)
(f f i))
(fn (f i)
(let F (fn (i) (f f i))
(aif i!parent
(+ 1 (F (item it)))
0)))))
And in this case, while the code does call for creating an F-lambda on every recursive call, I think it's easier for the compiler to eliminate it—I don't remember whether I'd gotten Racket to do it. (I think it probably did eliminate it when working with Racket code, but Arc, which generates all the ar-funcall expressions, might not have allowed that.)
The actual code for rfn will create a variable and then modify it, creating a lexical environment with a cycle in it. That's certainly a more straightforward approach. I figure the above is useful only if you're working in a context where you really want to avoid mutation or true cycles. (For example, I am considering a system that detects macros whose expansion is completely side-effect-free. It might be easier to use the above approach to defining iteration than to teach the system that rfn is "close enough" to being side-effect-free.)
Check out John Shutt's thesis on Kernel: https://web.cs.wpi.edu/~jshutt/kernel.html. He takes your observation that quasiquotation is pointless to the limit, dropping it entirely and constructing his macros out of cons and friends.
Thanks for the pointer. He has an entire chapter on hygiene... And then there is this:
"There is no need to provide quotation here because, having failed to enforce the prohibition against embedding combiners in a macro expansion, we don’t need to embed their unevaluated names in the expansion."
It's nice that his primitive $vau grabs the current lexenv, as this enables another kind of macro-like behavior I've thought might be useful: taking subexpressions, fully macroexpanding them, and doing something with the result (e.g. determining whether a variable is ever used, and omitting a computation if not). I don't know how that would mesh with the interpreter's semantics, though...
I'll probably have to read and brood on this further.
I actually tend to the opposite: use with everywhere unless I need withs. The reason isn't performance. It tends to make code more elegant to not rely on the order in which things are defined. And when I'm reading code, with gives me the warm fuzzies that the code is going to be cleaner. When I see withs I slow down to look at the dependencies between the bindings.
Similarly to this, when I'm writing Java, I use `final`^1 everywhere I can. It's nice to be able to know that anywhere later where the variable declared final is in scope, it will have the same value as at the point it's set. I don't need to look through any code to see if it's rebound; I know it hasn't been.
[1] "final" is kind of like "const", if I understand `const` right. `final int x = 3;` means that it is an error to later have the line of code `x = 4;`.
arc> (help get)
[fn] (get i)
Returns a function to pass 'i' to its input.
Useful in higher-order functions, or to index into lists, strings, tables, etc.
Examples:
arc> (get.2 '(1 2 3 4))
3
arc> (get!b (obj a 10 b 20))
20
arc> (get.9 sqrt)
3
arc> (map get.2
'((a b c)
(1 2 3)
(p q r)))
(c 3 r)
nil
arc> (help set)
[mac] (set . args)
Sets each place in 'args' to t.
nil
These are the functions you end up calling because your dispatch can't see the earlier get and set bindings.
I did ping the HN admins about the lock period a year or two ago, and they were kind enough to extend it for us. It's now 90 days, if I recall correctly.