Learn ClojureScript: Discovering Sequence Operations
learn-clojurescript.com
learn-clojurescript.com
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.
(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.
Over the years I've learned that if there is something very different about a language or a solution, or if there is something I do not understand, it is a strong indicator that it's something worth learning about. Sure, it might be worse than what I'm using, or might not be applicable to my use case, but usually there is something there worth investigating, and sometimes you discover a better way to program.
Clojure(Script) is not for everybody, but I love it because it is very natural for me to use, and it seems to fit the way that I think better than most OO languages that encourage top-down design. I agree that security, performance and correctness are good end goals, but they are not the only goals (another one that comes to mind is maintainability), and I have found Clojure to be a good means to reach those goals.
Honestly, I wasn't very impressed right away.
I hated the fact that Clojure hosts on JVM. I had not done enough Java at that point to know that JVM is a pretty solid piece of technology. People hate Java and sometimes don't even bother checking out JVM because they hate Java.
I did not like the fact that Clojure is a Lisp dialect. I had only shallow exposure to some emacs-lisp and wasn't even sure why people so excited about Lisp.
I did not like the fact that Clojure is a dynamically typed language. Learning some Haskell turned me into a big fan of typed programming, and naturally, I was quite skeptical. It was before Clojure.Spec, so until Spec was announced, I was still somewhat not sure if Clojure as a language is suited for large codebases.
It turned out - all my skepticism was unwarranted. I had worked with more than a few languages throughout my career - my first toy language was Basic, and my first "real" PL Turbo Pascal. Yeah, I guess I'm getting a bit old. Anyhow, Clojure turned out to be not just pure joy; it brought a lot of sense and clarity to my work. I never felt as productive with any other language before. Sometimes I try to rethink my positive thinking about Clojure and ask myself, maybe that's what Stockholm syndrome looks like? And I try to compare it with other languages and over and over again still feel like for me, right now, Clojure and Clojurescript make sense more than any other option. Sure, someday, it will stop making sense, and I will switch to something else, just like I did with many languages before. But until that day I will be using Clojure. Because for the type of work I'm doing today, it is probably the best option - it is nicely balanced between pragmatism and theoretical, idealistic language, hypothetical "silver bullet," which does not exist and never will.
As someone who has been using Elm at work for the last almost 2 years, but wants to move on, I’m curious about this line. I’ve been thinking PureScript or ClojureScript but admittedly I also have the strong concern of dynamically typed languages.
And I'm not going to tell you that Clojurescript is necessarily better. However, if your choice is dictated by academic curiosity, by all means, do try Purescript. But if you're seeking a tool that allows you to move fast but at the same time to be not too sloppy, maybe give Clojure a try. Beware though: you will encounter things you probably won't like:
- You will hate JVM startup times until you figure out that you don't have to restart REPL. I have some REPL sessions running for days and weeks.
- You will hate error messages. To be fair though, after using Elm, you will hate error messages in any language. Spec and libraries like Expound do improve things, but error messages never get to the level of Elm-awesome.
- If you have never used Lisp before, you will struggle with parentheses until you find a structural editing tool/plugin that works best for you. But after that, it is tough to live without them.
- And yes, you will miss the static type checker, even after learning Spec. Spec is fantastic, and you can do some pretty neat tricks with it, where for something equivalent in Haskell, you'd have to go Liquid. I missed type inference and static type checker in pretty much every dynamically typed language I've used. Clojure minimizes that feeling, but unfortunately, it never dissipates completely.
Historically, there are two camps of languages with roots in lambda-calculus: ML/Haskell and Lisp.
Clojure from the Haskellers' point of view is "worse is better" of Haskell. Haskell from Clojurists' perspective is "too ceremonial and bureaucratic."
But the truth is - you try to learn Haskell, maybe use for a bit, but the chance that you ever get to the point where you can comfortably use Haskell in production is not great. Admittedly, not too many of us developers ever get there.
With Clojure, you can get there pretty quickly. It is way more pragmatic. And every time I go back to Haskell/ML - there are things I miss that exist in Clojure, and whenever I write Clojure, there are things I miss from Haskell. My life is never will be the same
> Although I'm curious, I wouldn't ask you to explain why. You probably have your reasons. But I want to note that I love Elm. Elm is a fantastic language. For what is it made for - to build single-page web app frontends, it is hard to think of a better choice.
I'm happy to answer! Elm has been a fantastic introduction to functional programming. At work, we're very happy that we took the risk on it early on in our startup. What I'm looking for more is a more advanced language, that has things like type classes. A more open development plan would be great too.
> And I'm not going to tell you that Clojurescript is necessarily better. However, if your choice is dictated by academic curiosity, by all means, do try Purescript. But if you're seeking a tool that allows you to move fast but at the same time to be not too sloppy, maybe give Clojure a try. Beware though: you will encounter things you probably won't like:
Are the downsides of PureScript, similar to those for Haskell?
I can definitely imagine your other points being so true.
Do Clojure(Script) apps often have runtime errors because of the lack of static type checking? With Elm, there is that nice guarantee that if it compiles, then it simply won't have any runtime errors.
I'm looking to spend more time with a Lisp, before trying out Haskell.
> My life is never will be the same
I struggle to put into words why functional programming (and Elm) are are so good, it's sort of you just need to try it for a few months. Is that your experience with Clojure, or are you able to put it into words a bit more?
I can ensure that it's not like that at all. Clojure is a very productive language (see: focus on repl, clojure.spec and similar efforts) and also pragmatic.
If you and others have the feeling that it seems like a unproductive language, I invite you to take a closer look. Once I sat down and actually tried hard to learn it, it didn't take very long to get productive in it, compared to other languages I've learnt.
I beg to differ. I don't know how, maybe due to my experience and knowledge, but working with Clojure developers, discussions usually levitate towards the business problems and not the language ecosystem and tooling. Language just gets out of the way, it does not impede, does not slow you down, the distraction is minimal.
In other languages there are always talks about the syntax, about styling, about dependency conflict resolutions, subtle bugs that may or may not happen. In OOP languages - class hierarchies and patterns, interfaces, etc. There is always at least one smart-ass "rebel" in the team who writes code, adding some "nifty" tricks and esoteric, gimmicky features of the language no one else in the team knows about.
Of course, like any other language Clojure has "idioms" and like any other language it is possible to write "cryptic" code, but usually newbies learn those idioms quickly enough. Just like with irregular verbs in English - although they seem to be strange at first, you learn to use them because they are used very often.
I agree in everything else you wrote.
Once you learn Clojurescript to the sufficient level, pretty much anything feels faster to make with it.