You compose libraries, instead of totally buy into framework A or B. Some people prefer the opposite.
However, Composition works in Clojure because everyone is using the staples: functions, maps and seqs. Pretty much everything in the language can work as one (or all!) of them.
While I also like static types, this is my argument for a well designed dynanic language like Clojure or its Lisp relatives: stuff just snaps together like magic lego bricks.
With that said, might it also be possible that there could be some drawbacks? Having worked in and with Clojure, I found the ecosystem incredibly immature. It's somewhere beyond arrogant - reckless comes to mind - for most engineers to do all their own plumbing for a web service. The odds that they'll manage to design someone easily long-term maintainable are slim. The chances of authorization, authentication, potential SQL injection, and a thousand other security things being handled well is effectively none - the Clojure ecosystem often seems wildly ignorant of such things. The core philosophy of just composing the libraries you need with Ring means that anything you don't actively think of will be likely entirely neglected. This is a dangerous way to ship code that will be facing the real internet. Clojure requires you to fit absolutely every aspect of everything you're doing into your head and be handled by you, because nothing is going to do it for you. The lovely magical LEGO feeling is, sadly, a lie.
This stands in stark contrast to the level of consideration around maintenance and security that has gone into a modern, mature framework. Or a modern language where SAST is an option.
Doing low-level stuff feels absolutely amazing! The sense of control, of having your hands in the guts and the ability to do what you need without wasting time on bullshit magic, is heady and glorious. It's just perhaps worth considering that that magic can sometimes be incredibly valuable, well worth the tradeoff against the feeling of freedom.
This does not match my experience at all.
There is a thing called sane defaults. You solve a problem by decomposing it to simple elements, and then combine them to sane defaults, while still giving full power to your users.
Examples of sane defaults in areas you mention:
- authorization/authentication: buddy (https://github.com/funcool/buddy) has both core library with crypto stuff and complimentary libraries for auth etc. For example, if you want to securely hash a password, you don't have to come up with salting yourself, just use buddy-hashers: http://funcool.github.io/buddy-hashers/latest/#quickstart
- sql: clojure.java.jdbc and it's successor next-jdbc for sql queries use vector with query string containing question marks for arguments and then arguments that get escaped, see select example here https://cljdoc.org/d/seancorfield/next.jdbc/1.0.6/doc/gettin...
- "other security things": ring site defaults, see https://github.com/ring-clojure/ring-defaults/blob/master/sr...
You're right! Sane defaults are incredibly powerful and let developers make the right decision by not making decisions at all.
When developers have to stop and remember to integrate a library for authZ and authN, and then integrate individual modules from it for their use case, some might opine that we have gone beyond the bounds of sane defaults.
Sane defaults for Clojure in a web context are a wonderful idea. It's unfortunate, then, that buddy is less sane defaults and more a toolkit from which someone assembling a framework has the option to choose sane defaults.
Ecosystem takes a while.
(Yes, I was using both JDBC and Ring as well as other things like Leiningen. Core.async was a notable exception as something done better in Clojure than elsewhere)
Some of what you perceive as immature may be that the community is small, and a lot remains to be done.
With that said, it may be more difficult to get one used when the culture of library-land abhors frameworks. The Clojure developers I've worked with so far mostly seem repulsed by the idea of using a framework.
This leads me to fear that the ecosystem is likely to remain in the hole it has dug for itself. Possibility is wonderful, as you say, but it is unlikely to save applications being written today.
I think this is one of the memes in the community which is starting to be reconsidered. You're not alone in seeing these downsides, and people are starting to write about it more (code and words). Things like Fulcro or even Datomic Cloud are very interesting developments in this regard.
The closer you get to the application layer, the more it looks like high quality, opinionated frameworks and ecosystems are great. You get more mileage out of a sophisticated foundation once you're at that point, so your integration risk surface is higher if you choose to roll your own framework. To some extent, this is unavoidable, but I've always gotten value out of minimizing it.
To some extent, I think Clojure has some great things going for it because it can hook into the JVM and the Java package ecosystem. But if that's ignored, then it's essentially starting from scratch.
For folks who have production experience in Clojure: how valuable is Java interop in your experience? How often are you able to get away with using the most idiomatic parts of the Clojure-land ecosystem, and how often do you have to reach for Java-land? How easy is interop in terms of traceability?
Very, there a lots of high quality Java libraries, and interop makes it easy to use them.
Especially when integration with other software (databases, message queues, etc) being able to use the Java client libraries is a blessing.
Clojure is designed to be a hosted language, and so Java interop is first class and integral to it. In fact, the standard library specifically excludes anything that overlaps with Java's, and is designed to complement the existing Java standard library.
> How often are you able to get away with using the most idiomatic parts of the Clojure-land ecosystem, and how often do you have to reach for Java-land?
It's not really like that, they complement each other and work in a symbiosis. Yes, sometimes it feels like the worst part of Clojure is Java, but not reinventing the wheel and leveraging battle tested Java libraries and tooling was a very smart pragmatic decision, so it's also the best part of Clojure. Just know using Java from Clojure is very idiomatic in of itself.
> How easy is interop in terms of traceability?
Pretty much seamless.
The most used HTML templater(hiccup) doesn't do any secure output escaping, bad password/session management, auth*, sql injection, bad encryption methods, its all there.
Doing it that way you can also perform more selective sanitation if needed.
Hiccup2 does sanitation by default, still in beta though.
The common SQL lib is java.jdbc or next.jdbc and they use prepared statements by default. Can you explain more where you see common risks for SQL injections?
I'm not sure what you mean by bad password and session management? Normally, the common lib people use is ring-defaults https://github.com/ring-clojure/ring-defaults what about these do you feel are insufficient? Maybe do a pull request about it.
Auth and encryption I agree, but the untold reason is that people just use Java bountycastle and Apache Shiro or other for that. Buddy is the most popular Clojure one https://github.com/funcool/buddy , but honestly, for that stuff, you want something super battle tested, that means popular, and Clojure as a whole isn't popular enough for that, so really just pull in a Java lib done, it works flawlessly within Clojure.
One of the things that Rails' templates gets completely right is that it knows when something is nested and when it's going to be returned immediately. It is thus able to do its automatic escaping right before returning a response and not before.
At this point in history, I think this is the baseline behavior all templating systems should meet. Requiring users to remember to manually escape every output every time is begging for exploitation. Such a design error only strengthens the case that hiccup is not appropriate for serious use at this time.
That's actually what hiccup2 does as well by the way. I admit, it is a nicer behavior, and I'm not sure why hiccup2 hasn't gained more traction since it released, but you are free to use it, it's part of the normal hiccup release, just require hiccup2 instead of hiccup.
But hiccup is not the only popular Clojure HTML templating lib. Selmer, Clostache and Stencil which are much more ERB or Django like, both HTML escape by default.
What common SQL solution in Clojure are people using that introduces SQL injection, for example?
Examples include Emacs, Linux (especially distributions like NixOS or Arch) or classical Unix utilities.
That said Hickey gave a tiny argument to lessen the pain, the cost of freedom is often lesser than the cost of being jailed by a framework that cannot evolve
That said, I do find myself pining for the ability to easily compile a static executable (maybe with a small embedded runtime). Some time ago there was an attempt to port Clojure to Gambit Scheme, but that appears to have fizzled.
More recently, I’ve seen Ferret, which compiles a subset of Clojure down to C++. Also Joker, which is a Clojure-ish interpreter in Go.
Then there’s GraalVM, which I don’t trust, because it’s Oracle.
Racket (modulo the uncertainty cast by the Racket2 discussions) and Gerbil Scheme both have a pretty good “distribute an executable” story while trying to introduce some creature comforts beyond plain Scheme. I suppose my ideal, would be something a little more like Clojure in terms of language, and a little more like Scheme in terms of deployment options.
Here are my notes on that: https://taoofmac.com/space/blog/2019/06/20/2310
Beyond that, the land of libraries has sneaky little java dependencies scattered throughout. Clojure without the libraries isn’t very compelling.
As someone else mentioned, graal does a good job of making an “executable”
I recently read about RUtils  which uses reader macros to modernize a bit the syntax.
Clojure's focus on the REPL, immutability and simple datastructures make it possible to inspect data and redefine functions in running systems.
In other languages I spend a lot of time making small edits,
setting breakpoints, starting the debugger, waiting for the program to start, etc, where in Clojure I can just redefine and inspect stuff immediately, allowing very short feedback loops when programming.
A lot of Clojure tooling emphasizes short feedback loops:
component with it's reload function to restart systems,
figwheel/shadowclj with live reload of browser apps,
which is a huge productivity boost.
Once I got used to the quick feedback loops that Clojure provides, all the other languages feel bulky and slow to develop in.
> generating strings and passing them into eval like for example in Python
Python has become a pinata for language enthusiasts? Python lets you pin a decorator on a function, get the AST (a data structure AFAIK), manipulate it in a sane fashion in Python, compile it, return the result. No string munching eval require
I'm all for Lisp but this just isn't convincing
In Clojure you can write a list comprehension as `(for [x (range 10)] x)`. In Python you can write it as `[x for x in range(10)]`.
Imagine these didn't exist, and you wanted to implement them yourself.
In Clojure this is possible. Its list comprehensions are implemented as a Clojure macro: https://github.com/clojure/clojure/blob/b98ba84/src/clj/cloj...
In Python there are several problems. The first is that you can't easily pull the decorator trick on arbitrary expressions. Not even with a lambda function, at least without doing a lot of work - inspect.getsource doesn't handle those properly.
But let's lower the bar, and replicate `l = [x for x in range(10)]` instead. You can do that with a decorator and an ordinary function.
Then the problem becomes that you can only use existing syntax in the function body. Syntax that already means something else. If you put an ordinary list comprehension in the function body then you get a syntax error when the file is parsed, long before the decorator has a chance to do anything. So you have to make something up yourself.
You might end up with something like this:
l = list(map(lambda x: x, range(10)))
I don't know how big of a deal this actually is. I've never used Clojure, and I've barely used other Lisps. Expressing what I want in Python is easy enough in practice (but maybe I just don't know any better?). But there's certainly something significant there that Python can't do.
Edit: this is possible too in languages like Elixer and Ruby using macros, but eventually the readability suffers and debugging complex macros in those languages can be a nightmare.
About debugging, it is somewhat different from normal debugging since macros are a second layer of abstraction. You have a compile-time phase in which the macros are resolved (receiving code and returning code until there is no macro left) and a runtime in which the final generated code is run. If you only consider the runtime, then you're looking at code that is not the same as you wrote, which makes it indeed hard to see mistakes. But if you separate the concerns of compile and run time you can evaluate both separately most of the time.
Also, Ruby does not have macros. It has a very simple syntax in which everything is an object and all function calls are actually messages that can be interpreted in runtime which allows you to extend the syntax very easily by controlling the dispatch rules. What causes trouble is that classes are open and can be extended freely during runtime, so you cannot know that an object you're calling is not modified by any part of the code (including any library), a practice called monkey patching and usually considered bad practice since it makes code hard to reason about.
I use both Lisps and Python, and especially after learning to use Lisps I've wanted to do this in Python, so I looked for the ability to do what you describe here.
While it is technically possible to do this (like most things) in Python, it is quite the effort and nowhere near ergonomic. Among all the cases where I could really use this, I found it worth the effort to instead:
1. Manipulate & generate code statically (from ASTs), as a build-time / dev-time step, using redbaron etc.; or
2. Just use a Lisp (specifically, Hy).
Both methods involved writing quite a lot of code. In the former: to load, parse, match, and transform ASTs; in the latter: to port existing code (the code I originally wanted to manipulate) from Python to Hy.
The reason for all this is plainly the fact that Lisps are designed for this, while Python is not. Before learning Lisp, I used to wonder what could be so different in the language itself that could possibly make it that much easier. Now I use a Lisp-in-Python in production precisely because of this.
Indeed! Peter Norvig once made the same observation :
> Python does have access to the abstract syntax tree of programs, but this is not for the faint of heart. On the plus side, the modules are easy to understand, and with five minutes and five lines of code I was able to get this:
>>> parse("2 + 2")
['eval_input', ['testlist', ['test', ['and_test', ['not_test', ['comparison',
['expr', ['xor_expr', ['and_expr', ['shift_expr', ['arith_expr', ['term',
['factor', ['power', ['atom', [2, '2']]]]], [14, '+'], ['term', ['factor',
['power', ['atom', [2, '2']]]]]]]]]]]]]]], [4, ''], [0, '']]
The essential property of a Lisp, to me, is that the syntax is the AST. For any language which does not have this property, providing DOM-like access to the AST is neat but simply not practical for the user.
If you or your team are Lisp users then Hy or Clojure would be good choices, but if you are just trying to create a transformation for existing Python codebases, the decorator approach is a good shoehorn.
On the other hand, you can also interpret the difficulty of AST transforms as a signal from the language designers (had van Rossum really never heard of Lisp?) that they hurt readability and their use should require a little ceremony.
> if you are just trying to create a transformation for existing Python codebases, the decorator approach is a good shoehorn.
For transformations that need to modify the code, decorators are insufficient. Decorators have their value, and I have built good interfaces using decorators, which would have been rather difficult without them, but all of that was because decorators help _wrap_, not _transform_ code.
When I had a need to transform code, I looked for the right tool, and the takeaways of my searches are mentioned in my original comment.
> you can also interpret the difficulty of AST transforms as a signal from the language designers (had van Rossum really never heard of Lisp?) that they hurt readability and their use should require a little ceremony.
Nobody's claiming Guido had never heard of Lisp, or any non-lispy-language designer for that matter. Language designers make languages for varied reasons. Guido's reasons actually happen to be documented, and the only reason Python isn't homoiconic is because Guido didn't have a need for it, not because he explicitly wanted AST transforms to "require a little ceremony".
In fact, Python AST transforms do not merely "require a little ceremony". They are actually quite cumbersome. The standard library actually provides no help at all in transforms, making it necessary for libraries like Rope, RedBaron, and Astor to be written.
Third-party packages do make that somewhat better, but still not great. IIRC Jinja compiles templates to Python source code then compiles that to bytecode using the relevant builtins because it's simpler & more reliable than doing straight AST manipulations.
IMO the macros in Clojure make it easier for library authors to produce KISS, high-level interfaces for the rest of us.
Much like classes can be used as data transfer objects or to build out a large inheritance tree, macros can be used tactically - to DRY up things you otherwise couldn't, or introduce some readability improvement - or strategically, to lift the code up another layer of abstraction, or create DSLs that let you write your code much closer to the problem domain. Because Lisp supports them in an ergonomic way, macros are not seen as something special, but just another tool in the toolbox.
[1 2 3] is both the serialized form of a vector as well as valid code to create a vector of itself.
Now in JS, only object data is homoiconic. In Lisps, even code expressions are homoiconic, thus (+ 1 2) is the serialized form of the list of function + and args 1 and 2, as well as valid code to add 1 and 2 together.
You do not need macros for homoiconicity. Macros is not what makes a language homoiconic. But once your language is homoiconic, it is very easy to add macros to it, and macros become much simpler to write. Which is why Lisps have macros as well.
Nothing will convince you until you give it a heartfelt try. The joy from using Clojure is a real thing. Unfortunately, there are not too many languages out there you can say the same thing about (including Python).
Clojure is not absolutely frustration-free PL, but it maintains a good balance between "fun" and "profit".
f = decorator(f)
Afaik, the decorator deals with the internal function as a black box.
But with lisp being a lisp, the macro writer can choose the level of blackboxiness they want to deal with.
Note: I love both Python and Clojure and use them extensively.
You could also decompile the function's code object.
I find the ecosystem immature and full of ideas about "libraries, not frameworks" that leave developers incredibly vulnerable to their own ignorance and second-order ignorance. The number of developers I know - or even know of - that can be trusted to develop a useful and secure web application from the ground up with this kind of tooling approaches zero.
Clojure is a cool language. I can only hope the ecosystem soon matures enough to make it one that's suitable for serious use.
Wouldn't the security stuff be a part of the individual packages? Ie authentication package handles sessions and tokens, an http server/middleware with built in csp and ssl, orm, the data stuff handling serialization and data parsing?
Technically rails is composed of a bunch of smaller gems with a similar responsibility tree although without the centralized CVE distribution and pressure that creates.
When I used Clojure it was always mini frameworks which created a base set of important packages with a pluggable middleware system on top and flexible data back ends.
The individual packages need to have domain experts for their particular attack surfaces and exposures.
If anything its about a lack of eyes or community size on the various wholes, rather than expecting the end user to do the individual hardening or plumbing.
Of course, that's assuming the developer thinks far enough ahead to remember that they need authentication, decide how they want to handle it, figure out what they want for a CSP, and so on. Other things cannot be so readily handled. Authorization, for example, isn't a concern that can be bundled into a pre-fab function. It's something application-specific that has to be re-evaluated with every single web request and is going to be subject to unique business requirements.
Frameworks tend to handle these issues by setting out a set of conventions to adhere to, within which many of these concerns are handled for you to the extent possible. It's far from perfect, but it does a lot to reduce the chances of someone forgetting (for example) that forms need anti-CSRF tokens because their form-generation function doesn't do it for them or automatically work with the form-accepting and validating functions.
Or, to use example I've seen, the logging package's ignorance of what might be sensitive in the strings its been handed turns into secrets being sent to the centralized logging system and exposed to far more people than they should be. The developer didn't think about what might be in what they were logging, the package assumed that the developer did, and the result could have been avoided by a more integrated whole.
A culture of libraries instead of frameworks can mean that each project may have to make all the decisions a framework does all over again. Is everyone going to get it right every time?
Umm, is there any all in one web framework that protects against this to any level of guarantee?
So, yes, it is possible for logging (and serialization) systems to defend against this kind of thing.
Node only got to that point through plenty of failure and last time I used Go it didn't seem much different than Clojure.
The only reason I'm not using Clojure is the lack of types but that's another issue.
Have you tried Clojure.spec? Have you tried generative testing where you generate data based on given Spec?
Have you tried using Specs for form validation? Have you seen how Specs can be shared between Clojure and Clojurescript? Basically between two completely different platforms. Even vanilla nodejs cannot do that, even though the code is running on the same [JS] platform.
Have a look at Pedestal, Yada, Duct, Hoplon, Catacumba, Coast,
Those are all actively maintained frameworks being used by different people in production.
On top of those, you have the meta-libs frameworks such as Luminus and Chestnuts.
And what most people don't know is a lot of Clojure devs use a Java framework, because those are also available to be used from Clojure. I have a Spring MVC Clojure app for example. Lots of folks use Vert.x for example.
So whatever your definition of mature is, Clojure has everything you need and more. If you mean mature as in, one and only one way which everyone is rallied behind and all guides and tutorials teach and use, then no.
My impression was that java has the reputation of being "uncool" but JVM is highly regarded as modern marvel.
I am wrong about this ?
In my experience, people who say silly things about Java -- the technology, which certainly includes the Java Language Semantics, and JVM -- actually do not know what you can do with Java (the language).
So yes, you Lisp nerds have you homoiconic 'mash' of syntax and thus AST manipulation, and we have first class Class Loaders and Byte Code Engineering and the sky is the limit there. Magic.
But the sad reality of programmers' lives is the disconnect between the required cognitive capabilities of workers and the actual (domain) task at hand.
tldr;/ its your job that is uncool not the tech.
Yeah, I just don't understand the less lines of code argument. This is not the limiting factor in software development, it's just not. The limiting factor is having enough information for the immediate Use Case and enough business domain knowledge to come up with a solution that delivers value to the business. The time spent on delivering that value dwarfs the time it takes to write some extra code. Let's look at Uncle Bob's example in the linked article...
(println (take 25 (map #(* % %) (range))))
While fancy, that code is very Perlish and I think we all know the common criticisms of Perl. Also, fancy doesn't pay.
What I've found to have a much larger negative impact on a project than the language is overly complicated architectures. To the extent Clojure can help with that I'm all for it. But from what I've seen, Java 8 with some clear conventions and constraining source code to the implementation of the Function interface can get you a lot of the same benefits.
Damn! I felt that.
This is super apropos since I am JUST learning Clojure via the Clojure Koans.
Any tips from clojurists on getting the repl (and readline) working fluently? I'm using Leiningen but it's not responding as I'd expect. I would think this would all set up more effortlessly.
Tips on getting a new Clojure setup working would be appreciated.
Try out Calva for VS code, read the official REPL guide here https://clojure.org/guides/repl/introduction
For me, Boot has completely replaced Leiningen, and I wish it was used by all new Clojure projects, when deps.edn is not enough.
Thanks for the tips!
EDIT: and you might also want to check out https://clojureverse.org/
And like others said, people on slack are super helpful, don't be shy, the community is great.
That said, Clojure does not invest a lot into beginners. Not a lot of tutorials, guides and tooling are similarly unfriendly and assuming you know a lot. Not trying to scare you off, just setting the right expectations.
I've been using Atom so far, and I will try Cursive later
Not sure what you are using but i use CIDR on emacs.
Also I find Clojure a little dogmatic in regards to mutation, more so than even Scheme. I find Racket to be better designed and more elegant, though currently the performance isn’t as good.
I don’t disagree with you that it’s ugly and sometimes tedious to use Java in Clojure... but that is one of the foundational strengths of the language. It’s also something that you’re hopefully not doing a lot of... so I think the trade off of seeing ugly Java syntax sprinkled in your project is worthwhile.
You’re standing on the shoulders of giants with zero to no penalty as far as interop. I might reframe your mentality to see this as a huge benefit and not something stupid!
I don't think it's dogmatic (which I would say has a negative connotation) but simply opinionated because fundamentally at the heart of clojure seems to be a concurrency model that heavily relies on immutability to make sense, so it's a logical consequence of that design choice.
And I think it's an appropriate one because the mutable thread and lock world that we get in mutable imperative languages is honestly a little bit insane.
Funny that. While I prefer FP, and did since I've learned Python, my teachers are really hot about OO, even pushing us to use it in Python projects.
(Looking at projects from previous years, they mostly seemed to use Java. Then also because of browser integration and obviously because of the OO <-> UML <-> Project links ?)
A recent example is SwiftUI. While Swift is not a FP language, SwiftUI (FP UI framework) and Combine (a FP toolkit) is being pushed by Apple as the future UI architecture for all its OSes.
Complex static type systems on the other hand - are kinda "new kid on the block". And if we are not careful that may become a "new OOP" - doctrine so complex and convoluted that could become an impediment in building software. I know, static type systems are cool and all but it's easy to get from something elegant and simple like Elm into say Haskell, Idris, Agda etc. and into the weeds of homotopy type theory.
IMO, less code is better. Clojure is about as terse as you can get and still be readable (by my opinion). Scala is very visually noisy in comparison. While you can do types with Clojure.spec, you get to choose when and use when useful to you.
Rich Hickey is the best communicator of why Clojure is good. Here's a relevant starting point a video talk he gave on the 10 year anniversary of Clojure:
All this said, Scala has more jobs than Clojure in the same way that Java has more jobs than Scala does. It's more Java-like than Clojure (and not just because it isn't Lisp). (And incidentally, only people who haven't tried at all to use Clojure complain about parens ().)
These jobs are pretty much impossible to get, though.
An analogy to pique your curiosity: I would say that if Java was a balloon, Scala would be a zeppelin, and Clojure would be a plane.
I would also say that the stewardship of Clojure is much saner than that of Scala, due to a strong emphasis on stability. Typesafe / Lightbend is bigger than Cognitect, but big does not mean reliable. One sign of this is that Clojure now grows via libraries, the core having reached stability. Scala is still figuring out its fundamental semantics.
Is it just because they both run on the JVM? For all my Clojure projects, I picked the language despite that, not because of it. It's merely an implementation detail.
Clojure: Explore the problem you are facing with quick prototyping in the repl and see the blind spots, the unknowns, the shape of data received from flaky integrations. Mold and shape your functions as the problem becomes clearer.
Scala: Best it can do is write unit tests and the debugger to look at what exactly is happening, devise a bunch of classes to handle them, rinse repeat and of course wait hours for recompiles. Refactor when new unknowns surface and curse at the fucking compiler not knowing what type it should be inferring.
Clojure programmers are done before Scala has a test environment up.
This is a great way to end up with undocumented code with no tests. The repl definitely encourages this process, but you should be doing this via quick running unit tests rather than the repl so that when you are done you already have a set of tests. Otherwise tests are an after thought and usually after thoughts are either not done or done poorly.
edit: you can always run your unit tests via the repl too, if you'd rather not use a fast test runner.
Firstly Scala does have a couple of REPL's, the best being Ammonite which is mentioned below. It can be embedded in code to provide a live repl just as your Clojure program. I often work in Scala in a similar way to I work in Clojure; creating and testing new types and functions in the repl before moving them back to a source file.
The way you build programs sounds quite horrible, but I know people do it that way and it works well enough. Your complaints about long compile times are largely irrelevant. Incremental compilation means it's usually 2-3 seconds at the most before I can test a change. We don't build programs as a bunch of classes by the way, that would be OOP. Scala is more commonly written in a functional and/or streaming/reactive stle. Also I'm glad you brought up refactoring. It's a breeze in Scala because of the strong type system. Poorly defined data can be expressed just well in Scala with types as it can in your blob of maps of maps, and it's much easier to work with.
Finally you may finish your program first; it appears to do the thing you want but you can't really be sure. There will be more runtime errors such as NPE's and type errors because you don't have mechanisms to remove them from possibility. I'd much rather maintain and evolve a Scala program.
This is the exact opposite of my experience. All the Scala I've seen "in the wild" is basically Java with a slightly different syntax. All OO, with the odd high-level function thrown in to prove something I guess.
Ammonite is not it though.
The kernel I use (almond) is based on Ammonite, but I believe there are others out there.
1) Clojure is more fun, more interactive and interesting.
2) You learn a lot more, more quickly. In Scala it's easy to just fallback to writing classic OOP with a nicer syntax. Clojure forces you to learn FP, meta, logic, etc. much sooner.
3) I'm more productive in it.
4) The interop is way better.
5) Compile times are instantaneous.
6) The community is friendlier.
7) New versions are always backwards compatible, Scala often breaks compatibility and updating is annoying.
Also part of the reason Babel exists is to take the place of a macro system. Instead of transpiling array spread into a function polyfill, macros would just output the pollyfill into the AST. What’s nice about this is that since it’s userland, people can test drive new features. Rust did that for async/await. And the pattern matching proposal in JS is based off a SweetJS macro.
(Obviously not all language features can be implemented as macros but you get my point, right)
Sounds like Smalltalk.
What were the special cases for your use case?
Whenever I have a requirement to solve a problem in a different language (e.g. interview challenge, etc.) I would still do it first in Clojure and then convert it to whatever language is required. Yes, I have to do the same work twice, but even then - I am still faster doing it that way.
I’m not personally a fan of a VM such as JVM. Sure you get some easy benefits, but now you have two things that need tuning. Two things that need periodic updating.
Personally the total encapsulation of first-class static linking (such as Golang) is my preferred future. It must be first class and baked into the language in its design.
Now, imagine also having access to thousand of libraries written for JVM and Nodejs and Browsers? There are attempts to port Clojure to other ecosystems, making it possible to use them there. What if someday you can write Clojure code that can be shared between say Rust runtime, NodeJS and a browser? Wouldn't be that cool?
Yes, it's more succinct and elegant but is it worth it?
Tooling is important.
It's certainly possible to converge towards some shared notion of what constitutes maintainable code.
We additionally have an internal styleguide plus some formatter-related tooling.