Hacker News new | past | comments | ask | show | jobs | submit login

I think you are omitting the main reason why Clojure never succeeded: it's dynamically typed at the core.

It's on the wrong side of history in that respect.

I know it's trying very hard to catch up to statically typed languages now by retrofitting some type system, but it's too little, too late.

Static types are where the current state of the art is, and we're not going back. Clojure missed that train and will never catch it now.




Static types are the new religion, like OOP was in the 90s.

Types solve 2% of programming errors. They lead to coupling for the sake of the compiler, and make code harder to change, harder to write, and often needlessly constrain the utility of the code where they're applied.

But they do make the IDE code-completion go, so there's that.


My experience is the opposite. Types make code much easier to refactor, in fact almost effortlessly so and with a certain confidence the code won't be hopelessly broken afterwards.

They also make code easier to reason about since the coupling that exists because of types is the result of an explicit and thoughtful decision on the part of the author. You don't as often get into situations where the code appears to work based on superficial similarity of data, only to find out there is some deep difference which prevents it from ever working.

It's also the truth that the more expressive the type system is, the less coupling you are required to do. For instance, if the language supports type classes (traits), you can codify the fact that a part of the system requires not data of a particular type, but of any type supporting some set of operations. This is the very thing you imply is lost when coming from dynamic types, only this time it's tool-backed and not merely accidental.


I think what the most import for refactoring, is about runtime.

When you get a totally unfamiliar code base, only if you can run it over and over, or at least run the tests over and over, then you can talk about the refactoring. If you can't, the refactoring is almost impossible, not matter how strong the type system is.

For an argument which require type of string, the behavior could be unexpected if you just give it a string.


Yeah, sometimes I look at some Typescript or Scala code and feel like I don't even understand what the heck they are trying to build anymore.

Very often it feels like they're "elegantly" trying to solve some made-up, bullshit problems for some questionable gain. Feels like bureaucracy for the sake of bureaucracy.

Totally like OOP shit that we're still trying to make sense of, like:

public abstract class Ellipse2D extends RectangularShape

What's the problem? Ellipses are rectangles with some extreme rounded corners. That's exactly how Euclid described them.


You're aware that it's possible to write bad code in any language, even Clojure, right?


Look, I'm not trying to bash on static typing. I like type systems. I love Haskell's, for example. I missed static type checker in every single dynamically typed language, and Clojure is not an exception.

That being said, there's no conclusive evidence that dynamically typed systems can't be robust and scalable. Sometimes, dynamically typed systems make absolute sense, especially in the context of homoiconic language like Clojure, where you have a "true" REPL.

An analogy I can think of is wired headphones vs. Bluetooth headphones. Audiophiles would vehemently argue that you cannot deliver quality sound via wireless, and all professionals use wired headphones. But sometimes, wireless headphones are what you need - they grant you some freedom, for a small price - you have to charge them, you have to be close the source all the time, there's interference, etc. But at the end of the day, I rather charge them once in a while and enjoy the music.

After using Clojure for some time, now, whenever I need to program in a statically typed language - it feels like I'm a traveler, passing through a series of checkpoints in medieval China or something. Too much ceremony. Perhaps I'm just not smart enough to solve puzzles imposed by a type system over and over again. Maybe the simplicity that Clojure offers allows me to stay dumb and focused on the task at hand, and enjoy the ride.


> That being said, there's no conclusive evidence that dynamically typed systems can't be robust and scalable.

That was never the claim, though. It's possible to write anything in any language, witness the millions of lines of code that are written in PHP or FORTRAN today.

The question is trying to determine if there are characteristics of programming languages (such as their type system) that make achieving these goals easier, and which also possess other nice attributes (such as making the code easier to refactor and maintain, easier to navigate or learn by new hires, etc...).

In my experience, dynamically typed systems are harder to refactor, harder to understand, require more cognitive load to understand them, and are typically slower than statically typed languages. And because of the absence of type annotations and the 100% reliance on the (hoped) existence of tests, many developers simply decide not to refactor dynamic code for fear of breaking it, which leads to much more pronounced code rot with dynamically typed languages.

> Maybe the simplicity that Clojure offers allows me to stay dumb and focused on the task at hand, and enjoy the ride.

I'd argue the opposite: dynamically typed languages require you to hold a lot more stuff in your head (the types and what each object is and what they can do) whereas type annotations allow you to focus on more important things.


You don't have to explain benefits of statically typed languages to me, I'm not fresh from a bootcamp.

The flaw in any argumentation about programming languages is almost always universally stems from the fact that we eagerly paint everything either white or black.

And I've been coding for long enough to learn that there are no universal answers - clean OOP, or pure FP, dynamically or statically typed, garbage collected or manual memory management, etc. The answer is almost always: "it depends".

Looking at any specific language through a prism of your own beliefs guaranteed to form opinions that would be flawed.

You can't put all dynamically typed languages into the same bag - programming in Python is vastly different from programming in Clojure. Same way as you cannot do it for other properties of the language, like it being a Lisp or being hosted on JVM.


Totally agree, functional programming, and certainly the kind of programming you do with clojure, can get just as messy and far away from your goals as object oriented code.

Clear, organized, well-architected code, in any language, is the skill that takes a long time to develop. I do not personally find clojure makes this any easier than in any language.


> I do not personally find clojure makes this any easier than in any language.

Surprisingly, it does for me. After using many different languages, I find myself more productive in Clojure than in any other language I have used before. Every language I used before Clojure left a dent in my mental ability to appreciate what I do. It's not a single favorite PL of mine, but most other programming languages make me feel bored and not interested after a year or so of using them. They have so many inconsistencies and quirks that you slowly succumb to the inevitable - you become a hostage. Your language of choice becomes your mental prison. Your hobby becomes this thing where you dig up yet some another poorly documented inconsistency and brag about it to your colleagues and friends. You become an expert from burning too often and too much. The language becomes your identity because you've seen too many ugly parts of it.

You have no idea how many times I had to fight my anxiety and depression and seriously thought about leaving the field for good.

Learning Clojure has liberated me; it allowed me to maintain focus and put in use all the good things and patterns I learned over the years. In other languages, sometimes you have to bend over backwards to create something clean. Sometimes you have to build this colossal cathedral that requires enormous scaffolding just to hold its own weight, and you can't even remove the scaffolding, and you call that "an elegant solution."


I'm a Programming Language enthusiast, currently making my own language. I'm really curious what makes Clojure different - it's pretty much the most loved language among its proponents (i.e. people love e.g. Rust and Go as well, but not as much as those who love Clojure, love Clojure). Is there any very Clojure-y code you can point to, that would showcase it? Is it just the libraries - collections, concurrency primitives - that could be replicated in another language? It's can be just homoiconicity, as that's just Lisp, but it could definitely be a big part of it... Do you have any ideas what any other language (e.g. Python, TypeScript, Go, Rust, Julia, Haskell, OCaml) would need to change to make you as productive as Clojure?


You model your logic/domain as data using immutable values, and write functions that act on that data. There are a few good things that come from this design decision.

It means you can avoid getting into the situation where half your logic is encoded in the type system and enforced at compile time, and the other half as values at run-time. It's all values at run-time.

And for the same reason, you don't fall into the trap of "puzzle-solving" with a type system. Between run-time values and the compiler there is a world of infinite possibilities that some type systems (and I would also include some macro systems!) seem to encourage adding more and more layers to. Clojure tries to speak the language of data, which is a lot more grounded.


I have started programming when I was about 13-14, so it's half of my life now. Honestly, I never wanted to be a programmer. More of a writer/speaker/culture animator. I found Clojure in my first years of learning programming and, from start, it felt, like the only language that was really thought through, before creation. Clojure is the best because its syntax (or lack of thereof) along with data structures, namespaced keywords, specs, and whatnot, allows me to think properly. No other language gives me tools to think so clearly and plainly. I spent lot of time with JavaScript, some Python, some Ruby, a bit of Haskell. None of those really cares about giving you proper tools to think. When I need to use a language different than Clojure it's a burden now because I still think in Clojure. Or: I try to analyze and build a model of my domain without thinking about computers. The best programming language for that is Clojure. Other languages make you think about computers and, for me, that's waisted time


REPL driven development

We can develop our program while it's running.

And it's not a tricky thing that injects or restart anything, it's by design. Load file to REPL and now your functions are redefined. Just on this namespace. Load file/reload isn't a automagical thing. It's simple: just "stream" your file to the REPL. HTTP library, DB library, any library need to thing about "how do I hot reload", it's a language feature.

I see many developers arguing things like "types are important because avoid runtime errors". If you develop INSIDE runtime, you have no reason to fear runtime errors. Your runtime error will blow up during development process.

A cool thing about this pieces:

- I can start my app from REPL

- Connect my browser in this app

- Run my integration tests that create entities (inside the same REPL)

- See in the browser the state from app after run the test.


Since there are many answers already, I won’t elaborate too much. I just want to emphasize one thing: people mention immutable data structures as a default a lot, and sure, they’re great.

However, someone might turn around and say, “well, I can pull in a lib with immutable data structures in $lang if I want to.”

It needs to be highlighted that the real game changer is an /ecosystem of libraries/ built entirely out of immutable data structures.

That’s not something that can be engineered as needed.


I am not an expert Clojurist (yet) but I really love Clojure because it gets out of the way like Python but with the performance of Java. Almost all of the boilerplate is gone, and yes, static types next to each variable feels like boilerplate to me after doing enough Clojure.

Plus Clojure Core Teams laser focus on API stability makes 10 year old (and even unmaintained) libraries to just work. I have not come across a code snippet so far that did not work because of a breaking change, not saying they are not there, but I did not find it.


The survey answers to Q12 "How important have each of these aspects of Clojure, ClojureScript, or ClojureCLR been to you and your projects?" on the 2020 Clojure Survey results give some summary answers for several features of those languages, how important the people answering the survey finds them to be. https://www.surveymonkey.com/results/SM-CDBF7CYT7/


Being a Lisp is a big one for me for sure! Specifically, this means having a simple regular homoiconic syntax, great support for macros and meta-programming, and most important of all, a fully dynamic nature with REPL driven development and all constructs being reifiable at runtime, while still being fast and performant.

Yes, there are other Lisps, but Clojure also improved certain things compared to them:

- Clojure has way more reach. Being that it runs on the JVM, JS, CLR and has great interop. It means you can actually use Clojure instead of Java, JS and C# to do just about anything you could with those. That's not true of Common Lisp, Scheme, Racket, ELisp, where the main runtimes don't have a lot of money behind them, and where libraries trail behind.

- It disallows reader macros, so there's a limit to how wild people can customize it. This helps minimize the Lisp curse.

- It also has a pretty simple macro system, that is both hygienic, yet straightforward to use.

- It has a more visually pleasing syntax, by extending homoiconicity to also support maps, vectors, sets, regexes and keywords.

- It modernized some old remnants, like having first/rest instead of car/cdr.

- It ditched cons cells, and instead uses a proper sequence abstraction.

- It has proper true/false, and nil is not the same as the empty list.

On top of being a great modern and improved Lisp with bigger reach, it also is just a well designed language. Lets explore some of that:

- Functional programming as the default paradigm, but others are supported (logic, OOP, imperative) when it makes sense.

By default Clojure uses immutable persistent (fast and memory efficient) data-structures and variables are immutable. Functions support full closure over their environment. Anonymous functions are first class. Higher-order functions are included. Loops are handled recursively. The whole shebang. Yet, you can relax this in controlled way when it make sense. You can introduce controlled mutability, you can define polymorphic functions, you can create mutable types, etc.

- Every collection under the sun.

Data-structures are fundamental to computer-science, and Clojure has a bunch of them. Persistent Lists, Vectors, Maps, Sets, Queues. LinkedList, HashMap, ArrayMap, Array, DoublyLinkedList, HashSet, TreeSet, ArrayList, PriorityQueue, TreeMap, etc. All built on proper abstractions as well: Associative, Sequential, etc. And it has a ton of useful functions to operate over them as well.

- Awesome data manipulation constructs

Some people say information systems is all about taking information and transforming/moving it around. Boy does Clojure has you covered there. It has an awesome set of performant lazy data manipulation functions/abstractions called lazy sequences with things like: map, filter, remove, distinct, dedupe, group-by, sort-by, partition, split-at, replace, shuffle, reverse, rand-nth, etc. And, all of these are also available in an eager variant as well which performs loop fusion (called transducers).

- Great equality semantics

In Clojure, equal values are equal things. This is just awesome! Like, that's how a layman thinks of equality, and that's how programming languages should have it as well in my opinion. For performance, you can decide to use reference equality as well, but that's not the default.

- Full support for concurrency and multi-threading

Kind of self-explanatory, but Clojure has a lot of concurrency constructs which make it easy to write concurrent/multi-threaded programs.

- Types are open for extension and have good polymorphic support

A bit like traits and mix-ins, types can all be extended from outside their definitions, and polymorphism exists at many levels: dispatch based on the type of the first arg, dispatch based on the value or type of any arguments, dispatch based on the arity, dispatch based on some hierarchy, etc.

There's more obviously, but this is already pretty long. So I'll finish by answering your last question:

> Do you have any ideas what any other language (e.g. Python, TypeScript, Go, Rust, Julia, Haskell, OCaml) would need to change to make you as productive as Clojure?

Everything. I mean, they'd just need to become Clojure. The thing is, see how long my answer is? That's because it is not just one "killer feature" that makes Clojure awesome. Clojure has just the right balance of features all designed in just the right way to come together beautifully and coherently to create something that is greater than its parts. That said, you can have a look at Elixir, I think it gets closest to providing something that approximates Clojure.


Thanks! I got a few answers already, all of which were really helpful, but this one was most extensive! Another one, if you’ve time: What would you improve in Clojure?


Hum, that's an interesting question.

Error messages could be improved, currently, they often leak the implementation details, so the error is disconnected from your actual code.

Startup time is slow. There's ways around, like making a Graal Native build, or using ClojureScript or babashka instead, but those are all alternative thing you need to consciously choose to use. It be great if standard Clojure somehow could start really fast.

Memory usage could probably be improved further. It makes liberal use of Objects right now for everything, and that adds up quickly.

Performance is pretty fast, but I'll never say no to something that would perform faster.

I think I would make the data manipulation functions eager by default, and the lazy one would be the opt-in one.

There's a few names that could be improved, contains? is a famous confusing one, since it always checks for key, would have been better to call it contains-key?

When it comes to the language design itself, I'm not sure there's much I'd change. I think it could be interesting to try and see if you could build some language that's like Clojure and have some level of static type safety. I'm not sure what you'd have to trade away for it, but I think it be an interesting experiment.

Oh, and one more thing, doing unboxed things, operating over primitives could be improved and made easier. This is often needed for high performance numerics, or making better use of memory and caches.


This has been something I've been trying to answer and can't arrive at a conclusion.

I can't tell if I happen to have lucked out for the first time ever working with really good engineers, and that's why the Clojure code bases I currently work on are overall better. Or if it has anything to do with Clojure itself.

From my prior work experience: Scala, Java, C#, JavaScript, ActionScript 3, C++; the code bases were always kind of crap. Everything was always called "legacy code", even if it was something that we had built just the year before.

Similarly, in the open source, or even language core, things were always deprecating one after another, new release introducing breaking changes, and you had to constantly play the upgrade and refactor game to keep things working. That in turn contributed to making our own code bases so called "legacy", as the framework used even a few months back is being deprecated, or libraries you depend on that you can't upgrade to the newest release without breaking everything so you stick to outdated dependencies.

None of that happens in Clojure, things are mostly stable for decades.


I don’t know what percentage of errors are caught by static typing, but I do know what percentage of my time has been saved by switching to a statically typed language. I love clojure, but I spent at least an order of magnitude more time tracking down various bugs than I do now, simply because the compiler didn’t catch them for me.


I totally respect anyone for just choosing a language that works best for them. They are tools for craftsman, and often are about which one makes you a better programmer.

For me, Clojure definitely made me way more productive. My background was in statically typed language mostly prior, though I had done some Python and JavaScript as well. With Clojure I feel I'm between 30% to 300% more productive overall. I think some of this is personal, depends on your style and your own strengths and weaknesses.


https://news.ycombinator.com/item?id=19131272 . It don't know where your 2% number is coming from, but in Airbnb's experience it was much more. It feel like it would also be more, imo, but I guess it depe nds.

Typescript can be annoying but usually it helps a lot with refactoring. It's also pretty great for detecting unused props with react.

I don't use IDEs so my defense of typescript is only based on its type checking merits


Can you reference "Types solve 2% of programming errors"? I've read numerous research papers and blogs on this topic and have never seen anything as low as 2%.

Edit: Googled and nope, can't find anything.


Here are some which look at GitHub for various languages and count the defect rates by analysing commit messages:

https://dev.to/danlebrero/the-broken-promise-of-static-typin...

https://www.i-programmer.info/news/98-languages/11184-which-...

https://nextjournal.com/PRL-PRG/toplas-analysis

One thing to note is that the absolute difference in terms of bugs from the worst language to the best is still minimal. So language choice doesn't seem to make or break software.

Another thing to note is that while overall static languages faired better in terms of having lower defects, Clojure did best of all.

I knew of another one but can't find it again.

Then there are a few where they had beginner programmers implement trivial programs in different programming language and checked how many errors/time it took them. But I consider those all pretty bad since the experiments are so reductionist. So I won't list them. They aren't conclusive either. Some end up saying static and dynamic are same, some say dynamic is more productive at equal defect, and some say static had less defects at equal productivity.



> Types solve 2% .......

Vastly understated in my experience. Even trivial python programs contain errors that could be prevented with a trivial type system like Go's

And performance. Not being fast enough is a bug. Consuming much more energy is a bug, in my book.

Sufficiently expressive type systems are better than dynamic typing. They prevent so many errors, especially in FP-inspired high level data transformation functions. I am saying this as a person who doesn't even like most of FP. (You can infer from my comment history).


> like Go's

or Python's. It even has generics.


FYI: Clojure has types for performance, but not for static checking. They're called type hints: https://clojure.org/reference/java_interop#typehints


Static types turn certain runtime errors into compile time errors. When I learned Pascal in 1982 it became quite obvious what a huge benefit this was.

That said, I still like some dynamic languages for certain things.


I understand the issues with dynamic typing, but I don't think you can just compare Clojure to other dynamically typed languages, because Clojure is very data driven language. You have lists, vectors, maps and sets. Most of the time this is enough and these are the parameters to functions and also return values of those functions. And on top of that, you don't always have to think about these too, because of the unified sequence abstraction, which allows you to use the same core functions for processing/transforming those underlying types.


Python and JS succeeded long before they had a static type system. Many people still use Python and JS today without type annotations. Elixir is not statically typed either and seems to be doing fine.

I like static typing and agree that it will eventually "win", but I really don't see where you're coming from here.

> I know it's trying very hard to catch up to statically typed languages now by retrofitting some type system

What makes you say that?


spec


Describing the Clojure dev team as "trying very hard to catch up to statically typed languages by retrofitting some type system" because they work on spec is pushing a narrative that I doubt they'd agree with, both regarding spec's goals and its rhythm of development.


I'm not thinking highly of any narrative that the Clojure dev team believes. It is the pièce de résistance of the new "new" functional programming cult, which is in fact very old school.

Even just parroting some of the memes from this thread like "static types only prevent 2% of bugs" and "you can replace 12 developers with 3 amazing (italics) Clojure developers" and "a Clojure developer can replace any kind of developer!" means you don't have to make up absurd viewpoints to argue the contrary. These trends are old as time and completely nonsensical.


The dynamic vs static typing language tussle has been going on for pretty long. I think at this point in history the dynamic languages are doing much better than most times in PLT history. There was a time when C & Java were considered "serious" languages and dynamic languages were the underdogs.

I think dynamic languages are generally a good default, but the current world also matches them pretty well because programs talk to the outside world in a lot more varied ways than before and static typing is not that very good across distributed systems.

Static has a better case when talking about more powerful type systems than what mainstream languages have, but those don't really show accelerating signs of breaking through to mainstream. Rust is an exception in the recent history but it's not really poised to become a widely applicable app programming language.


Programming is like an exam. Good students know where errors might occur after they write code. Bad students do not know whether they are right or wrong and where they are wrong. Therefore, a variety of complex and lengthy error checking systems, such as static type systems. But this is a costly effort with little effect.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: