That’s my only data point with clojure.
I can't explain it any other way. Learning Clojure, or another Lisp, to the point of being comfortable around a codebase, is a matter of weeks for an experienced programmer. It's comparable to the time you need to even figure out how to rewrite an existing mid-sized codebase, and since you have to read it to understand it, why not learn the language along the way? Then suddenly the rewrite isn't needed. But it feels like a concept of learning a new language on the job isn't even considered by people.
Indeed.
I had the frustrating, but ultimately fortuitous, situation of having my first job be one where I was asked to build a solution with woefully under-powered tools. I'm not even talking about the difference between hand tools and power tools - I was essentially asked to use kid toys.
That situation made me very angry, and I was constantly working overtime to make up for the lack of productivity of my tools - even then we were going to miss our deadline.
Finally, I managed to convince management to let me and my team use real tools - nothing super advanced, but well suited for the job. Very quickly we turned around the release, I started working normal hours, and when I left that job shortly after the release my employer found that they didn't need to hire a replacement because the workload was so much less.
This experience taught me that the power of your tools does matter, and that the initial pain of technology transition can easily be outweighed by the benefits of using something better.
For me, the quality I look for in my tools is: does it reward mastery? I'm not (primarily) looking the initial growth curve and how long it takes before I get to my current productivity. Rather, I'm looking at where the ceiling is.
[1] http://danmidwood.com/content/2014/11/21/animated-paredit.ht... (also http://emacsrocks.com/e14.html)
[2] https://shaunlebron.github.io/parinfer/
[3] https://github.com/luxbock/evil-cleverparens (Vim with Emacs via Evil)
It took me a long time to figure out what's going on in that code - I remember realizing how it would have been much simpler to read in a popular statically typed language like C#, types are a lot like compiler verified documentation and in undocumented code they help a lot. To be fair this is not the norm - I've seen many cases where Clojure removed incredible amounts of boilerplate and made reading the code straightforward, and the nicest thing about it was that it made simple things simple.
I haven't worked with Clojure in couple of years, the reason I left it is because I felt it wouldn't scale well in the real world (in terms of developers and teams) but it really helped me "get" LISP and FP so I don't regreat learning it
And furthermore: it's just a library, but this library is adding the concept of go blocks (as seen in Go) to the Clojure language. Are you even able to add a new control structure like go blocks to the C# langauge by including a library?
But your second point is pretty much the reason why I wouldn't use Clojure in a bigger environment.I had the misfortune of working C# on a project where some smartass decided it was cool to use Maybe monad in C# using LINQ (something like https://github.com/louthy/csharp-monad) First it provided no actual value as the code was harder to read, but ignoring that - most of the developers didn't get it and just used it enough to get shit working, it was a hell to debug and maintain.
core.async is public and in core - but what about when your team meber X decides to implement that - and now he sprinkles that shit all over your codebase - people keep using it because they don't want to argue. Clojure really lends it self to that kind of a thing where you do that "smart thing" to solve "that one problem" that ends up being a pointless mess that's hard to maintain and reason about.
You could say that for a lot of other languages too, but you usually have some familiar base to work off of. That's less true with Lisps as they are so syntactically different than more mainstream languages
This has not been my experience with languages like Java, Python, Go, etc. where I feel that I can write good, clean code in a matter of weeks from reading through style guides and understanding language conventions.
- https://github.com/gothinkster/clojurescript-reframe-realworld-example-app for a great webapp example
- All of https://github.com/ztellman work but specifically:
- https://github.com/ztellman/manifold for more advanced Clojure
- https://github.com/reagent-project/reagent for interop with JS and advanced Clojurescript usagehttp://blog.cognitect.com/blog/2017/6/19/improving-on-types-...
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.
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.
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.
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).