If you take a look at the code for HN written in Arc, you can compare it against other Lisp dialects. I point to this as it is possibly the most widely-used and familiar application to most here written in Arc.
This is a great place to start with understanding Arc, as the jump to Anarki (an Arc fork) will make more sense as it comes bundled with News - a Hacker News style app - which is fun to play with if you have experience with HN and want to try a variation of this site.
To me, one of the most fundamental features of Arc is taking the unification of code and data much further. I don't know of other Lisps that do this! K is another language that does.
arc> (= l '(1 2 3))
(1 2 3)
arc> (= f [+ _ 1])
#<fn: f>
arc> (map l '(0 1 2))
(1 2 3)
arc> (map f '(0 1 2))
(1 2 3)
No offense, but I found your example code really confusing.
For the benefit of people familiar with Common Lisp:
* (= ...) is assignment, not equality testing
* (map list index-list) is equivalent to (loop for index in index-list collecting (nth index list)), or in a more FP style (mapcar (rcurry #'nth list) index-list)
* (map function list) is equivalent to (mapcar function list)
I think your example would have been less confusing if it used larger numbers to highlight where addition was happening and where indexing was happening:
arc> (= l '(4 5 6))
(4 5 6)
arc> (= f [+ _ 10])
#<fn: f>
arc> (map l '(2 1 0 0))
(6 5 4 4)
arc> (map f '(0 1 2))
(10 11 12)
What is the logic here? Is l made into a function that holds state and returns each element in turn (like an iterator function)? Because I would've expected:
I don't like the Arc language because it's dynamically typed and thus inherently unsafe (see other threads for this sort of discussion.)
But I like this particular feature. If you think about it, an array is a mathematical function, or map, from indices to values. So it makes sense to be able to apply it to indices to get the respective values.
I think the list '(0 1 2) is behaving like the function — in CL syntax, sorry, I don't know Arc — (lambda (x) (elt '(0 1 2) x)). That is, the list is being treated as a sequence, which is a function from each index to the corresponding element.
This was discussed a lot before (according to Queinnec book at least) but people were worried that too much overloading would yield confusing code and bugs.
First public release of Arc came not long after Clojure.
Clojure is definitely better thought out as a language, and while Arc has some interesting ideas around web development, it also doesn't have a module system, so everything is just loaded into the same global namespace, which is pretty insane.
Keep in mind that Arc is the start of something with long-term goals; further, it's part of a lifecycle that didn't necessarily start with Arc but with the first Lisp. PG points to an early essay titled "The Hundred-Year Language" [1] as an indicator of where Arc is intended to go and some future outlooks that might inspire more evolution.
I don't know that any programming language can be everything it needs to be from the outset, but it certainly needs inherent featured supporting longevity, a framework that supports change and evolution, while still having a pleasing and useful function that can be taken advantage of immediately.
Ah, that makes sense. The next main difference in my mind is ergonomic, but relies on this regularity: functional syntax for composition, negation, quoted application. There are also other syntax additions and in my experience they're well-chosen and go a long way.
It would be more correct to say that they can be coerced to a function with a special purposes (e.g. using a set as a function is an alias for checking whether an item exists).
To say they are functions would be incorrect. By the same logic, a keyword is would be a function.
They're callable as functions, i.e. they implement clojure.lang.IFn. So unless you want to shave hairs on what it means to say they're not "functions", they _are_ functions. Sets, vecs, maps, keywords, and IIRC symbols are all IFn
Oh and (#{1 2} 3) is equivalent to (get #{1 2} 3) not (contains? #{1 2} 3)
I think that it depends upon what you interpret it when you say “it’s a function”; it’s data as well. Which, now that I reconsider it, was kind of the point that the parent was trying to make, that they’re also very much unified.
The curse of Common Lisp is that every good programmer who tries it wants to improve it. Which always results in a weird product with less, rather than more, appeal.
I'm not nearly versed enough in either lisps or Arc to comment on it as a language, but as far as the architecture of the forum is concerned, what is the rationale for functions that appear to just print CSS and javascript[0]?
Wouldn't it be better to separate CSS and JS into separate, static files and only have the forum deal with, at best, IDs and classes in HTML?
I'm asking because I'm tempted to actually try to fork it and make a PR for it but it seems like such a simple and obvious bit of housekeeping that I have to assume there's a reason no one else has bothered yet.
I don't really get why you'd ever want a Lisp-2. Is there an argument/explanation somewhere that I could read up on?
(I've used Clojure and JS, which also seems like a "Lisp"-1 in that you can just put fns into variables and call them like 1st class fns, I just don't get why the distinction is in any way positive rather than confusing, and in Lisp-2s you now need to dereference everything all the time, like in Ruby).
If I understand it correctly, Lisp-2 is a way (not the only way!) of maintaining more intuitive semantics in the face of macros. If I write the following:
(your-macro
(let ((x (foo y))
(bar x))))
I probably don't want the meaning of FOO or BAR to be up for grabs based on the expansion of YOUR-MACRO, and certainly not LET. Lisp-1s often have hygienic macros to deal with this concern, but not always.
Completing your thought (please let me know if I misunderstood you) Y _is_ up for grabs because what you wrote might macroexpand to for example (let ((y 5)) (let ((x (foo y))) (bar x))).
Where your comments falls down is that all the Lisp-2s I know allow the "function definition" of a symbol to be overriden in a similar way, e.g., in Common Lisp the macro might expand to (flet ((bar (arg) (* 5 arg))) (let ((x (foo y))) (bar x))) where FLET is a macro similar to LET but for function definitions.
Where your comment falls down is that in a Lisp-1, every let is also a flet.
Also, of course, you must use gensyms for any locally bound identifier, whether it is a let or flet. Nobody said that Lisp-2 allows for labels and flets without having to use gensyms; nobody in their right mind is going to lexically bind identifiers in macro-generated code that don't use gensymed names (other than in cases when there is no possible capture).
Lisp-2 addresses (in a good-enough-beats-perfect way) the following problem: the programmer's code wrongly capturing references that the macro would like to use.
The macro wants to generate some (foo ...) call to a function in the global environment. But, oops, in a Lisp-1, a user's local variable foo takes this. In a Lisp-1, the user would have to have a local function by that name.
If global functions have reasonably descriptive names, and local functions are used sparingly, the potential for a clash is low.
We can have a warning when a local function shadows a global one; it won't be too much of a nuisance since global functiions tend to use descriptive names, and local functions are relatively rare compared to local variables.
Yes, that's a good point. Multiple namespaces arose in Lisps before things like FLET and LABELS, so this particular issue is likelier to come up in Common Lisp nowadays. I think there are several historical considerations in play and https://www.dreamsongs.com/Separation.html (as mentioned) is probably the best source for more information.
A lot of the comments mention reasons. In my experience, once you get used to a lisp-2 programming in a lisp-1 feels limiting: you can’t just write
(def str “foo”)
In Clojure, because you’ll shadow the builtin definition of str (which leads to really weird bugs). But, this means that there’s one more thing to think about when naming your variables.
Additionally, I think we’re pretty used to “separate namespaces” for nouns and verbs in normal languages: it’s fairly rare, for example, for the word “run” to be ambiguous between its noun use and it’s verb use, in the context of a sentence.
Lisp-1 fails to prevent the situation that the left position of a form is treated specially, both syntactically and semantically. Yet, the evaluation-strategy is sold that way to programmers: "look, symbols all positions of a compound form are treated uniformly".
It is not true syntactically, because special operators are recognized only in the leftmost position, as are function-style macros.
But it is not even true when all symbols in the form are variable bindings. Because at some point, the function is called, and that is not uniform. The leftmost thing is treated as a function being invoked, and the others as arguments being passed. These are different categories. We take the leftmost object and activate its ability to behave as a process; and we don't do that for the other objects. That's effectively a different semantic space.
Objects potentially have two "semantic bindings": a binding to the ability to behave as a function (denoting a process which takes arguments and produces values, and possibly side effects) and the trivial binding to the value that they denote; e.g. the integer object 3 to the abstract integer three. These bindings are two spaces, effectively. Therefore, Lisp-1 doesn't get away from two spaces. It resolves the leftmost object of a call form in the "function behavior space" and the remaining objects in the "value denotational space", in order to bring about a function call.
Lisp-2 has a cleaner story/explanation of operators. It simply embraces the idea that the left position and argument positions are different, through the entire evaluation stack, rather than trying to pretend they are the same.
In a Lisp-2, you cannot write an expression which refers to an operator as if it were a variable. Whereas in Lisp-1, meaningless nonsense like (progn progn) is possible, in a Lisp-2 this can exist with a meaning. The potential that we can give any form meaning is a core theme in Lisp. The fact that we can bind a progn variable so that (progn progn) works is more "Lispy" than having to give up and conclude that it's an absurdity.
To a certain extent it's just syntax sugar. It's relatively easy to implement a lisp-1 with a macro in a lisp-2 and vice versa.
At this point I'm so used to lisp-2 that I make all sorts of silly mistakes when I program in a lisp-1. In English, a word could be a noun or a verb depending on its position in a sentence, so there is a parallel there. Whether or not it's a good thing is a matter of taste.
The only clearly objective difference is that lisp-1 is simpler, which is probably why most lisps created in the past 30 or so years are lisp-1s.
Yes, JavaScript also has a single namespace, like a Lisp-1.
The advantage of a Lisp-2 is that it makes macro writing easier; you can include calls to known functions in the expansion without having to deal with the possibility that those names have been shadowed.
A Lisp-2 can treat function bindings specially at the compiler level.
In addition to the simplified hygiene, one consideration is that Lisp dialects typically have mutable variables. But the ANSI-Lisp-style labels and flet forms bind functions immutably. Thus a compiler never has to suspect that a local function will change.
In TXR Lisp, developed a way to combine Lisp-2 and Lisp-1 into a single dialect, to get the best of both with almost no downsides. In a nutshell, the square bracket syntax performs Lisp-1 style evaluation: [a b c] means expand/evaluate a, b, c in the same mannner, and then treat the value of a as a callable object which receives the values of b c.
Furthermore, any arguments of [...] which are symbolic (after macro-expansion) are treated in a combined namespace which contains both function and variable bindings. When the symbol is global, if it has both kinds of bindings, preference is given to the variable.
This [a b c] is a sugar for (dwim a b c), which is a special operator that is recognized properly by the macro expander, interpreter and compiler, including the various shadowing corner cases between macro and ordinary bindings.
Thus, there is no reason to have to choose between Lisp-1 and Lisp-2.
However, the implementation is a bit more complicated than just Lisp-2 alone, never mind Lisp-1.
The most commonly expressed reason is that you should be able to use variable names like "list" without shadowing the list function.
Someone with insight mentioned that there was a belief that it would be easier to optimize separate namespaces, but that reality proved them wrong, but I haven't verified that claim.