9 ms·
Clojure Error Messages Are Accidental
- gkya 8y agoThat 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.
- jonstaab 8y agoI 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.
- gkya 8y ago> 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.
- didibus 8y agoYes, 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.
- james-mcelwain 8y agoAs 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.
- gleenn 8y agoI 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.
- gkya 8y agoI 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.
- hota_mazi 8y ago> 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.
- abc_lisper 8y agoThat’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.
- hota_mazi 8y ago> That’s not true at all. I have built pretty big applications in clojure, and it’s been nothing but joy to use. I'm happy for you but your experience doesn't invalidate my claim. > clojure works very well for people with a little bit of experience and those who think things through before they write code. All languages work well for people who think before they write code. Clojure makes that harder than necessary, though, being dynamically typed.
- abc_lisper 8y agoHmm. I can’t disprove theoretically what you said. Practically, it is much easier to build a large scale application in clojure, than say, java.
- d4mi3n 8y ago
- beders 8y agoIt'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. My advice for people complaining about docs: http://clojuredocs.org/ 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.
- kimi 8y agoBTW: for libraries, there is kind-of standard docs being developed: https://cljdoc.xyz/ https://cljdoc.xyz/ - see e.g. https://cljdoc.xyz/d/cli-matic/cli-matic/0.1.13/doc/readme https://cljdoc.xyz/d/cli-matic/cli-matic/0.1.13/doc/readme
- beders 8y agoTo help with Clojure/ClojureScript adoption, I would love to see this, clojure-docs and the official documentation in one place. People give up easily if they can't find decent getting started and docs. It adds to that initial hurdle. Any chance the authors of these initiatives can get together?
- didibus 8y agoI think most of the reason for not having all these runtime checks in core is performance. Though I guess if there was an easy way to turn them off for production it would maybe be okay.
- aperiodic 8y agoCuriously, 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.
- didibus 8y agoMaybe pre/post was added later? 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.
- kuwze 8y agoYeah Clojure's error messages pushed me to Common Lisp (although I am still quite a neophyte in both). I haven't tried it, but maybe Sayid[0] would be of help to Clojurians? [0]: http://bpiel.github.io/sayid/ http://bpiel.github.io/sayid/
- emidln 8y agosayid is awesome. Spacemacs integrates it by default and it's a nice step up from tools.trace.
- alkonaut 8y ago> 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.
- jgalt212 8y agoVery good talk by Bozhidar Batzov at PartialConf 2017 that covers this point as well. Clojure, The Bad Parts https://www.youtube.com/watch?v=1YCkOo5Y4Oo https://www.youtube.com/watch?v=1YCkOo5Y4Oo
- al2o3cr 8y ago> 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?
- puredanger 8y agoIt'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.
- ajss 8y agoSpecs are not scoped. There's a global registry. You may end up chasing other people's bugs.
- lbj 8y agoThe 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. Example: core> (defn mydiv [n d] {:pre [(pos? d)]} (/ n d)) #'mydiv core> (mydiv 10 2) 5 core> (mydiv 10 0) AssertionError Assert failed: (pos? d) core/mydiv As opposed to the standard core> (/ 10 0) ArithmeticException Divide by zero clojure.lang.Numbers.divide (Numbers.java:158) Chasing down Numbers.java will take time, but with a pre-condition you get the exact name of the function and the test which failed.
- puredanger 8y agoBefore going down this path, it's important to understand the substantial performance implications.
- lbj 8y agoIndeed
- jonstaab 8y agoChecks are turned off by default. Are there performance implications even when they're turned off? Reference: https://clojure.org/guides/spec https://clojure.org/guides/spec
- ambulancechaser 8y agospec is wholly independent of the pre/post conditions.
- aperiodic 8y agoIn 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.
- kimi 8y agoAlex Miller wrote a few days ago on this very topic: https://clojureverse.org/t/improving-error-messages-in-clojure-as-a-library/1765/27 https://clojureverse.org/t/improving-error-messages-in-cloju...
- james-mcelwain 8y agoClojure 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".
- kimi 8y agoI 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.