On the other hand, the Clojure kids seem to reinvent programming badly while maligning other Lisps (see blog post), so maybe they do have a massive problem with trivial things like indentation.
On the other hand, the Clojure kids seem to reinvent programming badly while maligning other Lisps (see blog post), so maybe they do have a massive problem with trivial things like indentation.
We like our multiparadigmness, thank you very much. That includes being able to mutate things. We also like that the language is stable, and that means no changes to the spec, and that means it won't cover everything. That's OK, because our spec covers more than enough. We have less stable libraries and implementation-specific interfaces to cover the rest. We are still pretty conservative with those, too.
Clojure doesn't have a spec at all (it has documentation for the single implementation; it seems to have another implementation which is even more a toy since no real Clojure programs can run on it, because JVM is part of Clojure, really...) and once things are documented they either stick forever or break. I wouldn't say Clojure has warts; it has massive oozing injuries that keep on spreading indefinitely.
So yeah, we'd rather live with the warts from the 90s, the 80s, the 70s, and the 60s. We like our tradition, and we like our programs not breaking. We have a stable base that we can build 2020, 2030, 2040 stuff on.
That's a claim that would need to be substantiated first. My experience is that typical Clojure is quite descent actually.
It is true that Clojure doesn't have a spec, but it is very stable. I saw some very minor breaking changes in Clojure, and most code just works after a update (including very big projects). Clojure is very conservative in general, and this is something that Rich says as a explicit design goal.
BTW, are you referring to ClojureScript? Because ClojureScript is far from a toy language, it works well and we use in production. Sure it have some warts, but they're mostly workable and while I am not a fan, I am not a fan of frontend in general so this is probably part of the problem.
The differences honestly are often pretty similar to what some CL implementations differ in: host interop and multi-threading/concurrency.
The biggest difference is that Clojure standard library doesn't wrap all host APIs, so there's a bigger reliance on direct host interop. That's the main thing. And because the hosts themselves don't all share APIs, the interop specific code needs to be written specifically to the host.
It's a similar story for multi-threading and concurrency, some hosts don't support it at all, or support a very different version like Actors only, so in those cases the Clojure features related to them are dropped or altered to match the host.
You can still provide multi-host libraries, by making use of reader conditionals you can have the code fork at the points where it needs to be specialized for each host. That in turn can be used portably afterwards on whatever hosts the library would have supported.
I can take source code from the eighties and nineties (say from CMU AI archive) and, in all likelihood, with a few modifications make it respect the ANSI specification and have it run in 2020 on pretty much any CL implementation. This code is several times older than Clojure. I have a lot of code from 15 years ago to this day that works flawlessly on pretty much any CL implementation. Granted I also have some implementation-specific code and code that would suffer in efficiency and that would hit implementation limits.
I use mainly two CL implementations (SBCL, ECL) that serve purposes that are quite different. I know applications where CLISP was chosen because of its size and mostly-portable bytecode. People who care about Appleware tend to like CCL for its support on that platform. Some companies prefer the more enterprisey LispWorks/Allegro. These implementations are all meaningfully different. They all strive to conform to the specification. CLISP and Allegro have nonconforming modes as well, which not many people care about.
> Granted I also have some implementation-specific code and code that would suffer in efficiency and that would hit implementation limits.
That's my point. I mean that the Spec make you think it's always 100% compatible, but there are actually some small differences, and it's not always compatible or performing similarly.
Clojure just makes that reality explicit. Here are alternate implementations, here's what they support and where they differ, and don't trust that any implementation details might be exactly the same.
And for CL, a lot of choosing an alternate implementation is about finding one that supports the platform you target. But in Clojure, that's already handled by an intermediate host, like JVM, JS, or CLR. And so a lot of the meaningful differences between CL implementations arn't meaningful in Clojure, instead they'd be lifted to choosing which JVM you want to use, instead of which Clojure implementation you want to use. And it turns out the JVM is incredibly portable across platforms and does have a Spec, and so does indirectly Clojure.
So when you take it practically, the choice of implementation for me is about reach. And there are some places where CL reaches further then Clojure for now, specifically around embedded, IOT, lower level and such. There's also places where it doesn't reach as far as the JVM or JS hosts can give you. And yes, you can use say ABCL, but it's not as simple as copy/pasting your code to it. Part of wanting the reach of the JVM is to tap into its ecosystem of libraries and frameworks, and as soon as you do that, your ABCL code is stuck to only working on ABCL, and is no longer portable to any other CL host, same as Clojure.
I'd reframe the problem here, instead of saying CL is portable and has a Spec and Clojure doesn't. I'd say what exactly are you trying to achieve?
Do you want to run code written for x86 Windows on ARM64 FreeBSD? Well Clojure code is portable on both using the same implementation but a different JVM. And CL code can be portable as well if switching to a different CL compiler implementation with support for ARM64 FreeBSD.
Do you want to use JS libraries in a Common Lisp project and target the browser and NodeJS? Well, I'm actually not sure if CL has any way to do that, but assume it does, great you can do it, but most likely not have it be portable. Similarly with Clojure you have ClojureScript for that.
The standard could have been bigger and more comprehensive. But it’s just a non-problem most of the time. Threading is de facto standard and extremely well understood in its interactions with Common Lisp as defined.
Common Lisp is an extremely capable language to get things done cleanly. It just does things in a way that simply feels different from later languages.
If you want to talk about bolted on features, look no further than the plethora of Scheme libraries.
If your answer is performance or compatibility with 1980s operating systems or lisp implementations, then sure that’s a reason for the design but it doesn’t make the design cohesive, complete and composable.
I think CL is a good language despite this rather than because of it and I would suggest that your claim of it working “surprisingly well” supports that.
> Why can’t equality operators be extended to user-defined types?
That's a loaded question where a bad answer is worse than no answer.
I have taken a crack at it by inventing a concept called "equality substitution".
Equality for a new user-defined type is defined using a one-argument method, which takes an instance of that type itself. When an object of that type is compared with something using equal, then that method is called, and the return value is used instead; that is then iterated on. If an object has no such method, then its identity is used: the opposite argument must be that same object.
Thus, custom equality works by substituting some ordinary object: hence, "equality substitution".
For instance some employee object may have an equal function which returns the employee ID (an integer). Then employee object whose ID is 42, is equal to the integer 42, and therefore any other employee object whose ID is 42, or any other non-employee object also which returns 42.
To make it safer, the function could return a cons instead like (employee . 42), where the type symbol appears in the car. That cons is then equal only to another cons which has employee in car and 42 in cdr.
Equality substitution neatly solves the problem of hashing also. The hash able implementation uses the equality substitute of an object as the key: that's the value it hashes, and that's the value it compares.
The programmer need not define a custom hashing function, which is an error-prone, low-level task that we would not like to foist upon application programmers.
Now, not everyone will find it appealing that (equal non-cons-object '(employee . 42)) can yield true. They might say, if you believe that no answer is better than a bad answer, why on Earth did you do such a thing?
> Why pathnames?
Portability of code among very diverse historic operating systems, installations of which are no longer in deployment.
Pathnames in CL may be a pain in the but, but they are arguably a better abstraction than raw strings.
Mainly the thing that is wrong with CL pathnames is that the spec leaves certain behaviors implementation-defined in such a way that two people writing a CL implementation on POSIX, say, will end up with different behaviors. There is more than one way that POSIX path strings can correspond to pathnames objects. Oh, like for instance in foo.tar.gz, is "gz" the :type? Or is "tar.gz" the :type? That creates ironic situations whereby we can't port a Lisp program to the one single plaform using two implementations of the language, such that the behavior is identical!
A new revision of ANSI CL could solve this: it could specify that on Microsoft Windows, the correspondence between pathname objects and strings is such and such, and on POSIX such and such, and so forth.
Nope, someone wrote an article arguing for fixed formatting and Bozhidar Batsov decided to reply, but pretty much what he said is what is accepted by Clojure community anyway.