"In many ways ClojureScript has and continues to be ahead of the JavaScript mainstream with respect to best practices. Concepts which are only starting to break into the mainstream such as immutability, single atom application state, and agile UI development via robust hot-code reloading are old news to ClojureScript users. "
To my mind, the most compelling thing about Javascript-in-Clojure is working with Om Next. I say this as someone who has recently done some big projects with React.
I think it would be accurate to say that over the last 2 years React has been the Javascript framework that had the biggest impact on how people think about working with Javascript. You look at how much Angular tried to reinvent itself to be a bit more like React.
There are a lot of interesting ideas in React, and I learned a lot while working with it, yet I hate working with it. It is ridiculously verbose. And I hate the way it handles mutations -- it's as if React was trying to be Functional, but then decided to go back and be Object Oriented when it came to mutations.
But I don't mean to go off on a tangent. There are a lot of good ideas in React. Especially GraphQL, which arose as part of the React effort, but which I think will have a long life quite independent of React. React might some day fade away and be forgotten, but GraphQL's critique of the RESTful style is likely to change the way the industry thinks about APIs over the net.
But as I said, React is verbose. I like where Om Next is going -- in terms of immutability and data flow, it has the same basic outlook as React, but Om Next is simpler and it is truer to the Functional paradigm.
If I do any further work with ClojureScript, it will be because of Om Next.
I found almost everything about Om almost egregiously, shamelessly complex. I absolute _love_ the core philosophy, and I think it's a valuable technical effort. But even Om Next is not the big refactoring towards user friendliness that I was hoping for.
It's likely that I'm just too dumb for modern web programming, but I am waiting for someone to distill the (admittedly brilliant) ideas behind Om into something just as powerful but more usable. Reagent is elegant and simple, but you can see how it falls down in the face of larger apps, re-frame I just find a mess. I'm excited about all of this, but I don't think we're really there yet.
Om.next shows it's strength when you're working on a complex application 5k+ lines of code. A core abstractions of om.next (and graphQL) is to express data dependencies of your react classes in a query language (e.g. component X depends on data title, author, created, updated), then the logic around how to fetch data, refresh data, cache data or access cached data, update data, deduplicate data when it's needed in many places, optimistically update client-side data and wait for server-side data to synchronize is all disentangled from the logic on rendering complete data, missing data, data being loaded, reacting to user events, etc. In a smaller code base expressing these data dependencies would just seem tedious (writing graphQL or query expressions), but it pays up big time in larger applications.
I absolutely agree that larger apps are complicated, and that Om has thoughtful solutions to those problems, at the right level of abstraction. But I found it an absolute _pain_ to get going, and at every step it presented me with so many different primitives I felt lost.
If I had to compare it to any other tool, it'd be git. I love git. I use git every day. Do I understand the underlying mental model of git, or the 348 different git commands? Hell no.
Yes, I think those are all valid points/weaknesses. While I don't personal struggle with understanding the internals of git or om.next the learning curve is no joke. The community is aware of this, but it's still alpha software they're working on it.
I actually think of om.next more like a library than a web framework. As a library it has many degrees of freedom in how it can be composed and i imagine the author prioritized flexibility/generality over concreteness, and a lot of work is still left up to the user in filling in the blanks of a full fledge web app, untangled (https://untangled-web.github.io/untangled/) is one project tries to fill in all the missing gaps and expose a framework API, much better documentation, more concrete than om.next.
I'd have to agree. Reagent was able to expose the beauty of the concept to me more clearly than Om. When I approached Om, I found it to be on a completely different level with regards to things to understand and absorb.
Having said that, I'm also pretty sure that Om yields bigger payoffs further down the road as far as I can tell.
What about re-frame is "a mess"? Pretty tiny API surface, solely aimed at forcing the best practices of unidirectional data flow / single source of truth. Have you ever looked at what the same thing entails in JavaScript, ie Redux?
I'd be happy to concede that my real-life experience of re-frame isn't representative, if someone were to show me an example of best practise. My main worry working on real apps has been that it's very hard to layer subscribe/dispatch in a way that allows for modular components, while at the same time separating out component instance state and app state.
Beyond that, the new reg-effect-fx system isn't particularly mature, individual fx aren't extensible, e.g. being able to extend http-fx to cancel or debounce requests.
I don't _hate_ it, I'm building apps in it. I just think there's a lot of overhead above Reagent that I have yet to see pay off, and the idea that it _forces_ best practises is wrong. It's like an MVC system that lets you put (_encourages_ you to, if you read the docs) controller code anywhere.
Knowing where to use `subscribe` (vs pure components) is an art and something that gets easier/clearer as you understand the app better. I'm happy to take responsibility for that aspect of the architecture though; the library's role is to provide the operations and patterns I might want. And if further generalizable patterns evolve, those can be turned into a higher level library.
Very well put. I think this is an extremely insightful summary of the state of ClojureScript for the web today. I feel I'm still waiting for another leap forward.
Thanks for the kind words but I do think ClojureScript is much, much bigger than Om Next :) People are succeeding with many different approaches - Reagent, Rum, Hoplon, etc. all have enthusiastic, active user bases and for good reason. While I'm happy too see Om Next gain traction, in the end I'm much more excited to hear about someone adopting ClojureScript regardless of what other technology choices they might make.
I'm a big fan of Scheme/CL but I find development in ClojureScript to be quite frustrating. If anyone has some solutions I'd LOVE to hear them; while es6 is great I'd still prefer a more lispy language.
My major gripe is with debugging, when I'm writing es6 it's really easy to set a breakpoint and modify a function half way through execution. Or half finish a program and then play around with completions in the REPL to explore a problem. Sometimes I'll be traversing a complex data structure and I'll write an empty loop with just a breakpoint. I can then run the code and work through things in a concrete state and once I've got things working bringing the code back into my codebase.
When I write in any compile-to-js language I always run into the same problem where once I'm in browser I have to go back to vanilla js and any advantage I may have gained is quickly erased by having to deal with transpiled code.
Additionally with libraries like lodash-fp and newer ES features (promises, fat arrows, const and let etc.) the clarity in vanilla JS code is approaching a reasonable level.
Can't help much with your specific problem (I mostly do println debugging) but I've found that figwheel is a great workflow. Following cljs tutorials and whatever is at best painful and it is ridiculous that you need to read a giant webpage like a torah to get started. Use figwheel and add some years to your life: https://github.com/bhauman/lein-figwheel
My biggest complaint remains that without a meaningful concurrency story in js, the benefits of clojure dont make much sense to me.
Its just another hard to debug compiles to js language.
I get you can share some code between server and client... but we've used it in production and now we're getting rid of it; there just wasn't a compelling reason to keep it over es6.
> there just wasn't a compelling reason to keep it over es6
- hiccup syntax / sexps are trees analogous to the HTML you're constantly generating, and first-class supported data structures supported by the language which need no pre-compilation/transformation step, unlike JSX
- syntax is a good 20-30% less verbose than JS (yes, even ES6), especially when the core lib is taken into account (things like partition, zipmap, threading macros)
- one extremely battle-tested dependency resolution story, not named NPM - it just works
- incredible development tooling innovation, best of class for frontend web development
- the sequence abstraction and dozens of functions that operate on it, rather than Object.assign, Array.map, etc
- best-of-breed dead code elimination via Google Closure Compiler, meaning much of third party libraries and development-specific code ends up weighing less / nothing in production builds
- sets and set operations built in, along with tree walking, diffing and string utility libraries, included with the distribution
- no more defensive copying, thanks to immutability everywhere
What Clojure excels at (expressive data structure walking/manipulation) is, in my experience, seldom used in typical frontend programming, often taken care by underlying frameworks like React.
JSX works fine, looks like HTML, and Cljs needs precompilation anyways. Sexps being a pure representation of HTML trees has absolutely zero impact on productivity.
I use Closure Compiler, hot reloading and native Sets with regular ES6. I agree that immutability is a must and JS immutable libraries are just not ergonomic. If at least JS had operator overloading...
I love Clojure, but it just doesn't click for me in the frontend.
>> there just wasn't a compelling reason to keep it over es6 <<
How about these reasons:
- much simpler language
- core.async channels/ no callback hell, easier to read async code
- persistent data structures
- great sequence abstraction
- live code reloading
- vibrant community
- improved tooling
- macros
- transit
Given that it all compiles down to JS before execution, I'm not sure I agree. With Java, you're going to bytecode operations which (imo) allows for a simpler language than compiled to.
>> - core.async channels/ no callback hell, easier to read async code
I agree, callback hell sucks. I'm not sure that core.async is a huge win over promises, but this is an area that JS is quite horrible.
>> - persistent data structures
>> - great sequence abstraction
I'm not sure I see the benefits of clj's sequence abstraction over js. You get destructuring and sequence comprehension in es6, although clj's recur/callstack fix is quite nice.
>> - live code reloading - vibrant community - improved tooling -
I'm pretty sure all of these are better in JS than CLJs.
>> macros - transit
Macros are the old LISP fallback, but seem very counter to any argument of 'simplicity,' I very rarely find need for macros in the front and Clojure's design philosophy generally favors not using them (Data > Functions > Macros)
I do like CLJ and CLJS is interesting, but the only reason I really reach for CLJS is if I am feeling like writing something in om.next.
Clojure does not have the warts JS has. This is a win in itself.
Data structures in clojure are unimaginably better. In js you don't have anything like this. For example you can't tell how operations in js will perform since not even their complexities are documented.
Please elaborate about the better community. AFAIK js libraries have the half-life of months and you can't rely on them not being obsolete after you finish a project.
Macros are a tool which you don't have to use. Everything in the clojure world is optional. But when you need it you have something powerful. Try and emulate macros in js (like AOP in java) and you are in for a rude awakening.
> Given that it all compiles down to JS before execution, I'm not sure I agree.
Most likely, they meant "simple" more in the syntactical sense than in the sense of language abstraction level.
> Macros are the old LISP fallback, but seem very counter to any argument of 'simplicity'
Macros is just transformation over a data structure, albeit a data structure that is interpreted as code later on. It shouldn't add more complexity than other forms of data transformation.
> I very rarely find need for macros in the front…
Neither do I, but I'm glad they exist. Core.async is built with macros, for example. They are part of what makes it possible to have core.async as a library rather than having to build it into the language.
Macros de-risk themselves by going away before compile time. So even if they are complex, they are not complex in a way that causes us to lose sleep at night wondering "does that have a hidden bug that will blow up in the field".
Even if a macro is buggy, if the particular instances (i.e. macro calls) don't step on the bug, then we are okay. The expansion is done, and it is good.
Macros are deterministic and susceptible to regression testing with simple "X translates to Y" assertions.
We don't have to be overly concerned with the time and space performance of macros, either. The code in a macro expander can be structured for clarity and remain that way.
(Even in situations in which running Lisp apps are patched, the macro expansion doesn't have to be done in the application image. The translation unit can be compiled using a separate development image, and loaded as compiled files in the running app.)
>> Most likely, they meant "simple" more in the syntactical sense than in the sense of language abstraction level.
Outside of the Clojure world this would be a reasonable interpretation. However, if you watch 'Simple made easy' by Rich Hickey - that was the definition of simple I was going with (as opposed to easy.)
>> Macros is just transformation over a data structure, albeit a data structure that is interpreted as code later on. It shouldn't add more complexity than other forms of data transformation.
Because they specifically inject before the eval section they _do_ add a lot of complexity. Writing complex macros is a lot of guesswork.
>> Neither do I, but I'm glad they exist. Core.async is built with macros, for example. They are part of what makes it possible to have core.async as a library rather than having to build it into the language.
I'm not sure what the benefit of this vs 'language features' is at this point.
I think macros can still be used for developing simple systems but it comes at a more difficult development cost. Unless you're used to macros, they're weird to write and there aren't many places to learn about macro design patterns (On LISP and LOL are good IMHO). There are examples of major projects that use macros effectively though. Look at core.logic, om.next, core.async, or many other large cljs projects.
If knowledge of how to use macros was better, I think some of the macro FUD would go away. If you're not using macros then you might as well not use a LISP. I'd agree though that using macros improperly leads to all kinds of problems.
To be fair, the only thing if your list which is actually tangible is core async, the rest is just personal opinion (live reloading and vibrant community I question somewhat, just because javascript has both too).
...but really, it boils down to the fact that the people who were writing clojurescript in house wrote terrible spaghetti code that didn't work.
Without a tangible justification for re-writing (again) in clojurescript, the decision was made to rewrite it in es6 and throw all of the clojurescript away.
I think the lesson here is:
Having an excellent language (Clojure) can't save you from writing bad code.
For some reason writing clojurescript resulted in code that was a far lower quality than the clojure code from the same developers. /shrug
I can't explain why that is, but for us, it boiled down to: If you can't play nicely, you can't have nice toys.
Quality of the end product is more important than the tools used to build it.
I've written a decent amount of Clojurescript, and painless debugging is definitely what was missing for me. The callstacks are pretty useless internals, figwheel being outside the browser and away from the visual breakpoints and such are kinda hard to deal with compared to JS.
Not that JS doesn't have debugging pains either, but really the debugging story is the worst part of clojurescript.
yes, but that's only for objects that you have spec(ified). It really would be a lot of work to do for every variable, and you'd quickly hit the point of diminishing returns.
Another thing to consider is how many of your bugs are because of an ill specified object, or because i held null instead of integer.
I'd be surprised if the standard library wasn't spec'd up front, so you'll get that for free at least. Seeing as spec also generates tests and whatnot, it seems like a worthwhile investment for your own functions, if you were intending to write tests anyway.
Also, I think the idea is to write specs for stuff that happens on the edges of the app, such as data received on the wire. If you have that covered, it probably would catch most problems you might encounter, such as receiving a null in a place where you expected a positive integer or such. Since it also destructures the data for you, specs for stuff received on the wire would save you the time you'd otherwise spend taking it apart manually, while simultaneously making sure it's not malformed.
Although I'm not that thoroughly read on spec, so I'm not sure I'm taking everything into consideration.
JavaScript has concurrency (at least in the browser), Web Workers. Messaging isn't always a "nice" way to work, but a layer of abstraction (like a language) can make that better.
...Can't the Clojure people just implement a native code compiler for mobile stuff? I mean, come on, CL/Scheme have been doing that for years, and they weren't constrained by the JVM, forcing a more optimized design.
You can already write Android applications using Clojure[0] without too much trouble. There were a few rough edges when I last dabbled, but that was ~1.5 years ago and I imagine the ecosystem has only gotten better in the meantime.
No, the eco-system hardly changed and it just increases the development effort by forcing developers to move out of the Android Studio workflow and being slower than pure Java.
You could try running ClojureScript over React Native. Nice thing about that is you use React paradigms for UI which IMO makes ClojureScript really shine, and you can use Figwheel etc. and it can be cross-platform.
Clojure was designed from the beginning to be a hosted language. It's its greatest strength (great Java and JS interop) and weakness (no native binaries).
well, it's not just interop, but many core language functions are currently handed off to Java or JS. e.g. string operations and regex engine immediately come to mind.
I love using ClojureScript but one other downside is the lack of ClojureScript jobs. There are thousands of JS jobs right now but how many cljs jobs are hiring? Tens?
That sucks because cljs is great but it's probably the biggest incentive for sticking with JS.
market size is relevant, are the people doing the complaining willing to pay $$$ for cljs devs? I was on the market last winter and could only find $$ (baby startups) which was a 50% cut in rate to work in cljs. BTW I am about to start looking again, contact info in profile
> ClojureScript offers features like dead code elimination
In Clojurescript, it's my opinion the Closure compiler is of dubious value. It adds yet another level of indirection between the source and the target. And isn't it the dead code elimination that makes run time macros impossible in CLJS? They are possible in Clojure proper.
CLJS has been self hosted for about a year now. It's cool, but its really not needed. Google Closure on the other hand is amazing. I've seen speed improvements of 10x when switching to advanced compilation mode. Its not just dead code elimination, its also partial evaluation and inlining.
"In many ways ClojureScript has and continues to be ahead of the JavaScript mainstream with respect to best practices. Concepts which are only starting to break into the mainstream such as immutability, single atom application state, and agile UI development via robust hot-code reloading are old news to ClojureScript users. "
To my mind, the most compelling thing about Javascript-in-Clojure is working with Om Next. I say this as someone who has recently done some big projects with React.
I think it would be accurate to say that over the last 2 years React has been the Javascript framework that had the biggest impact on how people think about working with Javascript. You look at how much Angular tried to reinvent itself to be a bit more like React.
There are a lot of interesting ideas in React, and I learned a lot while working with it, yet I hate working with it. It is ridiculously verbose. And I hate the way it handles mutations -- it's as if React was trying to be Functional, but then decided to go back and be Object Oriented when it came to mutations.
But I don't mean to go off on a tangent. There are a lot of good ideas in React. Especially GraphQL, which arose as part of the React effort, but which I think will have a long life quite independent of React. React might some day fade away and be forgotten, but GraphQL's critique of the RESTful style is likely to change the way the industry thinks about APIs over the net.
But as I said, React is verbose. I like where Om Next is going -- in terms of immutability and data flow, it has the same basic outlook as React, but Om Next is simpler and it is truer to the Functional paradigm.
If I do any further work with ClojureScript, it will be because of Om Next.
https://github.com/omcljs/om/wiki/Quick-Start-(om.next)