Historically speaking, Arc was put forward as a language for experimentation. If you want to package up code in a squeaky clean format for others to use who don't care about implementation details, you're doing something other than experimentation; you're publishing a product people would supposedly rely on.
Moreover. not even the Arc language is stable (or so this website has always said). If it encouraged people to package up their stable code, that would prove deceptive once the whole foundation fell away. This happened to Anarki when Arc 3 was released; the community had built up lots of Arc 2 code, and not much of that was ported to work on Arc 3.
However, Arc is not without module systems. The first thing I did in Arc was build some namespace macros with explicit imports and exports for my own benefit (Lathe). Andrew Wilcox made "the hackinator" which I think could download Arc programs from a Web-hosted manifest file, most often from GitHub.
More recently, Arc/Nu is an Arc implementation with rich support for first-class namespaces, along with several utilities for modular loading of Arc code that's written in the usual sequence-of-commands style. Arc/Nu is probably the best platform to publish stable modules for, since it can load alternate namespaces to achieve backwards compatibility.
Yes, let is just a simpler form of withs, but it's worth using because it signals that you only need one var and therefore skips extra parens.
withs is the sequential form of with, where you'd like each variable available in defining later variables within the with. You have to say:
arc> (withs (x 1 y (* x 2)) (+ x y))
..but you can use the simpler with in:
arc> (with (x 1 y 2) (+ x y))
Not only can you, you should. Using the most general form when a simpler form will do is akin to crying wolf; it's very useful when reading code to be able to tell simpler parts from more complex parts at a single glance.
So use with to signal that you're defining multiple variables at once. Use withs to signal that there's a dependency between the definitions. If there's no dependency, with is more idiomatic. And if there's only one variable, let is more idiomatic. Finally, a withs with multiple bodies and a trivial body is easier to read if you put the final expressions into the body. This:
(withs (x 1
y (f x z))
y)
is better written as:
(withs (x 1)
(f x z))
and therefore:
(let x 1
(f x z))
(and further to (f 1 z), but let's pretend x is being bound to something less trivial.)
Even if a body is a little more complex:
(withs (x complex-expr1
y (complex-expr2 x)
z (complex-expr3 y))
(car z))
it's sometimes nicer to read as:
(withs (x complex-expr1
y (complex-expr2 x))
(car (complex-expr3 y)))
let, with and withs are nothing but macros, so if you avoid using them you're giving up the benefits of lisp. Might as well go back to a weaker language. In general, my idea when learning lisp was: "Since I come from an imperative background my tendency is to define intermediate variables. Therefore I will start by avoiding all temporaries, and only after I get it working will I insert the fewest possible temporaries to make the code readable." It's stood me in good stead.
c) let doesn't work with more than one variable; so withs is more general (should let even exist?). I can't tell what the difference between with and withs is, and I had picked the latter when I started needing more than one variable. What is their difference?
There isn't. I was considering writing something like quicklisp for Arc, but the downside is that with no module system, recursive loading of dependencies will break two different pieces of code that depend on different versions of a library.
We should probably at least make a wiki page with libraries, though.
Disk serialization of variables & tables is already part of Arc 3.1's fromdisk, diskvar, disktable, and todisk:
$ arc
Use (quit) to quit, (tl) to return here after an interrupt.
arc> (disktable h "/tmp/nested")
#hash()
arc> (= h (obj this "this" that 3 nested-hash-table (obj one "one" two "two" three 3) some-list (list 1 2 "three" 4)))
#hash((that . 3) (some-list . (1 2 "three" 4 . nil)) (this . "this") (nested-hash-table . #hash((one . "one") (two . "two") (three . 3))))
arc> (todisk h)
((nested-hash-table #hash((one . "one") (two . "two") (three . 3))) (this "this") (some-list (1 2 "three" 4)) (that 3))
arc> (quit)
$ cat /tmp/nested && echo
((nested-hash-table #hash((one . "one") (two . "two") (three . 3))) (this "this") (some-list (1 2 "three" 4)) (that 3))
$ arc
Use (quit) to quit, (tl) to return here after an interrupt.
arc> (disktable nt "/tmp/nested")
#hash((that . 3) (some-list . (1 2 "three" 4)) (this . "this") (nested-hash-table . #hash((one . "one") (two . "two") (three . 3))))
But, as I'm sure you're aware, the error with the above is that the serialized table (returned by todisk) has a literal Scheme #hash object nested inside, and when you read that back in, it's immutable:
So, the actual salient operation here has little to do with immutable tables, but rather "deep copying" them, an operator that vanilla Arc lacks. It only has copy:
(def copy (x . args)
(let x2 (case (type x)
sym x
cons (copylist x) ; (apply (fn args args) x)
string (let new (newstring (len x))
(forlen i x
(= (new i) (x i)))
new)
table (let new (table)
(each (k v) x
(= (new k) v))
new)
(err "Can't copy " x))
(map (fn ((k v)) (= (x2 k) v))
(pair args))
x2))
But we could model a deep-copy off of that:
; (map deep-copy xs) wouldn't work on dotted lists, hence this helper
(def deep-copylist (xs)
(if (no xs)
nil
(atom xs)
(deep-copy xs)
(cons (deep-copy (car xs))
(deep-copylist (cdr xs)))))
(def deep-copy (x . args)
(let x2 (case (type x)
sym x
char x
int x
num x
cons (deep-copylist x)
string (copy x)
table (let new (table)
(each (k v) x
(= (new (deep-copy k)) (deep-copy v)))
new)
(err "Can't deep copy " x))
(map (fn ((k v)) (= (x2 (deep-copy k)) (deep-copy v)))
(pair args))
x2))
You'll like the old discussion on Bullet which had some similar ideas, including considering how to suppress paren-insertion inside such a special operator, etc.
I realized that we actually don't need to have a closing bracket to signal the end of the expression. We just need to implement something like the $ operator in haskell.
def (foo a b c)
if $ and
a = 5
b = 10
c = 15
prn "success"
prn "failed"
There should be enough information based off of the indentation to tell how it should be parsed. $ should mean something along the lines of everything indented after the next symbol is a single expression.
Ok, interesting idea that I hadn't considered before.
As it happens, I've been watching a discussion about a different use for brackets: http://lambda-the-ultimate.org/node/4879. Lots of people (scheme, clojure, this liso guy) seem to have an idea about what to use brackets for, and it's not clear what the best use is for these precious punctuation characters.
Thanks for the investigation! Yes, that would be the way to disable transforms inside a quoted s-expr.
But I think that still would leave issues. For one, it is approximately as likely that a list innocuously contains a literal '+ as that we're constructing a fragment of code that is eventually intended to be eval'd. We need a way to say, "this is code" or "this is never going to be eval'd." A second issue is that quoting isn't the only way to read data. Imagine using read to read a list from a file. How would we suppress infix there?
I actually think reading data from a file is the bigger issue. Small quoted lists in code will be noticed, and can be replaced with some (klunky) combination of list and cons. It's far worse if you have a multi-megabyte file that silently gets corrupted because of one character.
I'm just wondering if the second rule is really such a good idea. If one wants to use indentation-sensitivity within parens, it is impossible. I'm not sure how often trying to write code like that would come up in practice, but I think the programmer should have the option in that case of whether indentation-sensitivity is actually being used (by using brackets or something else to make it clear).
I'm going to have to familiarize myself with the internals before I actually try to fix it. After looking for a little bit, I think the issue is in the transform_infix function where it handles the quoting.
if (is_quote_or_unquote(n.elems.front())) {
list<ast_node>::iterator p = n.elems.begin();
while (is_quote_or_unquote(*p)) {
trace("infix") << "skipping past " << *p;
++p;
if (p == n.elems.end()) {
trace("infix") << "just quotes/unquotes " << n;
return n;
}
}
I would think that after you determine that the first element is a quote, you would just ignore the rest instead of going through and checking which ones are also quoted. Since I'm not that familiar with c++ or how the internals of wart work, I'm going to have to leave it to you to see if this is actually the problem.
Yeah, that's a bug. I haven't yet bothered to create a version of read without the infix transform. For now you have to:
'(5 (+) 5)
If you decide to try to fix this I'd love to hear your experiences. The point of wart was to be easy to hack on, but I'm losing steam because it's been hard to get feedback on that score.
I kept looking and found that read has the functionality I was looking for.
(read)
5+5
=> (+ 5 5)
I also noticed that if I want to generate lists which are expressions, it is not as straightforward as it should be (I'm trying to get a list of the number 5 the symbol + and 5 again but wart for whatever reason rearranges them).
1. "Multi-word lines without leading parens are implicitly grouped with later indented lines."
2. "Indentation-sensitivity is disabled inside parens."
That's really all there is to it.
> is there any way to see what my code looks like if I were to type it using parenthesis?
It's a little clunkier than visualizing infix, but you can introspect on any function or macro. Here's an example session:
$ ./wart
ready! type in an expression, then hit enter twice. ctrl-d exits.
def (foo x)
if a b
c d
:else e
foo
=> (object function {sig, body, }) # wart doesn't print table values by default
(body foo)
=> ((if a b (c d) (:else e)))
Here you can see that turning (c d) and (:else e) into calls is probably not desired.
I just quickly tried wart and it does seem pretty impressive to me. The only thing I'm worried about is the rules for how something is grouped. I may just need to use it more to see if it's an actual issue, but I feel that having all of the different rules can make it very easy to write hard to find bugs. Other than that I find wart to be a very interesting idea which will be worth checking out.
Also is there any way to see what my code looks like if I were to type it using parenthesis?
I haven't actually played with it, but from reading the docs[1] Julia's macros -- like Dylan's or Nemerle's -- feel clunky compared to orthodox lisp. The clunkiness isn't caused by infix, though. Infix is easy to desugar, which might be easier to see with wart:
$ git clone http://github.com/akkartik/wart
$ git checkout c73dcd8d6 # wart is currently (and perhaps indefinitely) broken
$ ./wart
'a+b
=> (+ a b)
No, the real trouble lies in their use of f(x) syntax for function calls rather than (f x). As a result there's no way for a when macro to look like the builtin if keyword.[2]
That said, I'm sure it's useful to have even some clunky way to do code-generation. The key is that they stuck with a purely expression-based language, and didn't give in to the siren song of statements.
I saw a presentation by Stefan Karpinski, one of the Julia creators. It's a very interesting language that I'm sad to report that I have absolutely no reason to use. Unfortunately, I don't do any kind of mathematical computing.
I'm not too sad, because I do prefer prefix languages, but if I had to do something in that realm, I would definitely try out Julia.