CLJS Promises
bikeshaving 69 days ago [-]
I stopped using clojurescript around 2015, primarily because I was tired of searching for/implementing clojure-idiomatic wrappers around the many javascript libraries out there. Put another way, I left clojure on good terms, and often missed its core functions, immutable data structures and lisp syntax when writing front-end applications. However, since then, javascript has changed a lot, specifically in terms of execution-suspending operators like async/await/yield/yield*. Meanwhile, the core clojurescript team has almost completely ignored these developments, preferring the immutable seq abstraction over iterators, and the core.async channel abstraction over promises/async iterators.
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.
yogthos 69 days ago [-]
My team has been working with ClojureScript for close to 5 years now, and we haven't had any of these problems. Promises work just fine, you don't need any additional support for them. For example, this works just fine:
` (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.
https://www.youtube.com/watch?v=IekPZpfbdaI
cutler 69 days ago [-]
Your example is only calling Javascript directly. I think the OP meant that Clojurescript has not adapted to promises/async with its own (improved) version. You simply have core.async which predates ES6 and so does not take advantage of the new async/yield features of ES6. It's about implementation and I agree with him that Clojurescript does not seem to be interested in adapting to the current JS standard.
capableweb 69 days ago [-]
> Clojurescript has not adapted to promises/async with its own (improved) version
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.
iLemming 68 days ago [-]
> Clojurescript has not adapted to promises/async with its own (improved) version
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
blain_the_train 68 days ago [-]
I can't speak with specially on those features. But so far, if I want something from js or a npm lib it's I straight up just use it via interopt. Is there a reason that doesn't work here?
Scarbutt 69 days ago [-]
`
(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.
altanil 69 days ago [-]
Yes. At its worst it's just Javascript (with parentheses), especially when dealing with libraries and promises.
In other cases you can benefit from all the features of ClojureScript.
That's pretty magical in my experience
yogthos 69 days ago [-]
The thing is that interop typically lives at the edges of the application where IO happens. I'm typically using a Js library to do Ajax, paint stuff on the screen, and so on. All the core logic of the application is written in pure ClojureScript. And that's where all the interesting things happen.
kccqzy 68 days ago [-]
And I'd say writing JavaScript with parentheses is good. I'd take this over JSX any time. Reason being this is more concise than writing JSX, and with better editor support (paredit mostly).
iLemming 68 days ago [-]
> And you have gone full circle, back to writing Javascript but with parentheses.
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.
capableweb 69 days ago [-]
You could do more! Use Array.map and Function.apply via interop and you seem to have it all in JS
adz5a 69 days ago [-]
Hello, this is a really interesting comment although I disagree with some of its points. I recently had my first full time Clj/Cljs job and it made me realise some points:
- 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..
[2] https://github.com/mfikes/cljs-bean
kendru 69 days ago [-]
I was not aware of cljs-bean, but it definitely looks like it's worth checking out!
ambulancechaser 69 days ago [-]
How can you use hooks right now? The standard is reagent which compiles down to classes which don't work with hooks. I think reagent has given a functional react and hooks style programming for years but now is a little behind the curve.
orestis 68 days ago [-]
There’s a library called hx that embraces hooks and the native React way. You still write hiccup, but you get a React functional component.
iLemming 68 days ago [-]
Reagent works with hooks. https://github.com/reagent-project/reagent/blob/363f2d4976ac..
capableweb 69 days ago [-]
You would do what Reagent does and use JS interop yourself and call React directly. Plenty of people in Clojure-land implement things themselves if the thing to implement seems simple enough. Probably more true for Clojure developers than let's say JS/Ruby developers.
i_s 69 days ago [-]
At my job, I work on a ~40 KLOC Clojurescript re-frame app. It has been working very well for us, and none of the things you bring up have been an issue.
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.
bikeshaving 69 days ago [-]
What is the async story for clojure if it isn’t core async? How do you make server calls? If you’re still using callbacks, you’re really missing out on the convenience that async/await provides.
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.
capableweb 69 days ago [-]
In re-frame you would dispatch a event to tell your application you need some data to be fetched. Once the data has been fetched (or received an error), it dispatches another event. So basically with re-frame applications, it's all events.
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.
i_s 69 days ago [-]
For server calls, we just use the event dispatch system that comes with re-frame. For most such server calls, one needs to do 3 things:
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.
lemming 69 days ago [-]
I don't use CLJS a lot, but I do have some production CLJS code running on Lambda which uses promesa - it's much nicer than core.async IMO. I'm on a slightly older version, but it now contains versions of let which work transparently with promises:
https://funcool.github.io/promesa/latest/#promise-compositio..
robto 68 days ago [-]
Some alternatives to core async that I think are very thoughtful are promesa[0] and manifold[1]. We've got a large chunk of core async code that if I had to redo I would use one of those libraries. They are conceptually simpler, I think, and they don't have the same downsides as the `go` macro.
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
wellpast 69 days ago [-]
At first glance the cljs community doesn't seem short on libs that give await/async-like abstractions and from what I recall JS interop in CLJS was smooth enough to operate directly against Promise objects.
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..
ithkuil 68 days ago [-]
I'm confused. Doesn't core.async do the same? The go macro applies a transformation on the body that is analogous to what happens with async/await. The difference is the async/await contract is built on top of promises which define a single future value or an error, while core.async uses a channel which can only yield non-nil values (no place for errors).
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)
wellpast 68 days ago [-]
You're not confused. I think you're right that both cljs/go and js/async blocks are giving a 'procedural way' to basically create an asynchronous processing/state machine.
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 worth 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.
ithkuil 67 days ago [-]
we're mixing up language implementation issues and the underlying primitives.
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.
didibus 69 days ago [-]
I think there is a tendency to think... My current language would really benefit from having X,Y and Z. And then going to look for an alternate language that has X,Y and Z. But only your current language might benefit from X,Y and Z. It is not true that if you change the environment say to ClojureScript, that you will still feel the need for X,Y and Z.
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.
zelly 68 days ago [-]
A few years ago when I tried it, I got frustrated with it very quickly when I found out macros are not allowed in ClojureScript. I think you should either write JavaScript directly or compile to WebAssembly from some better language. Transpilation has always seemed to end in failure.
ithkuil 68 days ago [-]
Macros are allowed in clojurescript. It's just that they cannot be defined in the same compilation unit where they are used. Yes it's a pain, but not nearly as your phrasing makes it sound like.
That said, I'd also love a native webassembly target.
iLemming 68 days ago [-]
> macros are not allowed in ClojureScript
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?
dominotw 68 days ago [-]
> preferring the immutable seq abstraction over iterators, and the core.async channel abstraction over promises/async iterators.
do you know why javascript async and co are superior to this?
bikeshaving 68 days ago [-]
I’m not sure if they’re superior. One thing that iterators can do which clojure lazy-seqs can’t is that with iterators, we have the concept of abrupt termination or return. In javascript, this is analogous to calling break within an iteration block, or calling the return method directly on the iterator. There is no equivalent concept with clojure seqs, meaning you can’t do things which tie down resources, like creating a database cursor and yielding the results. With a generator, you just make sure that you close the cursor by wrapping your yield operators in a `try/finally`. There is no equivalent for a recursive lazy-seq function, even though clojure does have `try/catch/finally` expressions.
As far as why promises are superior to core.async you can refer to the list in my comment above.
vikeri 69 days ago [-]
Great to see people putting effort into making Clojure(Script) more accessible! As much as I like the language and the community, it historically wasn't a very beginner friendly place in my opinion. Not that people weren't friendly towards beginners but just a lack of resources targeted at beginners.
Zelphyr 69 days ago [-]
I’ve been going through this book lately and it is very well written. It’s one of the best introductions to Clojure and ClojureScript that I’ve found.
jwr 68 days ago [-]
Having read most comments under this post, my conclusion is that the Blub Paradox is very real. I am surprised that otherwise sophisticated HN readers fall prey to it. I see a lot of comments which are outright dismissive, from people clinging to what they know, and in most cases not knowing much about what they are criticizing.
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.
lbj 68 days ago [-]
As someone who has been doing Clojure/Cljs for a long time, I really appreciate it whenever someone goes the extra mile in explaining the basics - All the things clojurians take for granted in their every day work. Kudos to the author!
js4ever 69 days ago [-]
Closure seems to focus on purity instead of productivity. When I read this article I imagine my team spending days to implement basic features they could implement in few minutes with vanilla JS. Business don't care about code purity. Only security, performance and correctness really matter.
kendru 69 days ago [-]
Having worked with several functional programming languages, my personal experience is that Clojure seems to lean more towards the productive end of the spectrum than the pure end. There is a definite learning curve coming from JavaScript, but (as someone who currently writes more JavaScript than ClojureScript), I still find that the ClojureScript that I write ends up being simpler will a smaller code footprint than a similar JS solution.
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.
iLemming 68 days ago [-]
With respect, you honestly seem to have no idea what you're talking about. I, just like many other front-end developers and just like you, was pretty skeptical about Clojure and Clojurescript for quite a long time. But at some point, it felt like I've done enough Javascript to make me feel tired of dealing with its peculiarities. After working with Coffeescript, Typescript, LiveScript, GorillaScript, IcedCoffeescript, Babeljs, Traceur, Fay, Haste, GHCJS, after looking at Elm, Reason, and Scalajs I felt like I exhausted all possible alternatives. Finally, I got to try Clojurescript.
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.
dorian-graph 68 days ago [-]
> .. after looking at Elm, Reason, and Scalajs I felt like I exhausted all possible alternatives.
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.
iLemming 68 days ago [-]
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.
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
dorian-graph 67 days ago [-]\
First of all, I appreciate the reply!
> 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?
physicsyogi 69 days ago [-]
I can't speak for others, but I'm far more productive in Clojure. I'm able to write less code in less time, without any loss in correctness. My productivity comparison is in relation to Java and Python, which is mainly what I use at work.
iLemming 68 days ago [-]
I completely agree. No other language felt more productive to me as Clojure and I have used many. Today, when I have a problem to solve and I'm not even allowed to use Clojure (say for an interview challenge) I still first write it in Clojure and then by hand, line-by-line translate it to the required language. Even though it may seem like twice the work, I swear - it is faster that way.
sailfast 69 days ago [-]
I don’t work a lot with clojure but while perhaps, as you say, “businesses may not care about code purity“, consistent behavior of code, a more functional approach, and the ability to reason about what will happen in code can lead to significant increase in speed of development and increased reliability especially for more complex applications, generally. A focus on purity can deliver this. Sometimes getting over the initial learning curve to get to that level is worth it. Sometimes it may not be.
capableweb 69 days ago [-]
I can understand where you coming from, Clojure developers take a lot of time to think and discuss what seems to be irrelevant (to the business) details of software engineering.
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.
iLemming 68 days ago [-]
> Clojure developers take a lot of time to think and discuss what seems to be irrelevant (to the business) details of software engineering.
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.
capableweb 68 days ago [-]
I wasn't thinking about the ecosystem or tooling, but more the fundamentals of the structure of a program. Thinking more about the domain, how to solve the problem in a simple maner and similar. Not arguing about syntax or other smaller distractions.
I agree in everything else you wrote.
blain_the_train 68 days ago [-]
Can you give a concrete example of something that you feel takes longer to implement on js then in cljs?
##
iLemming 68 days ago [-]
There are lots of things migrated from Clojure to other languages, including Javascript. Destructuring implemented in ES6 has roots in Clojure. Mori and later Immutable.js borrowed many ideas from Clojure. Transducers first were implemented in Clojure. Many front-end devs got so excited about React hooks, unaware that Clojurescript developers been writing code in similar way for years.
Once you learn Clojurescript to the sufficient level, pretty much anything feels faster to make with it.