Differences with other Lisps
clojure.org
clojure.org
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.
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".
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.
Seriously, is there a reason you have to hint about what Lisp you use?
If you are interested, I'm using LispWorks <http://www.lispworks.com>. The other large commercial Common Lisp implementation is Allegro CL <https://franz.com/products/allegro-common-lisp/>.
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.
I fully plan on making it my main language for my own projects, with that being Java and the web stack at the moment.
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)
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.
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.
Using the reader on untrusted input is a bad idea in either Common Lisp or Clojure, because both readers can execute arbitrary code. However, there are packages like safe-read ( https://github.com/phoe/safe-read ) and Eclector ( https://github.com/s-expressionists/eclector ) that let you solve this problem pretty trivially in a safe way. Not to mention projects like my cl-edn ( https://github.com/fiddlerwoaroof/cl-edn ).
But, additionally, my point was that CL has literal representations for arbitrary data types, not that you can get immutable datastructures from a library.
There are a few, with varying degrees of distance from the JVM and varying degrees of similarity to Clojure.
- There's ClojureScript, which is Clojure transpiled to JavaScript.
- See also Lumo (<https://github.com/anmonteiro/lumo>) and Planck (<https://planck-repl.org/guide-all.html>)
- There's Babashka (https://github.com/babashka/babashka), which is a natively-compiled Clojure interpreter, implemented with the Small Clojure Interpreter (https://github.com/babashka/sci) and GraalVM for native compilation.- There's Joker (https://github.com/candid82/joker) with a similar mission to Babashka, but commonly used as a linter.
- There's ClojureCLR (https://groups.google.com/g/clojure-clr) on the Common Language Runtime.
- There's Cloture (https://github.com/ruricolist/cloture), which is Clojure in Common Lisp.
- There's Clojerl (https://www.clojerl.org/), which is Clojure for the Erlang VM.
- There's Pixie (https://blog.goodstuff.im/pixie), with Clojure syntax, but compiling to native.
- There's Ferret (<https://github.com/nakkaya/ferret>) which is Clojure transpiled to C++11.
- There are some Lisps that have borrowed Clojure's syntax:
- Janet <https://janet-lang.org/>
- Carp <https://github.com/carp-lang/Carp>
There are a variety of other Lisp-like languages listed here: https://github.com/dundalek/awesome-lisp-languagesApologies for the formatting.
For ClojureScript I guess there's lots of options since you can access the Javascript ecosystem.
One of the benefits of being hosted is that we can always fall back on the host language's options.
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.
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.
Not exactly bytecode but Hy https://github.com/hylang/hy is a dialect of Lisp that compiles to Python.
Joker is a Clojure interpreter, mostly used as a linter in the community but can be used as a runtime as well.
But yeah, unfortunately Joker doesn't do interop, which is sad because it's one of the coolest features of the Clojure language.
* https://convexhuman.com/graalvm-clojure.html * https://nitor.com/en/articles/fast-cold-starts-for-clojure-i... * https://www.innoq.com/en/blog/native-clojure-and-graalvm/
Since development in the REPL is so common (watch any Clojure live coding session), JVM startup time isn't much of an issue in practice.
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`
(defn myfunc []
(let [some-var (:some-key some-state)])
(.log js/console some-var)
)
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.
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.)
As a newbie(with zero social-capital) I couldn't agree more !
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.
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.
> 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.
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]
And the suggested use was not quite the syntax you showed: http://www.r6rs.org/final/html/r6rs-app/r6rs-app-Z-H-5.html
[1] http://community.schemewiki.org/?scheme-faq-language#H-yoyi8...
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 am firmly in the "paren clutter doesn't exist" camp.
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?
For example, in traditional Scheme one would write:
; this is in R5RS Scheme
(define (length elems)
(cond ((null? elems) 0)
(else (+ 1 (length (cdr elems))))))
But in Racket one would write: ; this is in Racket
(define (length elems)
(cond [(empty? elems) 0]
[else (+ 1 (length (rest elems)))]))
It is also used in let/let*/letrec bindings in Racket; see https://docs.racket-lang.org/guide/syntax-overview.html#%28p....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.
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.
Using vectors for function arguments is unique to Clojure, and I'm not entirely sure why they chose to do it that way.
Once you understand how destructing works in Clojure, it becomes obvious
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".
Edit: https://www.windley.com/archives/2008/11/tail_optimized_mutu...
Rust (2010) (edit Kotlin (2011) perhaps), but point being there aren't many newer: https://en.wikipedia.org/wiki/Timeline_of_programming_langua...
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...
PHP is like the backup-plan(welder,lawyer etc) your parents wanted you to have before going out on the road with your band and making it big :D
/mostly-sarcasm - but a little true ?
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.
But you are right :)
https://www.youtube.com/watch?v=HY0JRJPvjsU&list=PLwUPJvR9mZ...
There is just something nice about the Go-Lang eco-system and compiling to static binary, not too many XML/JVM magic.
https://clojure.org/about/rationale#_lisp_is_a_good_thing
> 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.
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.
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.
The current build system ASDF works in Lisp implementations which date back to the mid-70s...
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.
> Clojure is a Lisp not constrained by backwards compatibility
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 'correction' you made is an entirely different issue.
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.
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.
>Symbols are not storage locations (see Var)
I thought the way symbols worked in CL are one of the three big ideas (s-exps and macros being the other two).
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.