5 ms·
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
by Jach 5y ago
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 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-lapalme.pdf 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.
- nlitened 5y ago> 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.
- Jach 5y agoNeat, 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.jpeg 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.