News for hackers. Stuff that matters.
Re:Differences with other Lisps by Jach on Wednesday November 03, 2021 @04:26AM (#29092234)
Using [] for syntax like Clojure does is kind of weird and not too consistent IMO. As for data structures, CL has vectors (and more complex arrays), with #() as a literal syntax or my preference just the function (vector ...). But beyond that, CL is just not nearly as opinionated as other languages. You are free to define a function named [] as a wrapper around aref, if you wanted to write things like ([] array index), or you can define a reader macro that uses [ or ] and use them to implement whatever syntactic sugar you desire. That might be as an alternative to #() for vector literals and similar to Clojure, or something that mixes in commas and is more similar to JSON (https://gist.github.com/chaitanyagupta/9324402), or it could be to use them as syntax where you'd usually use ()s, or as some lambda shorthand e.g. [* _ 10] as shorthand for (lambda (_) (* _ 10)), or as sugar to wrap a special text type (e.g. [hello world] produces an object that wraps the string "hello world"), or to have List Comprehensions e.g. this paper from 1991: https://3e8.org/pub/scheme/doc/lisp-pointers/v4i2/p16-lapalm... whose code (that still works) in figure 2 + the bits right after to the set-macro-character calls lets you write things like [x (x <- xs) (oddp x)] to filter out odds, or [(* x x) (x <- xs)] to get squares, and you can do things with multiple lists. Or something else. Ultimately, it comes back to CL letting you do what you want.
Re:Differences with other Lisps by nlitened on Wednesday November 03, 2021 @05:17AM (#29092474)
> Using [] for syntax like Clojure does is kind of weird and not too consistent IMO

Actually, it’s very consistent. Round parentheses are used for syntax where the first element is a “command” that will be evaluated, and square brackets are used for list of things where the first element is not “special”.

For example, in `(let [x 0 y 1] (conj [2 y] x))`

- `let` and `conj` are commands that will be evaluated, and the rest of the syntax list are arguments to that command

- `x` and `2` are not commands to be evaluated, they are just members of the syntax list with no special evaluations.

The only inconsistency with this rule that comes to my mind is `case` with multiple matches, but outside of that it’s pretty consistent, and I started to like it after it “clicked” in my head.

Re:Differences with other Lisps by Jach on Wednesday November 03, 2021 @07:30AM (#29093249)
Neat, I don't remember hearing about the "command" justification before. I suppose this is a rationale behind e.g. the 'ns macro separating its command-keywords that map to functions with parens? Still, I always thought the full syntax of 'ns and the individual things inside of it was a bit wild. Thank goodness for Slamhound...

Like, take (import [package thing-in-package other-thing-in-package]) -- the first element is "special" in how the rest are interpreted. 'gen-class takes :methods as [[method-name [arg-types] return-type]] while 'letfn separates the inner functions with ()s. Multi-arity functions use () as the separator, too. But why not []? Is the [foo] arglist^H^H^H^Hvector in ([foo] body) in (defn blah ([] body) ([foo] body) ...) meant to be a "command"? Or the name of the local function in 'letfn a command?

Using "nothing"/implicit argument pairs like in 'case/'cond/'extend-protocol/... instead of wrapping them in a vector like 'let can feel inconsistent, but it's understandably popular even if it can make things harder to edit or easier to introduce bugs. Writing things that way is a common introduction-to-macros for CL, too, and pair-lists are often used for literal maps rather than importing something that makes a hash table. Looking at Clojure's 'case again though I guess you were mentioning its ability to match multiple values with (x y z)? Ok, but it seems to me just a consequence of porting behavior from CL, not a big deal.

It still strikes me as weird that Clojure introduced nice data literal sugar, except I can't be sure that when I see it that it's always a data literal. When I see [], it might be a literal vector with every element evaluated, or it might be embedded in a context with complex syntax and effects. Like sometimes it's just a sequence of binding forms such as in simple function arguments, sometimes there's an implicit pairwise association going on like in 'let, sometimes the first element determines how the rest are interpreted, or there might be keywords thrown in the middle that determine the rest. This problem extends to some popular libraries, too (e.g. Reagent's syntax for components always bugged me). In practice it's not a huge deal, sure, and I liked it for a while, then I got used to CL and prefer its idioms.

All this isn't to say that CL is a paragon of consistency (see kludges: https://stevelosh.com/static/images/blog/2018/07/lisp-kludge...) and when it comes to syntax you still have to deal with similar issues of interpretation (CL lambda-lists have some rich behavior, the loop macro has many features, format strings too, etc. not to mention stuff in libraries, and custom reader macros doing whatever), but I am saying that I don't think Clojure's introduction of [] and {} for core syntax on top of their roles as data literals actually made things any better.

Re:Differences with other Lisps by nlitened on Wednesday November 03, 2021 @08:07AM (#29093502)
Oh yeah, you're right, multi-arity fns are also a big exception to the empirical "rule" I mentioned. And yes, (ns) macro looks like a historical mess indeed.

> but I am saying that I don't think Clojure's introduction of [] and {} for core syntax on top of their roles as data literals actually made things any better

I think of it as embedded JSON so that it's easier to type data structures, and with Clojure you end up with _a lot_ of data structures to type, since Clojure strongly encourages data-oriented programming.

Disclaimer: though, Clojure is my first and only lisp I've used professionally, so I am for sure missing (by not knowing better) many goodnesses that Common Lisp brings to the table.

Re:Differences with other Lisps by Jach on Wednesday November 03, 2021 @10:14AM (#29094865)
Right, as data literals my program is going to use they're great, I fell in love with Python a long time ago in part because of its convenient data syntax. Clojure's values-are-values philosophy is also amazing. How much value are they though as core syntax forms? Take keyword parameters for instance. In Clojure you pass to defn something like [a & {:keys [x y]}]. In Common Lisp, that would be (a &key x y). If you want to add default values, in Clojure you'd change to [a & {:keys [x y] :or {x 1, y 2}}]. In Common Lisp, that would be (a &key (x 1) (y 2)). They're both data structures, they both require interpretation by the human and the defn/defun implementation, they're both not eagerly evaluated like other uses of data literals would be (neither the sequences nor the symbols need to be quoted). Apart from Clojure strangely bucking tradition with everything by making the top structure a vector instead of a list, and a quibble of repeating yourself for default mapping, I don't really think one is definitively better than the other, but that also means I don't think Clojure improved on anything with that choice.
Re:Differences with other Lisps by lispm on Wednesday November 03, 2021 @03:08PM (#29098967)
In Common Lisp the arglist is not a runtime datastructure, unless one uses something like APPLY. This means that the usual implementation (compiler/interpreter) will check the declared keyword arguments: is it duplicate, is it used, ...? For calls it can be checked if the keyword is known. That's usually all done by default.