(defn parse-image-links [pages]
(-> (map #(js/Promise. (parse-page %)) pages)
(js/Promise.all)
(.then #(->> % (map :Images) (apply concat) (map parse-image-url) set save-links))
(.catch js/console.error)))
https://gist.github.com/yogthos/d9d2324016f62d151c9843bdac3c...Here's another example working with async Ajax calls from re-frame https://github.com/ClojureTO/JS-Workshop/blob/re-frame/src/r...
Furthermore, we find that we rarely need things from Js ecosystem. Here's [1] a talk I recently copresented on an app my team built. I has around 50kloc ClojureScript, and we barely had to use anything in Js ecosystem for it.
In general, that's not how the development of Clojure has happened in general. Clojure is a leader when it comes to realization of new ideas in software engineering, sitting at the frontlines. Probably due to it's lisp nature mixed with it's pragmatic leader.
> core.async which predates ES6 and so does not take advantage of the new async/yield features of ES6
In general, in a Clojure(script) codebase, you don't have the same problems as in a JavaScript codebase. There is no need to change the implementation of a abstract thing like core.async (CSP in this case) just because there is a new way of doing it.
> Clojurescript does not seem to be interested in adapting to the current JS standard
This is true, and I think Clojure is trying to make a point of remaining like that. The JS Standard is evolving after what people want the most, not what it's designer think is the best. I guess this is the essence of the differences between Clojure and JS, one is leading one person, the other what people demand to have.
Why does it have to be in the core or even in core.async? There are libraries for that, e.g: https://github.com/funcool/promesa
(defn parse-image-links [pages]
(-> (map #(js/Promise. (parse-page %)) pages)
(js/Promise.all)
(.then #(->> % (map :Images) (apply concat) (map parse-image-url) set save-links))
(.catch js/console.error)))
And you have gone full circle, bact to writing Javascript but with parentheses.In other cases you can benefit from all the features of ClojureScript.
That's pretty magical in my experience.
Clojurescript offers much more than just a concise syntax.
Immutability by default alone has huge benefits.
There's "true" REPL. No, Javascript does not have a REPL, at best it has an "interactive environment". REPL is much more than a thing where you type a command into it and it spits out some statements.
There's consistency. In Javascript, because you don't have a standard library - there's none. Instead, you have Lodash, Rambda, Immutable.js, folktale, crocks, fantasyland, etc. etc. In Clojurescript for example, if you need to get the size of a set, vector, list, hash-map - there's a single function. In JS you have to know - sometimes it's .length, sometimes it is .size and there are many examples like that.
In Javascript, there's a precedence table with over 20 something items in it. Clojurescript doesn't even need one. There are like seven different things that can be "falsy" in JS, in Clojurescript only two - false and nil. These may seem to be like small things, but they add up.
Add to that, an enormous churn from libraries and tooling, Webpack alone (even though it is really good) may frustrate you for hours and days.
And like front-end wasn't enough, Javascript today is in the back-end too and although it is the same language, there are many inconsistencies you have to deal with, while Clojure and Clojurescript feel very much the same language, even though they are hosted on completely different platforms. You can actually can share code, the promise that has never gotten fulfilled for me with Nodejs.
And by the way, Javascript has way more parentheses and also commas, semicolons and curly braces. In Clojurescript parentheses give you structure and consistency, you stop noticing them after a while. In Javascript there's no consistency - arrow function with multiple params requires parens, with a single param - parens can be omitted, but with no params - parens are still required. Before Prettier you'd never even sure how to structure js code. Prettier simplifies things, but it still has so many options that vary from team to team, from a codebase to codebase. Single misplaced comma can make you feel like smashing your laptop. Write enough JSX and you get to a point where excitement from JSX turns into incurable frustration. In Clojurescript you don't even think about those problems.
So please stop treating Clojurescript developers like some kind of Lisp fanatics who hate Javascript for some made-up, bullshit reasons. We don't hate it. Most of us have consciously made our choice because it brings value and allows us to build things without mental overhead.
- although figwheel is an awesome build tool, shadow-cljs with its near seamless npm integration is imo the most powerful Cljs build tool atm [1].
- cljs-bean[2], recently released by Mike Fikes is a really cool tool: it makes working with POJO really easy without too much performance penalty
- the new react hook API is imo a game changer for CLJS frontend frameworks: the decision to overload native function for componenents instead of using ES6 classes makes working with react componenent much more easier, imo this new API suits CLJS perfectly: hooks are just js functions that are called in the same order at each render pass.
- clojure.spec and clojure.spec.gen in a REPL are really powerful tools for frontend development that are unavailable in the wide JS world.
All in all I am actually much more optimist than a few months back about the future of CLJS frontend dev, those are still relatively new tools, clojure.spec is still a work in progress (although I would recommend to take a look at the metosin spec-tools which are really nice).
Edit: formatting
[1] https://code.thheller.com/blog/shadow-cljs/2018/06/15/why-no...
In re-frame, programming with channels is pretty rare. We have it maybe 2 places in the whole app, and one of those places is an internal load-testing type screen. If core.async were gone tomorrow, I would rewrite maybe 30 lines of code, and not really care - it just isn't important at all.
I think in Clojure in general, rolling your own stuff is more common, so even if I agreed that interop was difficult, it just isn't that common. A lot of the time, integrating complicated libraries is not worth it if you can just make your own very easily.
Also, are you using core.spec or transducers regularly? Do you find them understandable? How has the clojure community responded to react hooks? There’s so much that I’m curious about regarding present-day large clojure codebases, sorry.
Also, I think the whole "callbacks vs async/await (promises)" is only relevant in JS, where callback-hell is easier to end up with. Mostly because clojure as a language (lisps really [I think?]) prefers very small, composable functions. So by just following that, you avoid callback-hell.
I'm also working on a clojurescript codebase in my daily work. Using clojure.spec to specify behaviour and simulate data for my reagent components. Transducers I have not have the need to use yet.
Regarding React Hooks, when using reagent together with re-frame, you basically end up with all components being state-less, and having the react hooks would just couple functionality to one component, so it's not really needed.
1) Based on the current app-state, make a request
2) Update the app-state with a marker that this request is pending
3) When the response comes back, update the app-state with the response
Re-frame doesn't ship with a handler that does all those 3 things, but it is pretty trivial to make your own, and that covers almost all use cases that we have. In some cases, when the request is just fetching data that is read-only, we do it via a subscription that is dereferenced in the view. The subscription can be made to handle everything, so it is even easier.
> Also, are you using core.spec or transducers regularly?
No we haven't seen a big need for either. For transducers, the idea is cool, but it hasn't changed how most people in Clojure write their code day to day. In most cases, normal (->> xs (map ..) (filter ..)) type approaches have acceptable performance, so bothering with something that will just make it harder to understand/modify does not seem worth it.
For spec, some of the concepts are good (like orienting around namespaced keywords, but we haven't been able to use it, since it isn't data-driven, and our app is very dynamic. In that sense, JSON Schema is better (which we do use in some places). We plan to give spec another look once they release the second version, which is meant to fix that.
For react-hooks, I haven't seen too much interest from the ClojureScript community. It just doesn't solve a real problem we have - we've already made good abstractions to work around the common problems people have with React.
https://funcool.github.io/promesa/latest/#promise-compositio...
That said, our code works and I'm not about to rewrite it just because some new libraries came out.
As for spec and transducers, I do use spec quite heavily, though now that clojure.spec.alpha2 is on the horizon I'm holding off a bit - it will be really nice to have data-based specs instead of macro based ones.
For transducers I've only used them when I've found a performance bottleneck in a data transformation pipeline, which for me means only on toy algorithm problems and not in production code. But that says more about my line of work than their usefulness, I think. I didn't find them too hard to grasp.
There's some cool work being done with hooks that are mentioned up thread, and I think there a fork of reagent that is experimenting with them. This is actually a really exciting time in cljs-land, there are a lot of interesting projects exploring what we can do to build on ideas from React (hooks) and Svelte. The Svelte approach is especially interesting to me, since it's basically just a compiler, which we can do with a macro. And with shadow-cljs the build tools are better than they've ever been.
[0]https://funcool.github.io/promesa/latest/ [1]https://github.com/ztellman/manifold
In Rich Hickey's famous / often-referenced "Simple Made Easy" talk he lists what he considers simple vs complex toolkits. [1]
There's a clear preference for declarative constructs vs imperative constructs. Async/await is attempting to take async behavior and make it "appear" synchronous. I suspect Hickey would slot this into the complexity category. At least I tend to agree. I think channels are both explicit about what is going on and also maximally decoupled/composable.
It seems that your preference leans the other way. I just point this^ out b/c it is possibly or likely an explicit choice and not a miss by the Clojurescript team (Nolan?).
I personally find the freedom given by Clojure/script ultimately simple and powerful.
Curious for others' thoughts on this.
[1] https://www.google.com/search?q=%22simplicity+toolkit%22+hic...
Yes, you can do more with channels. But you can also use them to implement simple "blocking" calls that return one single value (This is used quite a lot in Go, where doing basic blocking IO on a file involves underlying channels between a facade layer and IO scheduler thread).
Presenting an illusion of sequential code is precisely what core.async does. It's magic and hides complexity (which can leak back and bite you)
It would be interesting to see a fully-baked out analysis of async/await vs go and Promises vs channels.
I do think Clojure still has the win on simplicity if for the main reason that core.async is just a library [2] and Javascript implements async/await via syntax with the actual implementation at a subterranean level.
Clojure's version (core.async) can be macroexpanded/unwound -- i.e., the 'magic' you speak of is not magic at all and can be examined and implemented directly w/in ClojureScript itself.
From the channels vs Promise perspective I think there is more to argue. Channels are quite a simple concept when weighed against the Promise spec [3]; of course this is arguable and arguments welcome.
Also worht considering David Nolen's takes, one of them here: [4]
[1] https://clojure.github.io/core.async/
[2] https://clojure.org/news/2013/06/28/clojure-clore-async-chan...
[3] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe...
[4] https://swannodette.github.io/2013/08/23/make-no-promises
Edits: typos/cosmetic.
https://github.com/athos/kitchen-async offers a macro-based pure clojure expansion/unwinding of calls to functions returning promises, in a way that is similar to what javascript does with their treating of functions annotated with the async keyword that invoke other functions through the await keyword.
For me, the interesting bit is more in the difference in the primitive building block: a channel of values (where nil is disallowed) vs the result of a "computation" which can either succeed or fail.
The channel approach is the most minimal; in fact you can implement a "computation" on top of the channel approach by passing tuples of (result, error) in a channel and then closing it.
I think this is what is happening here. Now, obviously, until you unlearn old environment and re-learn new environment, you will be confused, and probably still feel the need for X,Y and Z... And why would you continue to learn this new language anyways if you were initially motivated by X,Y and Z only, and not learning a different style/paradigm?
I think this is a little bit what's happening with the JS vs CLJS debate. The styles and needs of ClojureScript are not exactly the same as JS. For example, the Promesa library used to have an async/await, but removed it with: "they don't demonstrate to be useful in comparison to the complexity that they introduce". Now, some of that complexity was in their implementation, but I think its true that most of the other constructs in Promesa kind of solve all the common use cases for async/await as well.
There's a coroutine library here: https://github.com/leonoel/cloroutine which you can use to build async/await and generators: https://github.com/leonoel/cloroutine/blob/master/doc/01-gen... and https://github.com/leonoel/cloroutine/blob/master/doc/02-asy... So it can be done, if you want, and I think you should play with it and see if the style bode well within the context of ClojureScript, but the lack of people trying it out to me indicate that its not something they feel as strongly being needed. Keep in mind, generators and async/await are very imperative in their nature, and ClojureScript is primarily a functional language.
That said, I'd also love a native webassembly target.
Macros ARE allowed in Clojurescript, they just need to be defined in other than a Clojurescript file, because of the way how macros work. And you still can do pretty cool stuff with macros. Also if you really need to, you can actually define a macro in Clojurescript that would use `eval`, but nobody does that, because it's rarely needed.
https://gist.github.com/alandipert/4669472
> Transpilation has always seemed to end in failure
There will always be a possibility for languages of higher-level of abstraction, e.g:
Assembly -> Fortran
Assembly -> C -> Python
JVM -> Java
JVM -> Scala
etc.
There will always be languages that transpile, compile into Javascript. It is especially becomes more relevant with the grows of JS, which is no longer a "small scripting language", ECMAscript spec is over 800 pages long today and they keep adding stuff. Java and JVM kind of become "assembly" languages for their respective platforms.
There are many benefits from using the same language on both back-end and front-end. And besides Nodejs, there are a few other options, including Clojure. JS devs even been transpiling one version of ECMAscript to another for many years. If doing that was a "failure", how can you explain so many small and big companies have adopted that model?
do you know why javascript async and co are superior to this?
As far as why promises are superior to core.async you can refer to the list in my comment above.
For this reason and this reason alone, I’ve personally stopped considering clojurescript as a viable technology or recommending it to others. I have no idea how comtemporary clojurescript developers interoperate with javascript libraries and built-ins which are now overwhelmingly promise-based when async. I am also incredibly skeptical of the core.async channel abstraction as an alternative to promises/async iterators insofar as I think it is strictly inferior to both promises and async iterators because:
1. There’s no standard way to communicate error conditions.
2. There’s no logical separation between data producers and data consumers, and any code with access to a channel can take or put values.
3. There’s no way to communicate nil values insofar as core.async chose to use nil to represent end of iteration.
I do not understand the decision by the core clojure team not to support promises or iterators, because one of clojure’s main selling points was pragmatic interopability with host language features. Nevertheless, I infer it must be an intentional decision not to support them, insofar as it’s so easy to support new language features in clojure with macros. Maybe it comes from a desire to adhere to “functional programming principles,” but I’ve found async/await, promises, and iterators/generators to be incredibly useful, so much so that I am unwilling to give it up for clojure’s immutability or nicer syntax.