I've been messing with Clojure/ClojureScript for a few years having previously had zero Lisp experience. Overall, I think Clojure does a good job of being both practical and lispy. It's a language that is for building real things.
I've been focusing on ClojureScript (https://clojurescript.org/) as you get the benefit of interoperating with the Javascript ecosystem. The fact that there's a strong community around both Javascript hosted and Java hosted gives a wealth of library options.
Overall, the tooling has been getting a lot closer to the sort of experience that contemporary developers expect. The Calva plugins integration with Visual Studio (https://calva.io/) makes it easy to get started - you can even run it online with gitpod (https://github.com/PEZ/rich4clojure).
That just leaves learning the language - the slight changes in syntax (brackets for different data types) definitely help early on, and for the most part Clojure discourages people going down the path of macros which means reading other peoples code is reasonably accessible. The main struggle is that it's a language used by a lot of advanced or full-time developers, so documentation is pretty dense and it can take a real commitment to understand the detail.
It may not be 'correct' enough if you're coming from other Lisps, but coming the other way from C/Python etc I've found it an accessible and practical option.
> Overall, I think Clojure does a good job of being both practical and lispy. It's a language that is for building real things.
As opposed to? Common Lisp and Emacs Lisp are both highly practical. Scheme you might call more academic, but that's not really what people think of when they say "Lisp".
CL is not really highly practical. The stdlib is not very coherent and lackluster in general (do I really need to import external code to split a string?), the commonly accepted package manager is in an eternal beta limbo, the whole QL/ASDF/... thing is clumsy, parallel/concurrent libraries are very low-level, etc.
Now SBCL in itself is rock-solid and a fantastic experience when used with emacs, but the CL ecosystem is insufficient to qualify as “highly practical”.
IMHO, the most practical lispy language is Racket: tight language, excellent stdlib, easy to package and deploy, and a development experience that worse than CL but good enough.
The commercial Common Lisp I use, also has large amounts of extensions to CL. Including a function to split sequences/strings, parallel and concurrent extensions, ... ;-) It's actually the same commercial Common Lisp which Rich Hickey used years ago to write his first Lisp programs and where he developed his first ideas for Clojure.
I wasn't trying to set up an "opposition" on the language level in my sentence. The decision was couched in my personal constraints - whether "as a hobbyist" it was practical for me to learn the language successfully. And, having done so would it have the capabilities so I can build things that I'm interested in. For me personally, I like to have plenty of 'similar code' so that I can see what other people do. I was comparing this with a more academic approach which I've seen many Lisp learners enjoy - for example going through the Structure and Interpretation of Computer Programs (SICP). Other languages I looked at and seemed interesting were Elm and Racket - no particular reason why they didn't stick.
I suspect a lot of the dynamics of my choice are because I wasn't coming from any sort of Lisp background previously. If you were coming from Common Lisp then things would feel different.
Yes, it looks like Calva has first-class support for launching REPLs from your project, or for attaching itself to a REPL session that was launched in some other way: https://calva.io/connect/
> There is more to collections than lists. You can have instances of empty collections, some of which have literal support ([], {}, and ()). Thus there can be no sentinel empty collection value.
Probably my favorite feature over common lisp. Combined with comma (,) being treated as whitespace (ie, use comma if you want to but you don't have to), makes typing out data structures in clojure a much more enjoyable process than other languages.
This ease of typing out data structures also extends to edn (clojure's version of json)
This isn’t really a thing, though: Common Lisp has built-in literal syntax for n-dimensional arrays and structs and libraries like fset provide literal syntax for Clojure-style immutable data structures.
I've used fset and cl21. The problem is that once you start using those you no longer are able to use libraries. Data structures have to be built into core.
The common lisp community has rejected reader macros for interop reasons the last time I checked (in the case of cl21)
Even the docs for fset doesn't show the use of data literals. it does things like (set) and (isetq s2 (set 'c 1 "foo" 'a 'c))
This pretty much proves my point. it's not fun typing (set 'c 1 "foo" 'a 'c) instead of #{'c 1 "foo" 'a 'c}. It's also not possible to put those on the wire (easily) for communication between common lisp processes. Breaking the whole point of homoiconicity.
> once you start using those you no longer are able to use libraries
This isn't true: if the libraries you use are designed to use generic functions for their internals, rather than normal functions. I've written code using custom data structures heavily, with little or no impact on which libraries I could use.
> The common lisp community has rejected reader macros
cl21 is just confusing matters here. Plenty of code uses named-readtables to use arbitrary reader macros.
I don't understand the "interop reasons" you're talking about: as long as you use named-readtables, interop basically Just Works.
> It's also not possible to put those on the wire (easily) for communication between common lisp processes. Breaking the whole point of homoiconicity.
It looks like you're right for the most part. Literals are supported if you use https://github.com/vseloved/rutils. If your cl-edn actually works then a combo of rutils and cl-edn could bring most of the value I found in clojure to common lisp.
Immutability by default is mostly overrated: I’ve written code in many languages (Haskell, Clojure, JS, CL, etc.), and I’ve found I care more whether a library (Immer, ramda, etc.) provides referentially transparent APIs than whether or not the language’s data-structures provide immutability guarantees.
But, additionally, my point was that CL has literal representations for arbitrary data types, not that you can get immutable datastructures from a library.
In many ways I feel like Clojure is a better Lisp than Lisp itself. Syntactic niceties like support for vectors/maps/sets are, once you've gotten used to them, hard to do without. And although it's orthogonal to the syntax, I also appreciate its focus on immutability. I just with there was a Lisp like it that didn't run on the JVM.
These projects are great and definitely needed as a first requirement for an extensive 3rd-party-library. Been looking around at LISPs/CJ and there doesn't seem a very up2date library for dynamic-web-scraping or controlling headless-Chrome. Compared say to Golang. Of course this a nitpick and a sample size of one :)
Is this surprising? I don't see an ecosystem of languages targeting the Go runtime, in contrast to the JVM, Javascript, Erlang, MS CLR...
I don't think this is necessarily a matter of popularity or maturity. I don't know of languages that compile to Python bytecode, either. In Python's case I suspect the VM is so optimized for Python that it makes an inflexible target for other languages: I don't know enough about this subject to say whether that's the case for Go as well.
> Is this surprising? I don't see an ecosystem of languages targeting the Go runtime, in contrast to the JVM, Javascript, Erlang, MS CLR...
I think I'm surprised in general that no language targets the Go runtime. A ML built on Go could probably be popular, lots of people would like features from it.
I see! "Built on X" would usually refer to what language something is built in, while what you're talking about is "interoperability" with the host language.
But yeah, unfortunately Joker doesn't do interop, which is sad because it's one of the coolest features of the Clojure language.
Maybe a more seasoned LISP-`aterian` can answer me ?
I'm in the process of learning ClojureScript - real fun so far. I have of course also looked at other LISPs and have noticed that in CJS the use of square brackets[] for
vectors, let and `function params`
I almost never see square brackets in other LISPs (yes I'm truly a beginner and haven't looked too hard), but it stiked me as odd ? The square bracket syntax really helps to differentiate and with reading the syntax. (The syntax is not bad it is just different than what most programmers are use to)
Am I wrong or just stupid or is there another reason most other LISP doesn't follow this "convention"?
Again I'm super-newbie, just something that jumped out at me.
As someone that was a hardcore Lisper (you could find me arguing that Scheme is not a Lisp, etc.) before adopting Clojure: I thought muddying the language with a new kind of syntax was terrible.
I don't suggest that this is what primarily drove the lack of adoption of other kind of braces but I, as a follower of the church of Lisp, had an immense dislike of the lack of purity.
In the end vector and map "syntax" is just the reader being more ergonomic in Clojure than in Common Lisp. (See https://clojure.org/reference/reader for details)
After I also got over my aversion of the Java ecosystem and just accepted that the jvm can be great without thinking Java is great, I'm all in on Clojure for the last few years.
I think the language is fantastic and due to the huge amount of interop I can use my favorite language in settings that would be difficult otherwise.
I've used Clojure on the web as ClojureScript, as backend as Clojure, on a ESP32 and reMarkable as ClojureScript and since relatively recent I can even accomplish scripting tasks with very good start-up times using babashka.
I'm a super happy camper and I've found my language for life. A pragmatic Lisp. (duck because stones are incoming.)
Well I mean CL is absolutely littered with pragmatism it came out of a standard committee after all. In many ways I'd argue clojure is far more opinionated, it's just an opinion that might more closely align with modern sensibilities.
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.
> 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.
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.
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.
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.
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.
The reality is that just about every single lisper will insist how they don't see the parenthesis and they are not a problem, and just about every single lisp has some ugly ad hoc hacks to reduce their clutter. They just differ somewhat between lisps.
Scheme (and descendants like racket) went so far as to make [] and () interchangeable. So you will in fact see brackets in some scheme most e.g. (let ([a 1] [b 2]) ...) is a common idiom, although old-school people stick to ().
Common lisp has stuff like loop, () aliased to nil (different connotations), and a verbose way to call passed or returned functions; it shares its infix pair syntax (head . tail) with scheme. Scheme, unlike common lisp has a pretty complex (hah) infix-syntax for complex number literals; both write rationals infix.
And have a look at the Lisp 1.5 manual some time :)
As far as such hacks go, this use of vectors in clojure is actually quite nice. Whereas the fact that clojure, unlike other lisps, uses "invisible" pairing brackets in binding constructs or "visible" whitespace in the form of commas for associative containers is a big pain in the ass for editing and formatting code.
[edit: fixed idiomatic let bracket/paren split, as pointed out by version_five]
Only R6RS made () and [] interchangeable. That was hugely controversial, with opponents (myself included) thinking it was a waste of 1/3 of the matching pair symbols.
Scheme reports lag, though. They usually codify what existing Scheme compiler/interpreters are already doing. I know I've seen plenty of Scheme code in the wild using square brackets interchangeably long before R6RS came out. You can even see the rationale behind this in the Scheme FAQ on Scheme Wiki, which seems to not have been updated since R5RS was current [1]
The use of square brackets in Lisp and Scheme is much older. IIRC there were Scheme books which used them much earlier.
Interlisp also for example uses square brackets. The left square bracket is equivalent to a round bracket and the right square bracket closes all open levels of brackets.
I fully agree (and thanks for the idiomatic let bracket ordering correction). But the fact that terribly hacks like this are nonetheless adopted shows how much of a pain the paren clutter is and that claims to the contrary are pure cope.
I count 5, (), [], {}, <> and /\ though the last one is a bit of a stretch (but it's reversible: \/). Am I missing something? Did you mean "1/3 of the matching pair symbols left" as () is already used? Or is <> not usable?
IIRC, creator of Clojure, Rich Hickey once said that people in general handle things better where there is some variety in symbols used for different things, when you "scan" the code with your eyes. Too much variety -> you end up with explosion of symbols and operators, not good; too little variety -> hard to distinguish things.
I don't know the origins of when brackets started appearing in Lisps. I don't remember seeing brackets in the LISP 1.5 Programmers Manual when using S-expressions (though brackets are a core part of M-expression syntax, which LISP 1.5 still supported), and I haven't encountered brackets in Common Lisp.
> It is also used in let/let*/letrec bindings in Racket
I suppose this is just a personal choice of the author of the manual, right? Please correct me if I am wrong but I thought Racket doesn't care whether you use brackets, parentheses, or even curly brackets.
In Clojure, `[1 2 3]` is a vector, which is a distinct data type from a list, which looks like `(1 2 3)`. A vector in Common Lisp is written using `#(1 2 3)`.
Using vectors for function arguments is unique to Clojure, and I'm not entirely sure why they chose to do it that way.
Previous lisps had only the list as a datastructure with a special syntax and the program was only represented as lists.
There are different "schools" now. Some argue that representing the program only with lists is more uniform and simple. Rich Hickey argues that the different multiple usages of lists in "traditional" lisps, such as in Common Lisp, imposes a cognitive burden so increase the complexity.
The Janet language for example follows Clojure whereas as Racket is still a modern language but follows the "tradition".
I like this list. I don’t agree with every choice but so what: they are all thoughtful choices (except why do you need `recur`?) that reflect an opinionated position which I appreciate.
I prefer Scala’s solution, where tail calls look like normal calls, and a @tailrec annotation can assert they really are optimized out (or break the build).
The upside of `recur` is that:
* just by looking at the code you can tell if it's a tail call or not
* easy to debug; no trampoline magic
* guaranteed to work on any platform, whether it supports TCO or not
* Javascript is a very popular target, via ClojureScript
* who wants to test and remember if Chrome or Firefox or Safari implement TCO? or implement it properly? and which version(s) of those browsers implement TCO properly?
* if you wanna want a trampoline, you got it! (https://clojuredocs.org/clojure.core/trampoline)
That metric is subjective and arbitrary, but it's on my mind because my company is hiring for clojure developers (see my immediate comment history & apologies for the advertisement).
And it's on my mind because common lisp and scheme are _old_.
One of the nicer things about clojure is relatively frequent mentions of how common lisp did things, then a choice to hew or differ. And common lisp and the lisp family of languages have a long history. I like this as an art-piece of lisp's age: http://kazimirmajorinc.com/Documents/Lisp-code-typography/in...
It is, a friend built a consultancy doing PHP, and they have enough work to keep them going, keep the lights on, and focus on other stuff relevant to them.
PHP would actually be (1998...n), PHP 3 was when it took off.
>PHP would actually be (1998...n), PHP 3 was when it took off.
Yea was wondering what to put as a start date, I decided on the year I left varsity and walked into my first "professional job" coding C++ (Borland C++ Builder) within 6 months, we changed the "C++ Server" with Apache and some wacky PHP files.
I know there are some very good projects out there, but if I could "wish" any software into existence: It would be nice to have an "enterprise-level" LISP-To-GoLang environment WITH a massive library of course.
There is just something nice about the Go-Lang eco-system and compiling to static binary, not too many XML/JVM magic.
> Clojure is a Lisp not constrained by backwards compatibility
Which means that basically no (!) Lisp software from the past ran in Clojure and trying to make it results in a re-implementation and re-architecture of that software.
> Clojure was designed with no backwar(d/t)s compatibility
Correction: Clojure was designed with no backwards compatibility with previous lisps
Clojure puts a huge focus on being backwards compatible within it's own ecosystem. I've used libraries that haven't been updated in ages (think Clojure 1.3) but still works the same way as they did back then. This is also reflected in the community where libraries are very careful of breaking the public API, often creating new libraries under new names if there are breaking changes.
> Clojure puts a huge focus on being backwards compatible within it's own ecosystem
Like lots of languages. Incl. Lisp itself. For example the Maxima computer algebra system written in Lisp has been started >50 years ago (as Macsyma) and still is being worked on.
Sure, I'm just highlighting that your statement about Clojure is correct when it comes to compatibility against other languages while being incorrect if you consider it within the Clojure ecosystem itself.
Right, that it is compatible to itself does not sound particular noteworthy. Lots of language eco-systems have that. That's also why many languages (like C, C++, JavaScript, Java, Ada, Common Lisp, ISLISP and Scheme) have actual language standards with, where there is a high focus on protecting investments.
I'm not submitting "Clojure and the ecosystem focuses on being backwards compatibility" as a new HN story. I'm simply adding additional information to your "Clojure was designed with no backwar(d/t)s compatibility" statement which could be read as "Clojure core/libraries break their own API interface all the time" (like in the JS/NPM ecosystem) which is simply not true.
Just to be clear, I'm not saying this is unique in Clojure, simply making sure that others who read your comment don't read it the wrong way as it was a bit ambiguous.
The context is very clear the comparison to other languages, when it is clearly the fact that Clojure was designed with no backwards compatibility to these mentioned languages. It was not a matter of a list of things on that page, but the break with the prior languages was much more radical.
The 'correction' you made is an entirely different issue.
Standards come about to address compatibility problems, and standards bodies are committees where reps of divergent implementations settle on compromises (or sufficiently vague langugage) to get some compatibility. Python, Clojure, C# etc don't need standards since there haven't been problems wtith competing incompatible variants of the languages out there.
Sarcasm aside, seeing the resulting popularity of Clojure, I don’t think at all that this tradedoff was not worthy. Depending on Java/Js’s much much richer ecosystem is a win.
The difficulty with these lists is they always have huge blind spots. For instance, any scheme user would reasonably want to know if macros were hygienic.
They're not hygienic, but the usage of auto-gemsyms on quasi-quote and the fact that symbols are automatically expanded to their namespaced forms solves most of the problems that unhygienic macros have: http://clojure-doc.org/articles/language/macros.html#macro-h...
All of the features mentioned seemed to be in favor of Clojure, like it doesn't mention the lack of CLOS and conditions/restarts, which were both major eye openers for me in learning Common Lisp. Just a personal point, I much prefer macros in CL as well.
There are parts of Clojure I like, such as the data structures and de-structuring. Maybe it was primarily the JVM, but I just couldn't get into Clojure...it doesn't give the same feeling as working in CL. I really wanted to like it though.
I do wish that someone would take some of the modern data structure ideas and backport them to Common Lisp (as a new lisp obviously). The historical cruft in lisp and lack a strong modern standard library are major reasons I don't play with it day-to-day.
There's two main aspects to the claim. The first is Clojure's general stance against "place-oriented programming" that ties in with its immutable-by-default data structures. A good talk is https://github.com/matthiasn/talk-transcripts/blob/master/Hi...
The second is more specific. In Clojure, symbols are just names. When the compiler encounters a symbol, and it's not a special java kind or a local, it'll try to lookup that symbol in a Namespace map, and return a Var. The Var is where the storage is, the symbol is just a name to find it. Re-def'ing the symbol doesn't change anything about the old Var, it just associates the symbol with a new Var. In CL though, symbols are themselves objects that have attributes (http://www.lispworks.com/documentation/HyperSpec/Body/t_symb...) which store data. If you (inspect 'a) you'll see it has a string name, package, and an empty value, function, and plist. When you (defparameter a 8) and inspect 'a again, you'll see it has a value attribute of 8. You can then (defun a ()) and inspect again, and see both the value and function attributes are set. Of course CL is a Lisp-2.
Part of Clojure symbols being just names, means they aren't owned by any namespace. If you have three namespaces with functions that return 'x, they will all be value-equal to each other. Clojure symbols can be namespace-qualified, like 'some-ns/x, but that just restricts the lookup, the namespace doesn't own such a symbol. You can even make such a symbol without having defined the namespace some-ns first. In CL by contrast, if you create three functions in three different packages returning 'x, they will not be value-equal. And if you try to reference a symbol in 'some-package:x when the package doesn't exist, you'll get an error.
Clojure symbols are not storage-equal though: (identical? 'x 'x) is false. In CL, in the same package, (eq 'a 'a) is true.
Some consequences: another feature is that Clojure's syntax-quote/quasi-quote ` for macros will automatically bring in the symbol's full namespace, e.g. if you (ns a) and quote 'inc or 'a you'll get inc and a back, but if you syntax-quote `inc and `a you'll get back clojure.core/inc and a/a respectively. This helps reduce the chance of name collisions. Another consequence is that since symbols don't belong to namespaces in Clojure, you don't have to worry about a CL behavior of polluting your package's namespace with a bunch of symbols. (i.e. in CL when you switch to package a and type 'a, you just interned the symbol a in that package.) And another consequence is that if you make a function private in Clojure, you can't get at it with the symbol name from outside of the namespace, whereas in CL, if you don't export something from your package (making it "private"), you can still get at it outside with package::symbol.
You mean you struggle to install Clojure on a Mac? Clojure maintains a tap with Homebrew: `brew install clojure/tools/clojure` Leiningen should also have one but I don’t have that link on hand.
I've been focusing on ClojureScript (https://clojurescript.org/) as you get the benefit of interoperating with the Javascript ecosystem. The fact that there's a strong community around both Javascript hosted and Java hosted gives a wealth of library options.
Overall, the tooling has been getting a lot closer to the sort of experience that contemporary developers expect. The Calva plugins integration with Visual Studio (https://calva.io/) makes it easy to get started - you can even run it online with gitpod (https://github.com/PEZ/rich4clojure).
That just leaves learning the language - the slight changes in syntax (brackets for different data types) definitely help early on, and for the most part Clojure discourages people going down the path of macros which means reading other peoples code is reasonably accessible. The main struggle is that it's a language used by a lot of advanced or full-time developers, so documentation is pretty dense and it can take a real commitment to understand the detail.
It may not be 'correct' enough if you're coming from other Lisps, but coming the other way from C/Python etc I've found it an accessible and practical option.