As a tool for building things it varies. I only know about the web, and on the web it's probably best as a secondary language to do heavy lifting as a service rather that the backbone of a SaaS, where Django or Rails will get you where you want to be faster. Trying to do fairly common web stuff in Clojure is a lot more work and happens at a much lower level of abstraction, even if you use curated selections of libraries like Luminus. The focus on libraries over frameworks is enticing for some engineers who want to spend a long time building reliable and totalized systems, but for quickly building out features it leaves a lot to be desired. The ecosystem also assumes you are going to make an SPA in ClojureScript. (That said, Fulcro is fascinating if you are committed to the SPA way of doing things; the way it manages data flow seems unique, at least to me.)
Re-frame in particular is a gem. It's as though someone tried the React/Redux stack, thought long and hard about actions and selectors, and realized that with one or two more pieces, everything falls into a beautiful, purely functional harmony.
Clojure on the backend is awesome because you can use mature, stable, pure Clojure libraries for almost everything and it works together beautifully.
ClojureScript on the other hand suffers from constantly having to interop with JS libs and tools, or trying to wrap them, with many wrappers being outdated or having a bus factor of 1.
I feel like the advantages of CLJS have eroded over the past few years:
- TypeScript has fully taken over, generalizing the advantages of static typing
- JS libraries have adopted functional patterns, narrowing the comparative advantage of CLJS
- Tools like create-react-app have emerged to simplify project configuration, while CLJS is still figuring things out: https://clojurescript.org/news/2020-04-24-bundle-target
Speaking personally, having worked at one level or another with, looks like, 48 of the top 50 TIOBE languages over 30+ years (lacking OpenEdge ABL and ABAP) and about a third of the next 50 (where Clojure resides), Clojure is near the top of the list for me in terms of brain-bending value.
But also, I just have a really hard time giving up static types these days. Even for small projects, I always feel anxious about code that doesn't have annotations on function boundaries. It may have more to do with personal sensibilities than actual need, but regardless, it makes things unpleasant for me.
However, of all the not-static-typed language ecosystems I see, Clojurists seem to miss types the least. I have dabbled with the language and I know about `spec` but I don't quite seem to grasp why even shops using Clojure without spec don't seem to miss types much either.
Perhaps people more experienced with Clojure can shed light on this?
Still, even with all of the above I'd rather have them than not have them.
If you want to make a Person class or type I would recommend instead making your information model as a Datomic schema and have all your functions use associative destructuring with defaults values for missing data with function comments for good measure
For dev I would recommend Datahike over Datomic initially
In my experience (which I think resonates with RH and most Clojurists) is that for the vast majority of _information-driven_ systems, types are used as the latter. If you have a `Customer` class that gives you guarantees about the availability of, say, an `accountNumber` field, that is useful for correctness as you can be sure the information you need is there. However, if some future downstream coder wants to use your customer in the more general sense of being a human, then (s)he has to worry about sub/super/abstract-classing, may have to make upstream modifications to expose previously hidden data, and similar faffage. In Clojure the idiomatic solution to this problem is to "just use a map"; the real-world downside to this, however, is extremely weak contracts between functions. In this example, what `spec` allows you to do is strengthen those contracts by verifying that the data your function is provided is sufficient for your uses (as in the map contains all the keys you'll need with suitable data types in the fields) _without_ constraining what downstream consumers can also do with this data (extra map keys are ignored).
These checks are only done at runtime and only when enabled, however writing a spec gives you (very-nearly-almost) free generative testing that will run your function a default of 1000 times with random data in the correct shape to make sure it doesn't blow up—this isn't a guarantee of correctness, but it does provide extremely high levels of confidence (most type systems are also not even close to guaranteeing correctness either, only compliance with the type system). Spec also gives you (for free) performant runtime coercion for use in actual real-world code. You get a lot of bang for your buck.
`spec` is definitely not a type system, but it very capably fulfils a similar role in the kinds of programs Clojure was intended to be good at. It gives you all the flexibility and dynamism of Clojure with most of the confidence of static typing, without constraining either.
This assumes a very class-oriented type system. Duck-typing and the like don't have the upstream ontology problems.
My main desire from types is as an iteration assistant (with editor integration). Even if I wrote a function myself, I may not remember the exact order of arguments, or the exact name of that one property on the returned map. I want to a) be able to quickly peek and see what those things are - either by visiting the definition or, even better, via a pop-over in my editor - and b) have my editor tell me immediately if I did something dumb so I can correct it and keep moving.
In a dynamic language, whenever I need to double-check the contract for some code, I can't just go look look at its type signature, I have to go read through it. I have to fully load that whole subtree of information into my brain (recursively to any functions it may itself call), when I'm really trying to focus my thoughts on something else. This can be a huge, needless drain on mental resources.
Spec would help with this some, assuming the author follows a good convention of putting all of their assertions at the top of the function. But maybe those assertions are done inside conditionals, creating a more complex type. And maybe my editor doesn't know what to make of them (do any editors? genuinely curious). Etc. It just creates a bunch of little speedbumps to cognition that add up.
Yes, this is a headache, and certainly a problem that afflicts Clojure. Spec doesn't really help much in this regard. There is a proper static typing system for Clojure[0] that does provide a lot of the editor integration you speak of, but as I recall it was a little too brittle and orthogonal to Clojure's way of doing things to be as useful as spec. Some of Clojure's core constructs are completely impossible to type statically.
As with everything there are tradeoffs and choices to be made. I've been writing Clojure professionally for 5+ years now and there's no other language I have much interest in dealing with full-time (yet). One has to choose one's poison I suppose.
[0]: https://github.com/typedclojure/typedclojure(defn get-row-by-id [^Number id] ...) (defn get-row-by-user-name [^String s] ...)
There's also destructuring which both "extracts" local variables from data structures and serves as an informal documentation/description of the data shape.
You can combine type-hints and destructuring for a very powerful effect. Nowadays, I almost never have a problem "remembering" what or what shape of data I need to pass around.
Clojure has good foundations but the ecosystem just isn't there.
I've worked with both, and i wouldn't ever move away from the ease of refactoring and confidence that a statically typed language offers you.
Now the reason I mention this is: I "hate" static types. But then again, the only place I've actually used them was when writing Java in plugin-less Vim in college ten years ago.
Am I the outsider who just got a bad taste because I was lacking proper editor/IDE support?
For languages with good type systems, I'd recommend something like OCaml (or maybe F# - I know it's very similar and probably has a better ecosystem but I haven't personally used it).
Elm also seems decent - it's designed to be pretty simple and has JS interop but it doesn't have good tooling. Typescript is an alternative that has an okay type system but pretty quality tooling.
I'd recommend staying away from Haskell even though it's commonly recommended. The type system is quite complex and its use of monadic I/O makes writing "real" programs, even toy ones, difficult for many people. If you want to learn Haskell, I'd say start with OCaml/F#/Elm. Those are all very similar and will let you learn the fundamentals of ML-style programming before diving into Haskell-specific details.
If you try something like OCaml, you really don't need any special editor support (though it's still nice if you have it).
FP is the default idiom, state is very isolated and explicit.
The language is dead simple and often declarative.
The way you write Clojure programs is by attaching a REPL to your editor and by evaluating expressions in isolation. And you'll be running a linter like clj-kondo[1]. This has a similar effect as running a type checker (and it in fact does catch a lot of things that a type checker does).
Clojure spec can express much more than most (mainstream) type systems, is itself introspective and can be used for generative testing.
First class support for names: Keywords are not explicitly attached to their containers/data-structures but can be optionally be attached to namespaces and specs. This means a lot of problems that arise when changing code are simply avoided in Clojure. They still carry around the semantics and consistency you gave them, regardless of where they are used.
I've been working on a side project since about a year now and I can't remember when I last had a type error.