It's unfortunately part of the learning curve.
Being a beginner with clojure myself, I was put off by some of the more arcane error messages.
But compared to 'undefined is not a function' it's about 10 times better ;)
Glad to see this being improved.
Don't get discouraged by this. Even if you never plan to use Clojure in a project, dive in and take a good long gulp from the functional tea of enlightenment. It will make you a better programmer.
Clojure is just so coupled with the host language. Which isn't a bad thing, since it helps make interop effortless, but does make it difficult to appreciate as a standalone language.
I first tried to learn Clojure before I knew anything about Java and things like error messages made it really difficult to get started.
Coming back after having written Java professionally for a year, things like random Java exceptions showing up or long stack traces suddenly became a lot more tractable, although still not "beautiful".
I have been doing Java professionally for like 20 years now; and one of the things I liked a lot were its stack traces and error messages. When I see a Java stack trace I find it simple to get started with it to go understand the issue; with Clojure, my head aches; I try staring at the code to see if anything comes up. Same - or worse - for Spec errors - they are huge and you need a cup of coffee to get started deciphering them.
This said, Clojure is a lovely language, I have used it for 3+ years now and is my favourite. It has its drawbacks, as with everything else.
That implementation for "keyword" is simply horrible. If it's correct that most errors caused by bad input to functions come not from verification but from an arbitrary place in code where a type-specific operation is applied is correct, that's more horrible. This deeply discourages me from touching Clojure ever.
I think that impulse is correct. As a clojure-lover, the state of error handling, and particularly Rich Hickey's decision to neglect it for so long and not code defensively, makes me a little sick.
That said, clojure remains my favorite language despite this flaw, and I can't wait to spend more time in it. It's beautifully designed. The design communicates well how to use it well, and it's only when you're abusing the language (or making mistakes) that you run into this kind of stuff. Not an excuse really, I'm just excited that errors are going to get somewhat taken care of by incorporating spec into the core.
> it's only when you're abusing the language (or making mistakes) that you run into this kind of stuff.
Not really. My experience has been that most of the time the docstrings leave a lot of guesswork to be done when deciding what to pass to a function and how to pass it through, which causes errors and time lost because there's no other authority on a functions I/O (Common Lisp has a spec, for example). This behaviour has spread to the community too. So in Clojure one passes "things" around and Clojure will do "stuff" to those "things" until either one or both the "stuff" or the "things" break, in which case you either get nothing or somethings that's big but worth nothing. TBH it's been quite some time since I tried to do sth. w/ Clojure so my impressions could be outdated, but the article suggests otherwise.
Yes, its true the docstrings are terse. And its true that Clojure follows a pattern of garbage in, garbage out. And if the rest of Clojure was designed like Java or Python, it would be a huge problem.
But its not. And if you take the time, like 30 days, to learn Clojure to intermediate proficiency levels, these issues disapear.
Its hard for me to convince you of this, because its definitly asking for a leap of faith. But think of it like starting Kick Boxing. At first, your knuckles hurt, your chin hurts, but spend 30 days at it, and you just adapt and those pain points disapear. All of a sudden, you feel great, stronger, faster, more agile. That was my experience with Clojure. And ya, just like Kick Boxing, once in a while, your knuckle might hurt again, or your chin, but unlike in the beginning, its very rare, and goes away really quickly.
EDIT: To add more concrete weight to my argument. Here's some of the things I believe contributes to making those issues disapear. The consistency and simplicity of the syntax. Most docstrings call the same things with the same names, and have a similar writing style. The REPL driven development means you can quickly explore and self learn how a function behaves. The immutable data structures avoid a lot of mistakes and simplify a lot of logic. The higher level loops and branching do a similar thing. The easy composability of data transforms become second nature. The functional first style makes writing tests easy. You always end up with little code, so there's just less that can go wrong. And I think most importantly, the Clojure source is easy to read and very accessible, so its often a better documentation then any docstring could be.
As someone who has (mostly) struggled through, I'd (mostly) agree. I find that when I am running into errors it is almost always because (a). I am moving too quickly and have misplaced something simple like my parens (which parinfer would probably solve) or (b). am not leaning enough on the REPL and am spending too much time before re-evaluating code.
I find that the more I live in the REPL, the less frequent these errors are.
I think if you're in the staticly typed camp then Clojure isn't for you either way. While the keyword thing is reasonably bad, it's not the type of error I almost ever make (I'm definitely far from a perfect coder). I think your reaction is a little strong though, for a good reason. I think Clojure has many of these little nuances where strange parameters result in non exceptions, but I'm often surprised how useful they can be, and I think some are quite intentional. For instance, the map and merge functions can take nil collections as params and they have pretty useful properties. Usually if you see some weird stack trace it's typically easier than the author suggests to deduce type problems.
The author points out a stray "@" symbol in his code throwing a confusing message about Futures. Well futures are a pretty first class language feature, so that would be pretty easy for figure out. Their complaint should be more about the sugary syntax of derefing using "@" than the bad error message.
Clojure could use better stack traces, but I don't think throwing out a otherwise very elegant language is merited.
I don't use staticaly typed languages (nowadays I'm trying Dart though, for Flutter). My main languages are Elisp, Perl, Python and Ruby. And some Posix shell. I'm a fan of Lisp.
WRT keyword, I think that's a bug, not a nuance. This is the doc:
> Returns a Keyword with the given namespace and name. Do not use : in the keyword strings, it will be added automatically.
Nowhere does it say it returns sth. else than a keyword. Following is the doc for "intern" from Emacs Lisp, which does a similar job and signals "wrong-type-argument" with improper input:
---
intern is a built-in function in ‘C source code’.
(intern STRING &optional OBARRAY)
Return the canonical symbol whose name is STRING.
If there is none, one is created by this function and returned.
A second optional argument specifies the obarray to use;
it defaults to the value of ‘obarray’.
---
So it does what it says or fails otherwise. This sort of error handling and streamlined, nice debugging is the best part of interactive programming; otherwise it's just Java with a lot of parentheses.
Handling non-standard arguments in intuitive ways can be useful indeed, but not so if they are accidental, possibly working due only to implementation details, and undocumented.
The overall conclusion is correct, most error messages happen by accident, but a lot the examples look like expected behavior to me. Maybe because Ive been using Clojure for too long, or maybe because its the expected trade-off when using a dynamically typed language. You get more fluidity at the cost of type checks - The way to mitigate this downside is adding checks of your own. Clojure has supported this since way back when using pre/post conditions.
The math functions are statically inlined for performance. Wrapping them in another level of function/var will have a significant impact on performance even if assertions for preconditions are turned off.
Spec assertions (not pre/post checks) can actually be compiled completely out of production code so those can really be nothing (but not if you're adding them in wrapper layers).
In this respect, they share the characteristic of being easy to wholly disable (i.e. prevent them from being compiled at all). With pre & post conditions if the var `clojure.core/assert` is bound to false, then they're elided. The usual pattern is to have them on for testing, and disable when compiling for production.
> The worst sin of stack traces is that the most important information is printed first, then followed by less and less important information, usually a few screenfuls of it.
No. It’s at the top of the web browser window. At the top of the unit test out window in the IDE and so on. Flipping stack traces to help console reading would be a mistake. Should be made optional in that case.
> But if the official spec doesn’t restrict get, Spec will let people redefine the spec for get for themselves.
> They can choose whatever subset of the type they want.
Total clojure-spec noob question: is this type scoped somehow, or does using your own type mean chasing down bugs in libraries that assume a looser version?
It's scoped in that you choose what gets loaded. I think it's generally a good idea for a library to provide specs as an optional namespace and let you choose whether and when to load it.
> It is more useful to think of error messages as non-existent than to think of them as bad.
If this is true, my respect for Clojure just completely dropped.
Error messages are a crucial part of a language and they are instrumental to the robustness of the programs it helps create.
The fact they have been so severely neglected by the Clojure teams makes me think the authors of the language don't really understand modern large scale programming.
That's quite a judgement call to make based on the utility of error messages. I can't remember the last time I was blocked based on a Clojure error message anyway. Errors are less frequent and usually pretty obvious.
I think you're wrong to say core devs don't understand modern programming, but you'd probably be right to say that they didn't prioritize making it approachable and stooping to the lowest common denominator. And it's not just Clojure, but datomic, core.async, spec -- all solving real problems in modern large scale programming with elegance.
Watch Rich's 'Effective Programs' talk [1] and lmk if you still feel that he's out of touch with modern large scale programming. I think he's pretty in tune.
> making it approachable and stooping to the lowest common denominator.
Why do people assume that being dev-friendly is the "lowest common denominator"?
Devs are not stupid.
Beginners are not stupid.
Moreover, the more senior you are, the more you begin to appreciate clear precise error messages that directly point to the problem.
There's only so much brainpower, and I'd rather spend it on solving a problem, and not on figuring out/memorizing yet another cryptic error message.
And yes, no amount of fluffy talks or "large scale apps built with datomic" will convince me that those devs are in touch with modern programming. They are just used to what they work with.
My point was simply that Rich built Clojure to solve his own problems, not to coddle beginners. Beginners are the lowest common denominator not because they are stupid but because they are inexperienced, by definition.
> Moreover, the more senior you are, the more you begin to appreciate clear precise error messages that directly point to the problem.
Hopefully the more senior you are, the less you encounter errors in the first place.
> There's only so much brainpower, and I'd rather spend it on solving a problem, and not on figuring out/memorizing yet another cryptic error message.
And I'm saying that's not the Clojure experience once you're up and rolling. You iterate over program construction in small manageable chunks, with immediate feedback from an editor jacked into a cider repl, and your repl experiments evolve into long-standing tests.
Also, even if the Clojure language doesn't necessarily cater to the beginner, the Clojure community definitely does. There are constant efforts to evolve better docs, extend tooling, and build user groups that reach out to beginners.
> coddle beginners
> stooping to the lowest common denominator
You keep saying that people are stupid. And since you keep iterating it, I believe this is the point you are making, not some speculation about what Rich Hickey does or does not.
> if the Clojure language doesn't necessarily cater to the beginner
A dev-frienndly language does not cater just to beginners. Everyone benefits.
> There are constant efforts to evolve better docs, extend tooling, and build user groups that reach out to beginners.
So, they do stoop to the lowest common denominator and coddle? Why can't Clojure?
I think that you're doing it right as long as you're using a language that helps you be productive and brings you joy. Sounds like you're already good where you're at.
> I didn't say that, because I don't feel that way.
Yet your language betrays your feelings.
I would never in my life use words like "stoop to lowest common denominator" or "coddle beginners" when talking about beginners (or other developers in general).
The rest of your message is a thinly veiled contempt at best. So, good weekend to you, too.
> My point was simply that Rich built Clojure to solve his own problems, not to coddle beginners.
And if Rich likes Clojure, he can go nuts with it.
But a significant chunk of Hackernews believes that Clojure is the greatest thing since sliced bread, or even the obvious successor/replacement to all other Lisps. As an experienced programmer myself, if someone told me that Clojure is better suited to my application needs than CL or Scheme, I would tell them no, and fuck you. And this is part of why; there are other deficiencies and quibbles I have with Clojure's design.
As a clojure programmer myself, it surprises me to see other clojure programmers say this. Error messages are an important part of programming and that you didn't face many errors doesn't mean others won't and doesn't mean they don't know how to program either. Even the article above clearly states that error messages are bad.
Which part do you disagree with? Are you commonly blocked by Clojure errors where you have to stop and spend a material amount of time deciphering them?
Nowhere have I said that if you see an error message you don't know how to program.
I face errors every day, but I don't face errors that are so cryptic that they block my productivity. We agree errors are useful even if we disagree that Clojure's errors are bad.
I did find the errors very confusing when I was getting started, but I think that was because the errors use Clojure terminology that I had not yet learned.
The simple fact that Clojure is dynamically typed tells me that this language is simply not suited for large scale programming.
The trend in programming languages is pretty clear, we are moving more and more strongly toward languages that are statically typed and with type inference.
The truth is, the issues that come out of your current model of programming makes you seek something that trades them out for other issues. Right now, those other issues are unknown to you, and you're not feeling the pain they bring. 10 years from now, they'll be all you complain about, and the trend will go back.
Now, maybe I'm wrong, but its rare that a truly better paradigm shows up. It happened before though, so might happen again. Maybe that'll be inferred static types, but we've had those for so long already that I'm doubtful. For example, Clojure has infered static types, a lot of people tried it and felt it was more trouble then worth. So in the small Clojure world, the trend to infered static types already happened and passed by back to runtime contracts.
It's not really clear to me what you mean with "scale" here. Are you referring to the scale of a program, i.e. size in LoC and/or complexity of components, or to the scale of a team of developers, i.e. the number of persons working on a given codebase?
Scale in performance, since dynamically typed languages have fundamental hurdles that will always make programs slower than when written in a statically typed language.
And scale in code base and developer strain. The fact that it's mathematically impossible to reliably refactor code automatically if the types are absent encourage spaghetti code bases that developers are afraid to modify because they can never be sure they're not breaking anything.
Such hesitations never happen in statically typed languages.
Having written a substantial full-stack Clojure app, I really don't find the lack of meaningful error messages to be a problem. I think you're underestimating how radically different Clojure is from most other languages.
Idiomatic Clojure code is written in atomic, easily tested units. If you're throwing exceptions, you should be using ex-info and creating the error message yourself when its thrown (so a bad message is your own damn fault). If the error is thrown by clojure.spec, it's a type error and the source is obvious. If the error is thrown by the JVM, the stack trace often points to the exact line of code causing the error because your code is written in small, atomic functions in the first place.
In short, if you approach Clojure like Java, yes, the error messages are a problem. If you approach it like Clojure, it's really just not a problem.
That’s not true at all. I have built pretty big applications in clojure, and it’s been nothing but joy to use. There are times when I don’t know how I came to a situation, but that’s very rare. Most of the time I make a large change and it works on the first try; brings a smile to my face every time.
I think, I may be wrong here, clojure works very well for people with a little bit of experience and those who think things through before they write code.
I have my guards when I write clojure though. I always write tests, and in the beginning I used to make small git commits, though I don’t need to do that now anymore.
Going to have to disagree with you there—if for no other reason than the tooling around Java is much more robust and available than for Clojure. I’m thinking static analysis, IDE support, fuzzers, linters, etc.
Clojure has potential, but the lack of good feedback when things go wrong makes it _very_ hostile to users of the language, especially when first trying to pick it up.
I've worked with large Java applications and large Closure applications. The effectiveness of the tooling in clojure, in particular Emacs and nREPL-based tooling, makes local and remote programming and debugging, even in production miles better than anything I experienced in Java. I don't remember how it was when I started, but as an expert user (the only case I particularly care about), nothing I've tried except Common Lisp approaches Clojure.
I guess all we can do is provide our individual datapoints. From my experience, I find it much easier to do so with Clojure. Systems generally last longer, almost never need refactoring, accumulate a lot less tech dept, and easily accommodate future requirements when written in Clojure as opposed to Java. I come from a team who has a 50/50 set of systems in java and clojure, been the case for 3 years. Team size averages around 8 devs.
Tooling in Clojure is actually pretty great. Generative testing, linters and IDEs I find work better then in Java personally.
Where I agree with you is learning curve. Clojure is definitly difficult for new users to pick up. The community and core team I think are more focused on improving that now then ever though. So hopefully it'll change. Also, I'm not sure hostile is a good word, if anything, the best part about Clojure is the community is actually really nice, helpful and supportive, even to new users. There's no trolls, and a lot of great open dialogue, active members ready to help you on chat and forums at all times, etc.
True, but I don’t need most of the those tools. Static analysis is most useful when working on large teams. When 3 or 4 people are working on a project, it is mostly unnecessary. I’m not saying error messages are not useful, or static analysis isn’t, they are, but not as much as people are thinking here(wrt clojure)
There is 3-5x difference in productivity between java and clojure most of the time, just because I don’t have to read a lot of code to make a change. Clojure, written right, feels like math equation on a board. My eyes don’t dart around up and down figuring out the code. It is terse, to the point and immutable.
It is subjective, but clojure vs Java is like doing math with Arabic numerals vs doing them with Roman numerals. I’m sure we need lot more help when doing math with Roman numerals, but that level of help is simply not necessary, and is more about the medium than the message
> Practically, it is much easier to build a large scale application in clojure
There is a vast amount of evidence that shows the opposite. Most of the code bases you use on a daily basis today, such as Google, Amazon, eBay, etc... are built on Java.
What is meant is that Clojure rarely throws its own errors. Instead you'll get the underlying JVM error.
And the underlying JVM errors are normally very noisy and often a little indirect to exactly what you did wrong.
As someone who knew Java very well though, I've never had much trouble connecting the underlying JVM error to my Clojure code. That said, for beginners or people unfamiliar with Java, its an extra burden to have to decipher. And I myself wouldn't mind errors that point me more directly to my Clojure code, instead of towards its implementation.
I'm the most junior member of the Clojure dev team and I have been programming professionally for 27 years (and started programming about 10 years before that). All of us have worked on large scale systems for the majority of our career in a variety of languages, both statically and dynamically typed (Java, C++, C#, Ruby, Lisp, Clojure, etc). I mention this not in defense (you can make your own judgements as you will) but as background.
I don't think any of us would agree with the characterization of Clojure errors in the article.
Error messages or exceptions? Clojure has errors, its actually quite strict in that regard compared to other popular dynamic programming languages. Its types are strict, argument count matters, dependent vars and functions must be declared in advance.
The problem is the errors aren't easy to understand what you did wrong exactly. As in, the message isn't very precise. But the errors are there. Well, except with get I guess, but that seems like it was on purpose, the implementation would have to specifically check for and return nil.
Curiously, the language's features for doing runtime checks as a user--core.spec and pre & post conditions--are both very easy to completely disable for production. I don't know why they're not used in core: probably a combination of the core team's feeling that they don't need them, and not wanting to make the language slower than necessary by default.
They're now in the process of speccing all of core. Hopefully next release will have it.
I do think slow by default is part of it. I've seen a lot of builds deployed to prod without prod optimizations in my experience. That's why I think spec in not instrumented by default. Too worried people will keep it on at all times, and then Clojure would get a bad rep for being slow.
But compared to 'undefined is not a function' it's about 10 times better ;) Glad to see this being improved.
Don't get discouraged by this. Even if you never plan to use Clojure in a project, dive in and take a good long gulp from the functional tea of enlightenment. It will make you a better programmer.
My advice for people complaining about docs: http://clojuredocs.org/ is much much nicer.
The standard docs are very very terse and you'll need to learn the typical abbreviations and idioms.