4 ms·
I don't think it's hard to read, I think it's extremely difficult to analyze and almost impossible to debug. Lazyness is one of those "cool" features, IMO, tha
by PopsiclePete 8y ago
I don't think it's hard to read, I think it's extremely difficult to analyze and almost impossible to debug.
Lazyness is one of those "cool" features, IMO, that have more cons than pros. Yes, certain algorithms look more "elegant", but it's also harder to reason about/debug.
Most of the time, you can ignore the fact that this awesome language runs on the JVM, until you see a barely-comprehensible stack trace containing nothing but `org.clojure.Iseq` something-or-other and no user code in sight.
Looking at how a "real" LISP displays errors and allows you to debug it and comparing it to Clojure makes me feel really sad inside.
Sadly, these are the reasons I tend to favor Go for work-related projects. It's less "fun" to write than a LISP, but makes pinpointing and fixing errors a breeze in comparison. And when my job is on the line, that's the killer language feature for me.
- nybble41 8y agoI don't think laziness is really that much harder to reason about, unless you're trying to fit it into a mental model based on eager evaluation. Evaluation occurs when the result is needed; that's so hard about that? And it's not really about making algorithms look "elegant" but rather about composiblity and reuse: it's much easier to add eager evaluation to non-strict interfaces than it is to get non-strict evaluation from strict interfaces. In the former case you're adding additional behavior, which can often be accomplished with a wrapper; in the latter case you're faced with removing something baked in to the API. Eager evaluation is merely a performance optimization, one which assumes that the data being evaluated will actually be used. When the data is not used it ceases to be an optimization and can even result in incorrect behavior (unnecessary exceptions / non-termination). Either way, making eager evaluation the default is a form of premature optimization.
- PopsiclePete 8y agoWhat makes lazyness difficult for me isn't the mental model, it's the fact that when you get an error, the error is far from its call-site, and the truly helpful information that I could've had in the stack trace is gone. I'm looking at the error - but which lazy operation in the sequence caused it? Where? When? I by no means claim to be a Clojure guru, and I'm sure people who have more experience can just look at it and know, but for me, the time lost to pinpoint that error is too much. And when I have something in production, I need to be able to pinpoint the error in minutes and deploy the fix quickly. >Either way, making eager evaluation the default is a form of premature optimization. I don't see how that's true - it's the default way CPU's operate on the silicon level. Millions upon millions of lines of non-lazy code have been written and put into production and they perform real, valuable work. I'm not against lazy evaluation or against Clojure - all I'm saying is - it's hard for me to reason about it when things go wrong. If they don't go wrong (almost never for me) - it truly is a thing of beauty.
- nybble41 8y ago> when you get an error, the error is far from its call-site, and the truly helpful information that I could've had in the stack trace is gone I'll grant you that one. Languages with non-strict evaluation (or tail calls, or inlining, or extensive use of macros) rarely have useful stack traces. You can see what was being evaluated but not necessarily how it got there. The ability to leverage the stack as a debugging aid is a feature of certain runtime conventions more applicable to imperative programming paradigms than to functional ones. In modern Haskell there is a way for functions to receive extra information about their call site as an implicit parameter, which helps with error reporting. I'm not sure what tools Clojure might offer along these lines. I will point out that tracing the source of incorrect or corrupted data is no less of a problem for programs written in strict, imperative languages. Functional programming adds to this by emphasising functions as a form of data, and non-strict evaluation carries that still further by treating all data as closures. On the other hand, strong static typing limits the kinds of runtime errors which lay hidden within those closures to a manageable set (assuming a pure functional language like Haskell). >> Either way, making eager evaluation the default is a form of premature optimization. > I don't see how that's true - it's the default way CPU's operate on the silicon level. That's not really true, given the existence of speculative and out-of-order execution. In any case, how the CPU operates at the silicon level has very little to do with whether a high-level language optimization is premature. > Millions upon millions of lines of non-lazy code have been written and put into production and they perform real, valuable work. I'm not disputing that, but it's beside the point. Code which has been prematurely optimized can still "perform real, valuable work". It just may take a bit longer to run or require more resources to develop (for example by being harder to reuse, requiring more code to be written to accomplish the same result).