Yes you are. First of all there isn't such a thing as "strict typing", types are either static/dynamic and/or strong/weak. I suppose you meant Elixir has no static types. It is however a strongly typed language.
And just like it usually happens, static typing enthusiasts often miss several key insights when confronting dynamically typed languages like Clojure or Elixir (which was inspired by ideas implemented in Clojure).
It's not simply "white" and "black", just like everything else in the natural world.
You have to address:
- Runtime flexibility vs. compile-time safety trade-offs — like most things, things have a price to it, nothing is free.
- Different error handling philosophies. Sometimes, designing systems that gracefully handle and recover from runtime failures makes far more resilient software.
- Expressiveness benefits. Dynamic typing often enables more concise, polymorphic code.
- Testing culture differences. Dynamic languages often foster stronger testing practices as comprehensive test suites often provide confidence comparable to and even exceeding static type checking.
- Metaprogramming power. Macros and runtime introspection enable powerful abstractions that can be difficult in statically typed languages.
- Gradual typing possibilities. There are things you can do in Clojure spec that are far more difficult to achieve even in systems like Liquid Haskell or other advanced static type systems.
The bottom line: There are only two absolutely guaranteed ways to build bug-free, resilient, maintainable software. Two. And they are not static vs. dynamic typing. Two ways. Thing is - we humans have yet to discover either of those two.
You act like OP has never experienced dynamic type programming.
They clearly said they "can't go back to" it, meaning they've experienced both, are aware of the trade-offs, and have decided they prefer static types.
> Gradual typing possibilities. There are things you can do in Clojure spec that are far more difficult to achieve even in systems like Liquid Haskell or other advanced static type systems.
That's great for clojure and python and PHP, but we're not talking about them.
You act as if I said anything about anyone's experience. "they prefer static types" can mean a whole lot of things - there's type inference, soundness, turing-completeness, type classes, GADTs, higher-kinded types, dependent types, gradual typing, structural vs nominal typing, variance annotations, type-level programming, refinement types, linear types, effect systems, row polymorphism, and countless other dimensions along which type systems vary in their expressiveness, guarantees, and ergonomics.
Dynamic typing also varies - there's type introspection, runtime type modification aka monkey patching, different type checking strategies - duck typing & protocol checking, lazy & eager, contracts, guards and pattern matching; object models for single & multiple dispatch, method resolution order, delegation & inheritance, mixins, traits, inheritance chains, metaprogramming: reflection, code generation, proxies, metacircular evaluation, homoiconicity; there are memory and performance strategies: JIT, inline caching, hidden classes/maps; there are error handling ways, interoperability - FFI type marshaling, type hinting, etc. etc.
Like I said already - things aren't that simple, there isn't "yes" or "no" answer to this. "Preferring" only static typing or choosing solely dynamic typing is like insisting on using only a hammer or only a screwdriver to build a house. Different tasks call for different tools, and skilled developers know when to reach for each one. Static typing gives you the safety net and blueprints for large-scale construction, while dynamic typing offers the flexibility to quickly prototype and adapt on the fly. The best builders keep both in their toolbox and choose based on what they're building, not ideology.
In that sense, the OP is wrong - you can't judge pretty much any programming language solely based on one specific aspect of that PL, one has to try the "holistic" experience and decide if that PL is good for them, for their team and for the project(s) they're building.
Runtime flexibility is not restricted to dynamically typed languages, it just happens to be less available in some of the popular statically typed languages.
Error handling, expressiveness, testing culture, meta-programming and gradual typing have nothing to do with static vs dynamic typing.
The main "advantage" of dynamically typed languages is that you can start writing code now and not thing about it too much. Then you discover all the problems at runtime...forever.
Statically typed languages force you to think about what you are doing in advance a lot more which can help you avoid some structural issues. Then when you do refactor computer helps you find all the places where you need to change things.
Dynamically typed languages force you to write more tests that are not required in statically typed languages and that might prompt you to write other tests but if also increases the chance you just give up when you start refactoring.
Finally, after some time has passed and few updates have been applied to the language and libraries, you may not have a working project anymore. With statically typed languages you can usually find and fix all the compile errors and have the fully working project again. With dynamically typed languages, you will never know until you explore every line of code, which will usually happen at runtime and on the client computer.
i see things like that written almost on the daily but i've never seen it come to pass.
this is anecdotal but i've worked professionally in ruby and clojure and it was a pretty good experience. java/kotlin/scala made me wish i could go back to dynamic land...
these days i'm in a rust shop but i keep clojure for all my scripts/small programs needs, one day i intend to go back to some kind of lisp full time though.
Sure you're right on most of this, but allow me a slight pushback here. I am sorry, I am inclined to use Clojure/Lisp in my examples, but only because of its recency in my toolbelt, I could probably come up with similar Elexir examples, but I lack intimate familiarity with it.
- Dynamic languages can harbor bugs that only surface in production, sometimes in rarely-executed code paths, yes. However, some dynamically typed languages do offer various tools to mitigate that. For example, take Clojurescript - dynamically/strongly typed language and let's compare it with Typescript. Type safety of compiled Typescript completely evaporates at runtime - type annotations are gone, leaving you open to potential type mismatches at API boundaries. There's no protection against other JS code that doesn't respect your types. In comparison, Clojurescript retains its strong typing guarantees at runtime.
This is why many TS projects end up adding runtime validation libraries (like Zod or io-ts) to get back some of that runtime safety - essentially manually adding what CLJS provides more naturally.
If you add Malli or Spec to that, then you can express constraints that would make Typescript's type system look primitive - simple things like "The end-date must be after start-date" would make you write some boilerplate - in CLjS it's a simple two-liner.
- Static type systems absolutely shine for refactoring assistance, that's true. However, structural editing in Lisp is a powerful refactoring tool that offers different advantages than static typing. I'm sorry once again for changing the goalposts - I just can't speak specifically for Elixir on this point. Structural editing guarantees syntactic correctness, gives you semantic-preserving transformations, allows fearless large-scale restructuring. You can even easily write refactoring functions that manipulate your codebase programmatically.
- Yes, static typing does encourage (or require) more deliberate API design and data modeling early on, which can prevent architectural mistakes. On the other hand many dynamically typed systems allow you to prototype and build much more rapidly.
- Long-term maintenance, sure, I'll give a point to statically typed systems here, but honestly, some dynamically typed languages are really, really good in that aspect. Not every single dynamic language is doomed to "write once, debug forever" characterization. Emacs is a great example - some code in it is from 1980s and it still runs perfectly today - there's almost legendary backward compatibility.
Pragmatically speaking, from my long-term experience of writing code in various programming languages, the outcome often depends not on technical things but cultural factors. A team working with an incredibly flexible and sophisticated static type system can sometimes create horrifically complex, unmaintainable codebases and the opposite is equally true. There's just not enough irrefutable proof either way for granting any tactical or strategic advantage in a general sense. And I'm afraid there will never be any and we'll all be doomed to succumb to endless debates on this topic.
> The bottom line: There are only two absolutely guaranteed ways to build bug-free, resilient, maintainable software. Two. And they are not static vs. dynamic typing. Two ways. Thing is - we humans have yet to discover either of those two.
That's true but some languages don't let you ship code to prod that multiplies files by 9, or that subtracts squids from apricots
> that multiplies files by 9, or that subtracts squids from apricots
I don't understand why when someone mentions the word "dynamic", programmers automatically think javascript, php, bash or awk. Some dynamically typed PLs have advanced type systems. Please stop fetishizing over one-time 'uncaught NPE in production' PTSD and acting as if refusing to use a statically typed PL means we're all gonna die.
A funny thing is I once had a type bug while coding elixir, that bash or perl would've prevented, but rust or haskell wouldn't have caught. I forgot to convert some strings to numbers and sorted them, so they were wrongly sorted by string order rather than numerical order.
In haskell (typeclasses), rust (traits), and elixir comparison is polymorphic so code you write intending to work on numbers will run but give a wrong output when passed strings. In perl and bash < is just numeric comparison, you need to use a different operator to compare strings.
In the case of comparison elixir is more polymorphic than even python and ruby, as at least in those languages if you do 3 < "a" you get a runtime error, but in general elixir is less polymorphic, ie + just works on numbers, not also on strings and lists and Dates and other objects like python or js.
I also experienced more type errors in clojure compared to common lisp, as clojure code is much more generic by default. Of course noone would want to code in rust without traits, obviously there are tradeoffs here, as you're one of the minority in this thread recognizing. There is one axis where the more bugs a type system can catch the less expressive and generic code can be. Then another axis where advanced type systems with stuff like GADT can type check some expressive code, but at the cost of increasing complexity. You can spend a lot more time trying to understand a codebase doing advanced typesystem stuff than it would take to just fix the occasional runtime error without it.
A lot of people in this thread are promoting gleam as if its strictly better than elixir because statically typed, when that just means they chose a different set of tradeoffs. Gleam can never have a web framework like Phoenix and Ash in elixir, as they've rejected metaprogramming and even traits/typeclasses.
>>> open('foo') * 3
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
TypeError: unsupported operand type(s) for *: '_io.TextIOWrapper' and 'int'
You have to go to some length to get Python to mix types so badly.
Well, yes, Python can sure feel pretty fucking awkward from both perspectives. It started as fully dynamic, then added type hints, but that's not the main problem with it, in my opinion, the problem that you're still passing around opaque objects, not transparent data.
Compare it with Clojure (and to certain extent Elixir as well). From their view, static typing often feels like wearing a suit of armor to do yoga - it protects against the wrong things while making the important movements harder.
- Most bugs aren't type errors - they're logic errors
- Data is just data - maps, vectors, sets - not opaque objects
- You can literally see your data structure: {:name "Alice" :age 30}
- The interesting constraints (like "end-date > start-date") are semantic, not structural and most static type systems excel at structural checking but struggle with semantic rules - e.g., "Is this user authorized to perform this action?" is nearly impossible to verify with static type systems.
What static types typically struggle with:
- Business rules and invariants
- Relationships between values
- Runtime-dependent constraints
- The actual "correctness" that matters
Static type systems end up creating complex type hierarchies to model what Clojure does with simple predicates. You need dependent types or refinement types to express what clojure.spec handles naturally.
Elixir also uses transparent data structures - maps, tuples, lists, and structs are just maps. It has powerful pattern matching machinery over type hierarchies - you match on shape, not class. Elixir thinks in terms of messages between processes and type safety matters less when processes are isolated.
Python's type hints do help with IDE support and catching basic errors, yet, they don't help with the semantic constraints that matter. They add ceremony without data transparency. You still need runtime validation for real invariants.
So Python gets some static analysis benefits but never gains a truly powerful type system like Haskell's, while also never getting "it's just data" simplicity. So yes, in that sense, Python kinda gets "meh" from both camps.
I'm not sure what you're saying, but just in case if you're implying that choosing to use something like Clojure or Elixir adds "unnecessary layer of problems" just because they are not statically typed, let me remind you that "simplicity" is basically Clojure's middle-name. Rich Hickey made a seminal talk on simplicity, https://www.youtube.com/watch?v=SxdOUGdseq4 it's quite eye-opening and isn't really about Clojure. Every programmer should watch it, perhaps even multiple times throughout different stages of their career progression.
I don’t imply anything. I state (pun very much intended).
I cannot consider seriously anybody who thinks that programmer convenience and maintainability are different things on any level. And focusing on one doesn’t need the other.
Also this speech is academic blabla. I understand it, but there is a reason why he had to build a completely new vocabulary… because it’s not simple and easy at all. And there is a reason why there is exactly zero examples in almost the whole speech.
Because I can give you a very, very good example to contradict the whole speech: NAND gate.
Also you can code in Haskell basically with the same logic as in imperative languages (I used Clojure rarely, so I cannot say the same). So the “simplicity” is there only if you want to. But that’s true also the other way around: you can have the same “simplicity” in languages where mutability is the default. I agree that you should do immutability by default, but it’s stupid to enforce it. And he said the same thing, just it was a half sentence, because it contradicts everything what he preached: there are cases when his simplicity toolkit is the worse option.
Ruthless immutability causes less readable and maintainable code in many cases. And I state it, and I can give you an example right away (not like Hickey did): constructing complex data.
Also every time when somebody comes up with ORM and how bad it is, I just realize that they are bad coders. Yes, a lot of people don’t know how to use them. But just because it allows you to do bad things, doesn’t mean that it’s bad. You can say the same thing about everything after all. Every high level languages are slower than code in Assembly. Does that mean that every high level languages are OMG, and we should avoid them? Obviously not. You need to know when to touch lower layers directly. This is especially funny because there is a thread about that part on Reddit, because he used some vocabulary which is basically non existent, and the thread is a clear example of that people don’t even know what’s the problem with it. It’s a common knowledge that it’s bad, and they don’t know even why. For example, whoever fuck up a query through ORM, would fuck up the same way just with different syntax, like with a loop, because they clearly don’t know databases, and they definitely don’t even heard about normal forms.
And yes I state it again, using flexible type systems add unnecessary layer of problems. I also like that he mentioned Haskell, because it makes it clear, that his speech is completely orthogonal to the discussion here.
You seem to be making several interconnected points, but your writing style is making it somewhat difficult to follow.
It looks like you're mischaracterizing Hickey's talk. The distinction between "simple" (not intertwined) and "easy" (familiar) isn't "academic blabla' - it's fundamental to building maintainable systems.
Your NAND gate example actually supports his point: we build higher-level abstractions precisely because working at the lowest level isn't always optimal. That's what Clojure's immutable data structures do - they provide efficient abstractions over mutation.
As for "constructing complex data" being harder with immutability - have you ever heard about Clojure's transients or update-in, lenses or Specter? They make complex data manipulation both readable and efficient.
The ORM critique isn't about "bad coders" - it's about impedance mismatch between relational and object models. Even expert users hit fundamental limitations that require workarounds.
Calling dynamic typing an "unnecessary layer of problems" at this point is pretty much just your opinion, as it is clear that you have one-sided experience that contradicts everything I said. The choice between static and dynamic typing involves real tradeoffs; there isn't a clear winner for every situation, domain, team, and project, you can "state" whatever you want, but that's just fundamental truth.
It’s academic blabla, because as I stated it created a new vocabulary for no good reason. You can say the same thing without introducing it. You can say those things even better with examples.
I told you that I don’t know clojure, I know Haskell, which was also topic of Hickey’s presentation. I also know builder pattern… But looking into it:
> Clojure data structures use mutation every time you call, e.g. assoc, creating one or more arrays and mutating them, before returning them for immutable use thereafter.
So the solution is mutability… when Hickey preached about immutability. And basically builder pattern. A pattern which is in almost every programming language, most of the times a single line. So… immutability is not the best option every time, just as I stated, and it causes worse code. We agree.
You hit limitations with everything. ORM is not different regarding that. When I write s.isEmpty() in a condition in Java, I had to use a workaround… very painful. And yes, I state that if you think an entity is a simple class, then you are a bad coder, and if you cannot jump this “complexity”, you will never be a not bad coder. Same with sockets, ports, and pins.
Your last paragraph is totally worthless. You can say the same thing about everything.
I simply don’t preach. And I also wouldn’t say such things without considered an expert in ORM for example. I also wouldn’t say anything about which I don’t have first hand experience.
And as I stated, no matter what’s your tooling, bad coders will make bad code. On the other hand, you don’t need restrictive tooling to make good code. So what’s my problem is that, you don’t need Clojure or Haskell. It’s good to see something like that in your life, the reasoning etc, but it’s pointless to say, that you must be “simple”, when we can see that that is simply not true. Not even in Clojure according to Clojure.
You seem to have been frustrated with prescriptive programming advice and language evangelism, which is understandable. Yet it looks like you're conflating Hickey's design philosophy with language zealotry. The talk's core message about reducing accidental complexity remains valuable, even if the presentation style and some specifics, sure, can be characterized as debatable.
I can buy your vocabulary criticism - fair point, okay. Although vocabulary often can help crystallize concepts. Clojure's internal mutation - alright, accurate observation. Clojure uses mutation internally for performance while presenting an immutable interface. This does show pragmatism over purity. "Tooling doesn't fix bad coders" - true. No language or paradigm prevents bad code if developers don't understand the principles. Immutability isn't always best - correct. Even functional languages acknowledge this (IO monads, ST monad, etc.), but Clojure doesn't force immutability - it provides default immutability with escape hatches.
Now things that I can't agree with:
"Academic blabla" is outright dismissive. The talk addresses real architectural problems many developers face. The criticism of ORMs isn't just about "complexity" - it's about impedance mismatch, hidden performance costs, and leaky abstractions. These are well-documented issues. "Must be simple" is a misrepresentation of Hickey's point. He advocates for simplicity as a goal, not an absolute rule. Builder pattern equivalence is oversimplification. Persistent data structures offer different guarantees than builders (structural sharing, thread safety, etc.).
I think a lot of people feel repulsed by dynamic typing for the same reason I used to: they wrote a lot of JS, and now they write a lot of TS. The experience of working in a JS codebase vs working in a (well-typed and maintained) TS codebase is a wide, wide gulf. I love working in TS, and I absolutely despise working in JS.
For a while I extrapolated my experience to mean “static typing is awesome and dynamic typing is horrible”, but then I started learning clojure, and my opinions have changed a lot.
There are a ton of things that make a codebase nice to work with. I think static typing raises the quality floor significantly, but it isn’t a requirement. Some other things that contribute to a good experience are
- good tests, obviously. Especially property based tests using stuff like test.check
- lack of bad tests. At work we have a very well-typed codebase, but the tests are horrible. 100 lines of mocks per 20 lines of logic. They’re brittle and don’t catch any real bugs.
- a repl!!
- other developers and managers who actually care about quality
All four of these examples seem pretty easy to find in the clojure world. Most people don’t learn clojure just to get a job, which is maybe a hidden feature of building your company with a niche language.
At the same time, I recognize that most of those examples are “skill issues”. Static typing does a good job of erasing certain skill issues. Which is great, because none of us are perfect all the time!
Exactly this. When speaking about static or dynamic type systems, you can't discuss them in isolation, you need to specify because the holistic experience of types in JS, Python, Clojure, Elixir, Haskell, Rust, etc. will differ, sometimes drastically. Leaning too much to any side is perilous; it may create a false narrative in your head about what types fundamentally mean and how they should be used.
Camping too long on either side may create wrong assumptions like that all/most problems are type problems; make you conflate implementation with concepts and make you miss the real tradeoffs; you'd start ignoring context and scale.
Static types aren't always about "safety vs. flexibility" - sometimes they're about tooling, refactoring confidence, or documentation. Dynamic types aren't always about "rapid prototyping" - sometimes they enable architectural patterns that are genuinely difficult to express statically.
One really needs to see how Rust's ownership system or Haskell's type inference offers completely different experiences, or how Clojure's emphasis on immutability changes the dynamic typing game entirely.
> Over time you will probably feel drawn to both, for different reasons
I agree 100%. At first I liked C# and Java types, but then I moved to Python and I was happy. Learning some Typescript pulled me back into the static typing camp, yet then I discovered Clojure it revealed to me how needlessly cumbersome and almost impractical TS type system felt to me in comparison. Experimenting with Haskell and looking into Rust gave me a different perspective once again. If there's a lesson I've learned, it's that my preferences at any point in life are just that - preferences that seldom represent universal truths, particularly when no definitive, unambiguous answer even exists.
That's quite the list of languages. If you're interested in types I would suggest also looking into F#. Shouldn't be tricky for someone who has already experienced C# and is open to different approaches to programming. For me F# was a revelation into what programming language typing can do for the developer.
I do know and like F#, and just like the other commenter I think I personally prefer it over some Haskell's ways. F# was my first FP language, I was so excited about it, but it was a hard sell back then to my team of charpers, so I sneaked it in by writing tests in it for an otherwise altogether C# codebase.
Too sad that the dotnet world largely still operates in csharpland and F# unfairly gets ignored even within dotnet community. I never have found a team where F# is preferred over other options, and I'd absolutely love to see what it's like. Unfortunately, F#'s hiring story is far worse than other less popular languages and even if there is anything, you most likely end up supporting other projects in C#, and honestly, even though it's a fine language by many measures, I see C# as "past experience" that I personally am not too eager to try again and do it as my daily job.
I really like the way F# (and presumably ocaml) do typing, it truly feels like the best of both worlds. Rarely do you need to actually write a type anywhere, and when you do it's because otherwise there might have been a runtime error.
I wouldn’t say that static types remove the need for unit tests… but they do significantly reduce that need.
Static types and unit tests are not equivalent either. A static type check is a proof that the code is constructed in a valid way. Unit tests only verify certain input-output combinations.
Yes you are. First of all there isn't such a thing as "strict typing", types are either static/dynamic and/or strong/weak. I suppose you meant Elixir has no static types. It is however a strongly typed language.
And just like it usually happens, static typing enthusiasts often miss several key insights when confronting dynamically typed languages like Clojure or Elixir (which was inspired by ideas implemented in Clojure).
It's not simply "white" and "black", just like everything else in the natural world.
You have to address:
- Runtime flexibility vs. compile-time safety trade-offs — like most things, things have a price to it, nothing is free.
- Different error handling philosophies. Sometimes, designing systems that gracefully handle and recover from runtime failures makes far more resilient software.
- Expressiveness benefits. Dynamic typing often enables more concise, polymorphic code.
- Testing culture differences. Dynamic languages often foster stronger testing practices as comprehensive test suites often provide confidence comparable to and even exceeding static type checking.
- Metaprogramming power. Macros and runtime introspection enable powerful abstractions that can be difficult in statically typed languages.
- Gradual typing possibilities. There are things you can do in Clojure spec that are far more difficult to achieve even in systems like Liquid Haskell or other advanced static type systems.
The bottom line: There are only two absolutely guaranteed ways to build bug-free, resilient, maintainable software. Two. And they are not static vs. dynamic typing. Two ways. Thing is - we humans have yet to discover either of those two.