Good points. I started with ClojureScript earlier this year and I agree with your article. But for me ClojureScript is still much better option than staying in JavaScript land. Performance will be solved over time with faster machines (I've purchased high-end iMac recently, so I don't feel such pain :-). Workflows for incremental compilation like Figwheel also help a lot. And I think there is a great promise that tooling will evolve quickly.
We have a good sized clojurescript SPA in production, and it has been a real joy to work with. We are using Reagent, which when coupled with FigWheel[0] goes a long way toward mitigating slow startup, as well as "tooling" issues in general. The live reloading is a fantastic experience.
Sourcemaps have been a big help for debugging in general, though we have had several instances of bugs taking a bit of effort to get to the bottom of because of some deep javascript stacktraces -- for the most part the java side of the compiler hasn't been a big deal. Many of these have been type related bugs, so we're looking into adding some schemas[1] going forward where it seems appropriate. Despite those issues, it has overall been a huge productivity boost. We've also witnessed this story only improving with time.
The community truly is fantastic. Lots of very smart, friendly folks doing interesting things.
Let me just preface this by saying that I've written Clojure in anger for the past 5 years and I agree with all of the author's points.
I love writing Clojure(Script) code, but I hate reading it. It fits perfectly with the way I think (when I'm writing code), but horribly with the way I try to understand code I wrote months ago.
With that said, the emergence of Elm as a viable alternative to front end functional programming has pushed all my new projects away from ClojureScript. Now, for the author's CS complaints, Elm has:
* Fast compile times, startup time indistinguishable from JS.
I was going to have a section on Elm, and then the post got too long :) Clojure(script) feels much more mature, and general-purpose. Elm is client-only, not nearly as many libraries, etc.
Would clojure's readability be helped by having a non-lisp pretty-printer? I've been thinking about this a bit - s-exps are great for writing, but fails to take advantage of our advanced visual pattern matching when reading it.
Cool, do write about your Elm experiences, I'd be interested to read!
Unfortunately, yes, Elm is restricted to the browser. But this might change in the future. And although it is somewhat tedious to write ports, IME Elm communicates better with JS than ClojureScript. You can just drop any old JS library and write a port for it (it will be async, though).
Sexps alone don't bother me at all. What bothers me (and I only came to this conclusion recently, after reading this[0]) is the interplay between functional code and the absence of type annotations. Sometimes it feels like Lisp just wants you to write clever code, all the time. Clever Python code looks bad. Clever Lisp code looks pretty nice.
It's interesting to hear from someone else who also finds that Clojure/Lisp code can be difficult to read. I worked with Clojure professionally for 2 years and had started to think that I was the odd one out since everyone else I worked with was just gushing over the readability of the code. At times it could be easy to read, but very often I found it hard to follow and would have to spend more time then others deconstructing what was happening.
Which is not to say that I didn't like working with Clojure. I very much did. But it always seemed to be such a chore to read others code or code I hadn't seen in a few months or more. I remember on occasion showing some snippets of code to a co-worker and commenting that I thought there was too much "clever" code in there, that it was too dense and hard to follow. The co-worker didn't agree with me though, and I guess I just resigned myself to the idea that I was the odd one out here or something.
Ultimately, I think I ended up coming to the same conclusion as you. My way of thinking (and reading code apparently) just clicks so very much better with typed languages.
(I hope no one reads my post and treats this as some kind of attack on Clojure/Lisp and s-exps. It's not. I'm just noting that the style just doesn't jive with certain types of people. And I gave it plenty of time (2 years as noted above) before coming to that realization.)
Yup, sometimes it's easier for me to understand somebody else's OCaml/F#/Elm code than Clojure code written by myself. I still love working with Clojure, though. Go figure...
what aspects of the text made the code hard to read for you ? Was it the parens ? If LISP was displayed in the editor without parens (but still had them behind-the-scenes), so that the code was formatted much like Python, then would it be acceptable ?
I just listened to this podcast[0], and there's a really great discussion in there about when it's nice to have types, and when it's more trouble than it's really worth.
That's why javascript's flow[1] appeals to me so much, and I'm really interested in core.typed.
Elm's reactive model is best suited to UI's in my opinion. There's no good way to limit the data coming at you in a push-only model, so if you can't keep up with the incoming data flow bad things happen. On the UI this case usually isnt encountered, but on the server one needs mechanisms to deal with this
Imagine you subscribe to lots of realtime data sources (e.g. via websocket), that deliver lots of updates to your UI. Then you have a race condition whether you can draw the updates faster than you receive them.
In an eventloop environment like Javascript it could be self-regulating (http/ws stack wouldn't receive new frames until you return from drawing. But it might also be not (because a C++ background thread in your browser receives the messages and queues them until they can be pushed to JS) - then you might get out of memory. But I totally agree that it's an exception for UIs to deal with such scenarios, but it should be normal an a server.
I went through the same debate with myself earlier this year, and finally decided to go with Elm. But, 100% of my work is client-side web, and has been for 15 years, so having a tool specialized in that field makes sense for me.
Brandon Bloom's fipp[1] works fine but the Chrome Canary dev tools have support for object formatters. I get reasonable results on .170 just doing a (js/console.log a-map).
>I love writing Clojure(Script) code, but I hate reading it. It fits perfectly with the way I think (when I'm writing code), but horribly with the way I try to understand code I wrote months ago.
I've used Clojure for 2 years - I feel the same way. It's a wonderfull language that exposed me to great ideas and showed me that simple things can be simple but boy does it get nasty when things don't fit the provided primitives and actually start getting complex - the language has no tools to deal with complexity it can't simplify.
Also a - for CLJS from me is that they haven't abstracted out the (do ...) macro from channels (ironic with all those talks harping on how simplicity is about disentangling things). The biggest pain in JS/DOM ecosystem is shitty semantics, poor standard library and callback stacks. ClojureScript fixes first two really well and does a half-assed job at fixing the last - channels are too much overhead for such task (from both performance and conceptual POV - putting a queue in front of every callback is silly in a single threaded model).
I feel like Elm is a language designed to explore an idea not to be practical, this is why I haven't tried using it for anything serious.
TypeScript has potential in theory to bring ES6 + type safety to avoid semantic pitfalls - but in practice it's supposedly biggest strength of leveraging the existing JS ecosystem is also it's greatest weakness in that you're still left there with shitty JS ecosystem with zero consistency, most libraries deal with JS pitfalls in their own unique way which doesn't mesh well with other approaches or with TS, fragmented build, packing, dependency management, module loading tools - all trying to solve the same problem in different and incompatible ways - it's just a pile of junk that wastes more time trying to reuse stuff than it takes to build anything - which is probably why most of it is in the state it is - and very few things are actually maintained so the argument that reinventing something means you need to maintain it yourself is not really convincing.
Dart has a built in package manager, has had tools for dealing with async code for a while now (both async/await and streams), it's familiar to the Java/C# devs but it's surprisingly concise (quite a few nice touches like constructor property-parameter mapping or => getter syntax for tedious stuff), the standard library is great and avoids all the JS pitfalls.
The downside is that you still need JS interop because there's no way around it (for eg. emscripten) - and talking to JS from Dart is still nasty - much more involved than CLJS or TS.
Having used all 3 mentioned for non trivial work I like Dart the most and the new JS interop could make it even better.
My impression of Elm was that it's trying to create a platform for real-time interaction (e.g. games, UIs, etc.) that happens to target the browser. There's a constant undercurrent of practicality in the community: for example, new language features are regarded suspiciously, and modifications to existing behaviour are only made when they reduce the burden of complexity to the programmer. Obviously it's a language that has yet to hit 1.0 so there are occasionally large swings in its design, but apparently they've become more stable as of late (I'm only picked it up ~1 month ago).
The only unpractical part I'd noticed was its perspective on reusing components, as seen in this overview under 'Nesting' [1]. That bit of explicit routing aside, may I ask what dissuaded you from using the language seriously?
I think this article is very fair and, today, you know you are ready for cljs when you no longer value all these things over the pure elegance and simplicity and power of great clojure code, which JavaScript doesn't even approach even with React and es6. The type of person who chooses to write cljs is someone who values code elegance and abstraction power above all else at any cost, and, indeed, pays that cost every day when they try to debug core.async, or try to get a newbie's machine up and running (you end up apologizing 10 times about the fragile tooling and then a hundred more time for the horrible stack traces... I hosted clojure katas for beginners at a meetup last night so this issue is particularly fresh) I could go on. But in the end it is so worth it to me because the raw power of clojure just hits all the high notes in my brain in ways no other language has.
ClojureScript has some very powerful tools available (figwheel, om/next), and the community is working on resolving many of the issues mentioned in the article. Derek Slager's presentation "ClosureScript for Skeptics" is really good and covers many of these topics (https://www.youtube.com/watch?v=gsffg5xxFQI).
Myself, I prefer ScalaJS because I already know Scala, the tooling is excellent, it is fast, and the resulting JS is of reasonable size.
Fortunately, it is not an either/or decision. Both compile to JS and can interoperate without too much effort.
I was roughly where you were at the beginning of the year. I've been in and out of the Clojure community for a couple years and decided to take a job writing clojurescript full time. App is around 15k loc and was initially in Om and has now been migrated to (my fork of) re-frame.
Positives: <3 re-frame. It's the first architecture in 15 years of frontend dev that I've been happy with and I've tried pretty much everything. cljs is a better language than es6. I like devcards.
Negatives: I've put two work weeks into dealing with tooling problems. I have a 5 minute build. This mostly doesn't matter because incremental builds but every so often (e.g. Tuesday) I hit a problem and have to bisect with a fresh build at each step.
While I'm happy with the language, I'm less happy than I expected to be. I'm in the process of porting devcards and re-frame patterns to (functional) Typescript for my personal projects. The language is a significant barrier to contribution from a larger community and I get a bigger lift from optional types+autocomplete than I do from protocols and macros.
Edit: Is anybody interested in a set of Sublime-ish keybindings for Cursive? I made the set for my coworkers and for the things I can't do via IdeaVim and they've been working fairly well for the past couple weeks.
5 minutes for a 15KLOC project seems pretty strange to me unless you're building on a machine with very limited CPU/RAM. Make sure you're giving the JVM enough RAM. Also the next release of ClojureScript supports parallel builds, I've consistently seen users report 30%-300% faster builds (depends on # of cores, dependency graph).
This is on a 2012 MBA for a prod build. I see times of 140s or 180s on a fresh dev build. Times are lower on faster machines but still in the multiple-minute range. I'll tweak the build JVM settings tomorrow and see if that improves things.
Just as another data point, our project is 16k lines (including tests) and I just timed it at 2.7 minutes to run "lein cljsbuild once" with no difference when setting those jvm-opts. This is on a quad core 2.8ghz i7 MBP.
:compiler-stats has been a thing for a while now. This would give more information - like how much time in ClojureScript vs. advanced compilation. Also the compiler version matters - 1.7.170 is what you want to use. Otherwise you may need :recompile-dependents false.
I see you're using IdeaVim. For very long time I was hesitant to move from WebStorm to Emacs. Until I discovered Spacemacs. Try it and I promise you - you'll be very happy.
There are a few weird conflations in this article that I think could use a bit of cleaning up. Why is om/next being compared to Falcor and Relay? Om in general is a very React-oriented story; why not compare how working with the two feels like?
It also feels like the author is primarily interested in client-side ClojureScript, in which case the Leiningen start-up time should really only be a problem once per...day? week? I re-start my Clojure(Script) REPL process only when I change underlying dependencies, which isn't frequent enough for it to be a major issue.
The biggest issue for me that I agree with is the immature tooling thing, but that's been getting a lot better since I also blogged about being frustrated with ClojureScript (relevant link: blog.venanti.us/clojurescript-blues/). Vim and the CLJS REPL now work well together, and I've been really impressed by the direction and velocity of Figwheel as a project.
1) I see om/next attacking the same problems as falcor and relay, and david nolen makes reference to both of them (as sources of inspiration) in the talks he's given.
2) it may only hurt once a day/week once you have an established flow going, but when trying to figure things out & start things up, you restart a lot more than that. hence the barrier to entry
1. Going to look at the recent state of Om/next docs, I see that you're right. I don't think the earlier iterations of the project had quite the same focus but it's clearly been made a higher priority.
2. I think the barrier to starting a new project is unreasonably high, yes, and requires a highly inconvenient amount of configuration familiarity. Unfortunately the gap between just running the ClojureScript compiler on a single namespace file and actually configuring a viable project and build pipeline in Leiningen is quite large and really requires a concerted effort to overcome (or, to have someone nearby who's willing and able to show you what you're doing wrong).
om.next lifts some great ideas out of Relay and Falcor. All three libraries share the assumption that components should describe the data they want, in the shape that they want it in. A querying layer is then responsible for for maintaining a local cache of the data, fetching it from the server when it's not available in the cache, and updating the components that are subscribed that data when it changes. This leads to some really impressive performance optimizations (only parts of the page that depend on new data need to be updated) and some very impressive server-side caching characteristics (om.next can use HTTP for caching because the data is normalized).
I still view the language as a one man hackers sorta thing. I hate setting up a new project with the options I want, not to mention I always seem to be the guy submitting corrections for windows machines (come on ppl, really).
While coding it is awesome, debugging and maint is hell. I'm off to Typescript land after a great two year trip with ClojureScript. It reminded of everything I loved and hated about a dynamic language.
Yes, I am sticking with F# and Typescript. I still love PicoLisp and Shen for my own projects, and as Lisps. Shen runs on everything, and is very versatile, but has minimal libraries and community. I find both very readable months later, but then again I also love the J Programming language! Clojure/Clojurescript are too convoluted with their purposeful tie-ins to the JVM/JavaScript. Cleaner Lisps, Common Lisp, are easier for me to deal with later on when reviewing code I've written, although, I have not written anything medium to large.
I'm really surprised that there isn't much better debugging support for languages that use JS as a compiler target. Everyone should be able to simply debug in their source language. REPL should simply return JS objects and values, but allow people to type in expressions in whatever language they have. (Might require the compiler to be written in JS or compiled to JS, then also add an API.)
I've used clojurescript commercially for a couple of years. It has been a good experience.
More than anything it has completely changed my world view (and I'm a very experienced, older programmer who has seen a bit). I feel very lucky to have had this opportunity.
IMO, some of the issues you raise are genuine, some not.
For example, the JVM startup time issue is never, ever a problem. If it is, you aren't using the tools right.
I use Cursive and figwheel etc, so I find the tooling generally fine and in many ways excellent. The trajectory in this area is good.
Debugging initially can be rough. Knowing about things like cljs-devtools can dramatically improve things. But, yeah, valid point.
And, yes, poor error messages can definitely be a problem initially -- although this has improved over the last year.
To get going, I'd recommend you use this set of technologies:
In javascript, everything is mutable, but w/ new es7
spread/rest syntax it is increasingly easy to just do
immutable anyway. E.g. instead of myobj.x = newx you can
do myobj = {...myobj, x: newx} and everything is immutable
& persistant!
I think most of your issues really come down to not embracing or understanding a proper clojure(script) workflow and/or not really understanding the tooling that currently exists.
I double checked the date of this article to make sure this wasn't written 6 months ago and posted now.
You should check out the clojurians slack channel, there you can probably get good answers for 85% of the things that are holding you back.
Good luck with it, using clojure/script + cursive clojure + intellij has been the best development experience I've had in 18 years of professional coding! It's 110% worth the investment.
i just dove into clj & cljs w/o such a cost-benefit analysis b/c after writing some scheme and elisp, it seemed like it would be fun, maybe more fun than js, and for the most part, it has been.
good article overall. a point that was a little irksome was
> chrome devtools is way ahead of your cli repl (and even your editor-integrated repl) in a bunch of important ways
(emphasis added). b/c while i'm not saying the devtools is not ahead of figwheel+cider in some sense, enumerating specific want-to-haves would be more informative.
my main nice-to-have (and maybe there's something out there???) would be a way to set breakpoints for dropping into a repl. b/c of the scarcity of mutable state, it hasn't been such a problem, but there have been one of two times where i could've saved a few minutes by throwing a `(debugger)` or something in.
I'm the author of cljs-devtools. I think for development it is perfectly fine to use Chrome Canary. Also I would recommend you to check out this article: https://github.com/binaryage/cljs-devtools/wiki/Figwheel-REP....
My public projects so far:
https://github.com/binaryage/cljs-devtools
https://github.com/binaryage/chromex
https://github.com/darwin/plastic
https://github.com/darwin/faceboard
https://github.com/binaryage/pure-frame