But the mental model is fundamentally different. It's not like you write a bunch of code, set a breakpoint and see what things are. You essentially boot up a lisp image and then you make changes to it. It's more like carving out a statue from a piece of rock rather than building a statue layer by layer.
Data shapes in Clojure typically explicit and consistent. The context usually makes things quite obvious. Data is self-describing - you can just look at a map and immediately see its structure and contents - the keywords serve as explicit labels and the data, well... is just fucking data. That kind of "data transparency" makes Clojure code easier to reason about.
In contrast, in many other PLs, you often need to know the class definition (or some other obscured shit) to understand what properties exist or are accessible. The object's internal state may be encapsulated/hidden, and its representation might be spread across a class hierarchy. You often can't even just print it to see what's inside it in a meaningful way. And of course, it makes nearly impossible to navigate such codebases without static types.
And of course the REPL - it simply feels extremely liberating, being able to connect to some remote service, running in a container or k8s pod and directly manipulate it. It feels like walking through walls while building a map in a video game. Almost like some magic that allows you to inspect, debug, and modify production systems in real-time, safely and interactively, without stopping or redeploying them.
Not to mention that Clojure does have very powerful type systems, although of course, skeptics would argue that Malli and Spec are not "true" types and they'd be missing the point - they are intentionally different tools solving real problems pragmatically. They can be used for runtime validation when and where you need it. They can be easily manipulated as data. They have dynamic validation mechanisms that static types just can't easily express.
One thing I learned after using dozens of different programming languages - you can't just simply pick one feature or aspect in any of them and say: "it's great or horrible because of one specific thing", because programming languages are complex ecosystems where features interact and complement each other in subtle ways. A language's true value emerges from how all its parts work together, e.g.,
- Clojure's dynamic nature + REPL + data orientation
- Haskell's type system + purity + lazy evaluation
- Erlang's processes + supervision + fault tolerance
What might seem like a weakness in isolation often enables strengths in combination with other features. The language's philosophy, tooling, and community also play crucial roles in the overall development experience.
If one says: "I can't use Clojure because it doesn't have static types", they probably have learned little about the trade they chose to pursue.