top | item 27089576

Lessons on Writing JavaScript and React from ClojureScript

100 points| yogthos | 4 years ago |jerue.org | reply

56 comments

order
[+] rchaves|4 years ago|reply
> ClojureScript has unlocked whole new levels of productivity

I keep seeing this claim over and over again about Clojure and Clojurescript. I don't get it, at all. I tried it, multiple times, I don't get where the productivity boost could come from. I though maybe the so-called REPL-driven development (as if clojure is the only language ever with a REPL), I watched videos about REPL-driven development, developed a project that way, cool... not more productive than say TDD or hot-reloading

> I enjoy it because there is little cognitive load in terms of understanding what the program is doing.

Is there? I mentioned I was not more productive in clojure, in fact I was actually much slower in clojure. It's not lack of FP knowledge, because in fact I love how productive I am with Elm, so maybe it's the lisp style, or maybe my inexperience with the language, I can't memorize all thousands of functions to manipulate lists and hashs in clojure (get-in, map-vals, conj, dissoc, select-keys, sorted-map-by, do you know what all those do?) which do a lot, I felt clojure was dense, the opposite of little cognitive load

After that, his following arguments about reagent vs useContext and Re-Frame vs redux is not about some real advantage of clojurescript over javascript, but rather conscious decisions that js community did to be explicit over implicit, it was an explicit trade-off, doesn't convince me that much as positive point for cljs

I want to like Clojure and Clojurescript, I really do, I would like to brag about writing lisp like the rest of you too, please convince me, claiming productivity because of "repl" or "less symbols" is not working, I need deeper reasoning

[+] adamkl|4 years ago|reply
I think claims of improved productivity when switching to Clojure have a lot to do with the person making those claims and where they are coming from.

I see Clojure as almost sub-set of JavaScript bundled with a great standard library akin to Lodash or Ramda. You can write very Clojure-y functional code with JavaScript, and if that is what you are already used to doing, than you might not see a big difference in switching to Clojure.

But JavaScript is a bit of a kitchen sink. You want classes, you got em. You want prototypes, you got em. Functional programming, sure. This wide mix of language features means that everyone’s experience with the language will be different, and if somebody is coming from a very object-oriented background, being exposed to the data-oriented functional approach of Clojure can be an eye-opener.

Personally, using Clojure had a huge impact on how I approach development (having from from a traditional C#/Java background). I consider myself much more productive now and view problems in a completely different light. I don’t write Clojure for work, I’m pretty much full-time Node.js these days, but I credit Clojure with changing my development practices for the better.

I guess what I’m saying is that it’s not necessarily Clojure-the-language that make people feel more productive, but maybe it’s just the development style that Clojure almost forces upon people that makes them feel more productive.

(And personally, I just love the elegance of a language with such minimal syntax... and an abundance of parens)

[+] armincerf|4 years ago|reply
> I can't memorize all thousands of functions to manipulate lists and hashs in clojure (get-in, map-vals, conj, dissoc, select-keys, sorted-map-by, do you know what all those do?) which do a lot, I felt clojure was dense, the opposite of little cognitive load

Funny, I would say that it’s the functions for manipulating data structures that makes clojure a joy to use. To me it feels like there are very few of them, and there’s a lot of consistency which makes it easy to guess what one does if you haven’t seen it before (once you’ve learned map, map-keys/map-vals/mapv don’t really need learning etc). Yes there are actually a lot, but I basically only use a dozen or so. Would be interesting to see the frequencies of clojure syntax vs other languages.

Clojure was my first (professional) language though, and I have used it every day for the last 4 years. I guess it’s different if you’re trying to learn it on the side.

I have dabbled in a few other languages on side projects (js/go/swift/svelte) and while there are definitely big improvements to be found in the tooling, nothing really matches Clojure as a language for me. Though I won’t claim that ‘clojure is more productive’, I will claim that ‘I am more productive with Clojure’, probably largely for the same reasons that you are not, it’s comfortable for me.

[+] titanix88|4 years ago|reply
Clojure is not easy, but it is simple, as Rich Hicky explained. For the trivial projects, you won’t find yourself bulding products faster in this stack compared to others.

To feel productive, you need to amortize your time spent learning Clojure over the time saved building and maintaining complex, non-trivial projects.

I can’t say for Elm as I never tried it. But, as a mostly backend developer, moving from Javascript to Clojurescript was a huge enablement for me. I am a rather mediocre programmer in a two man team. Still, with Clojure/Clojurescript, I was able to make something I could only dream about before.

[+] achikin|4 years ago|reply
> I can't memorize all thousands of functions to manipulate lists and hashs in clojure (get-in, map-vals, conj, dissoc, select-keys, sorted-map-by, do you know what all those do?) which do a lot, I felt clojure was dense, the opposite of little cognitive load

In fact you need to memorize 10-20 functions to be productive in Clojure. Many of them are common across the other FP languages and some FP libraries for imperative languages and most of them cover common patterns for vector/map manipulation. Yes, the names could be confusing at first but there is good reasoning and logic behind them so it’s easy to learn.

> because in fact I love how productive I am with Elm

This is interesting because according to Real World App benchmarks Elm implementation has ~4x more lines of code then Clojurescript one [1]. How could it be more productive to write 4x code for the same result?

[1] https://medium.com/dailyjs/a-realworld-comparison-of-front-e...

[+] mark_l_watson|4 years ago|reply
I agree with you. I have used Clojure many times in work environments because, well, that is what the customer/teams used. I also like Clojure, and am currently writing a short cookbook style book on Clojure AI projects. But, but, but: I am so much more efficient working in Common Lisp.

I think it is more about good software developers using the tools they love and getting good with them. Years ago, one of my repeating consulting customers brought me into a Clojure project where he had hired the Clojure Company Cognitect. One of their people (I think it was Stuart Sierra) was just incredibly productive. I think it was because of who he was and his skill set, not the language.

Several years ago, I managed someone who had non-computer science PhD, who was incredibly productive using Python. Was it Python? I don’t think so.

BTW, re: REPL based development (which is how I roll): I don’t much like Python, but I can’t imagine doing exploratory development not using Emacs and a REPL, in the same mode as using Emacs+Clojure+Cider. Different languages, same experience. It is the mode of development that is more important than the language.

[+] madia_leva|4 years ago|reply
I got into clojure as a hobby for more than one year I am not an expert yet by any means.

It took me a long time to become productive in Clojure. There were simply too many things to learn at once, from syntax, to tools, to, more important, change of paradigm. I did the mistake to also learn Emacs at the same time (love it, but it just took too much time that I didn't have).

I know feel very productive because everything is done in the same way and those dozens of functions you mention is all I have to use 95% of the time. It takes time to learn them and even more to become proficient, but once you are there everything is very simple and fast.

There are two misconceptions that took me way too much time to realize:

- Macros are unfortunately needed sometimes, but they should be avoided as much as possible. DSLs are better done with data structures. - REPL driven development is messy and stateful (you keep modifying vars all the time). A test driven development is much better. Having the REPL is still valid and all what you need most of the time for simple things, though.

Clojure can be very productive, but it requires significant (in my particular case, very significant) investment to get there. In the process, I learnt a lot about FP and, more importantly, about how to model almost anything as immutable data. Once this clicked on me, working in languages without this support for easy manipulation of immutable data structures looks like going backwards.

[+] didibus|4 years ago|reply
It's okay for you not to have been more productive. Programming always involves the programmer to be in the loop, so the language can only help the programmer be more efficient, and a lot of that has to do with how the particular programmer thinks, reasons, and develops, their style and their way of approaching programming. It depends a lot on where your own bottlenecks are, what motivates you, what keeps you engaged, productive, what gets out of your way, and what lets you focus in joy on your problem and work.

Clojure/Script does that for some, it truly makes them more productive, and maybe it doesn't for you, that's totally fine, there's nothing to convince you of.

It's a mistake to think that something that works for you should work for everyone, Clojure/Script truly works for some, and probably won't for everyone and that's okay.

[+] yen223|4 years ago|reply
Yeah, I would like to see more examples of startups picking up Clojurescript (or any of these non-mainstream languages), and running circles around their competitors as a result.
[+] akra|4 years ago|reply
Could it be because it is somewhat similar to Javascript in a way in that it is a dynamic language? Javascript while some may argue is clunky needs to be known to work on web stuff so I still have to learn it anyway. From what I've seen expressing concise patterns isn't where the weakness of modern Javascript is, and therefore I'm not sure if adding Clojure makes it that much better. It doesn't necessarily address the weaknesses that slow me down when working with Javascript is what I'm getting from your comment.

REPL development is great, but I'm more keen to try it on something like F#'s Fable, where I get static typing, the REPL driven development experience (F# also has an adequate REPL with package manager support), with a concise language that maps pretty closely to Javascript. At least then I'm covering up the weaknesses in development I see when doing Javascript - the lack of a type system especially as the project scales. If I'm going to use something to better my Javascript it needs to add something that Javascript doesn't have to make up for the extra complexity IMO. My biggest frustration when writing Javascript is remembering what each function returns especially cross libraries/cross projects. At least for me I'm not sure how ClojureScript fixes that problem. Especially since I could use something like Lodash to get the standard FP functions/syntax.

[+] raspasov|4 years ago|reply
It's not the only language with a REPL but in few languages the REPL is really usable.

For example, I tried the Python REPL recently. It's not worth the effort. Pervasive mutability in the language makes it basically useless for anything practical. No tooling is designed for it, and I believe it can't be without making a new language effectively. If somebody has really used a Python REPL to a great effect, I would like to see it, please let me know.

Clojure allows you to escape "place oriented, mutable-first programming" that is super easy to fall into with all other popular languages. You won't see a benefit if you just try for a day or two.

And yes, I do know what all of those do :) (using Clojure since ~2014).

In reality, you probably need ~20 functions to be productive in Clojure. It's much better IMO to memorize consistently named functions that don't ever change than figuring out what some specific string syntax means in some (new) language. That's just a benefit of being a Lisp, nothing specific to Clojure (in that case).

[+] dkersten|4 years ago|reply
For me, it comes from a few things, none of which are unique to Clojure or necessarily from the language itself. I use reagent, re-frame and shadow-cljs and use the REPL very infrequently in clojurescript:

1. Reagent provides a much simpler and stable abstraction that js react does. Almost all my reagent views today look exactly the same as they did six years ago, while react has went through a number of API shifts. I barely noticed the switch to hooks, for example, the only place where it mattered to me is with interop with native react components and that was rather minimal. My reagent views are typically extremely simple with no logic, except when needed for performance optimisation.

2. I use live reload (figwheel in the past, shadow-cljs now) to auto update my changes. I use reframe-10x to inspect my state.

3. Re-frame gives me the application structure and let’s me write reusable code easily.

Clojure’s immutable data structures make this pleasant and safe (I don’t really need to think about what could be mutating what outside of interop, but the same can be done in js with a re-frame-like framework and a little discipline) and the sequence abstraction is a natural way of dealing with collections of data for me.

These things make clojurescript react development very productive and pleasurable for me. But like I said, none of these things are actually unique to Clojure.

> I can't memorize all thousands of functions to manipulate lists and hashs in clojure

I mean, that’s just part of learning any language or framework and takes time and hands on coding. When I started with Clojure I would regularly just browse the clojuredocs reference pages to remind me what exists. After a while you stop having to look them up. This is no different from learning any function library really, except that if you’re using an OO language with good tooling or static types your autocomplete might be able to better predict what is relevant to you. After years of using Clojure, I rarely need to look functions up, which is similar to how after years of C++ I don’t use autocomplete to find functions (I use it just to save time typing, not to find what the function might be).

I dunno how long you spent with Clojure but it sounds to me that maybe you didn’t spend long enough to get comfortable enough to see any productivity boosts? Clojure isn’t an easy language to learn so it wouldn’t be reasonable to expect to see productivity right away. It’s also not everybodies things, so maybe it just didn’t click with you.

[+] qsort|4 years ago|reply
I don't get it either. I do backend mostly, but my main differentiator isn't even functional vs. imperative, it's typed vs. untyped. As much as this take is going to make half of HN throw up, I'll take Java over Clojure any day, and Java doesn't even have a good type system to begin with.

I could see myself being very productive with lisps for small, plumbing-type stuff that you would use Python or Perl for, but in that area the foremost priority is libraries and the ecosystem, and Python isn't going to lose to any lisp in that department, even though I'm not a fan of the core language.

[+] ithrow|4 years ago|reply
The "level of productivity" when writing a browser app is going to depend completely on your other skills as a developer and not on the superficial differences between JS and Cljs. Only language zealots say Cljs gives them "whole new levels of productivity" over JS, learn to recognize them and ignore them.

For many, learning Clojure can make them better developers but most don't keep using it once they realized they can apply all the hindsights they learn from Clojure to a more productive ecosystem.

[+] namelosw|4 years ago|reply
I have tried both ways, and I agree with what you've said. It's not faster than hot-reloading, and it's not faster than TDD either given the tests run super fast. Both are not too hard to achieve in the JavaScript world.

However, I found where REPL-driven development shines and more productive, is when there's no specific requirement and the programmer is exploring things. Traditionally there are product owners, designers, and programmers in the team, and they work in the pipelines. Both the functionality and the design are settled, the programmer just needs to implement the functionality and the UI.

Now just imagine in a small team there's no split between roles. One obvious way to work is to pretend there are roles: 1) design the core functionalities on paper 2) design the UI on Figma 3) write the code. Although this approach brings clarity, it's rigid and not pretty productive.

A reasonable alternative is to do all the things altogether: design when programming. REPL-driven development is a perfect fit for this methodology. TDD, in this case, is not working well because tests need rigid boundaries to test on, if I write tests I have to erase them repeatedly because I don't have specific ideas of what I'm going to build in the first place. What about hot-reloading? Say if we are developing a game, and the player can jump, with hot-reloading, I still have to implement a button to jump, while with REPL I can send a piece of code to let the player jump without writing UIs for it at all. REPL-driven development provides a perfect playground that can be leveraged as a tool for both thinking and programming.

In hindsight, TDD resonates functionality designs from product people - if it's settled we can write test cases and just implemented it. And hot-reloading helps a lot in implementing settled graphic design - because there's no better way to test graphic than just seeing it in real-time. If there is neither functionality design nor graphic design in the first place, we need new things to help us to do the all designs with our code, where REPL-driven programming is a perfect it. Rather I would call it REPL-driven design.

To maximize gains from a tool, the way we work and think must be adjusted. As another example, many programmers who favor statically typed languages program in dynamically typed languages, usually don't write in the same way as those who favor dynamically typed languages, thus for them, dynamically typed languages are just net loss.

IMO the REPL-driven design resonates with Paul Graham: A programming language is for thinking about programs, not for expressing programs you've already thought of. It should be a pencil, not a pen. . This approach also reminds me of the 'Sketch HTML' approach in Getting Real from 37 Signals.

[+] zshrdlu|4 years ago|reply
Perhaps it could be framed differently. Clojurescript has unlocked new levels of productivity for him/her.
[+] testycloj|4 years ago|reply
I feel the best thing about clojure is test.check. The fuzzy generators are a delight to work with.

Everything else is just bonus: functional, immutable, benevolent use of brackets, edn, https://clojuredocs.org/, interop, uberjar, spec, clojurescript, https://github.com/clojure, lein, deps, native-binaries, repl, etc.

[+] da39a3ee|4 years ago|reply
I'm certainly attracted to getting more experience of FP / a lisp in production (as opposed to a lisp in my text editor). However,

> Previously working with TypeScript has made me miss types and appreciate the runtime checks of spec.

This seems like a huge deal. Javascript has got to the point where it has nice static typing via typescript, and various new type-check-time language constructs associated with the typing, and enums, and really nice tooling for it all. Coming from Python I've been really enjoying typescript, since the tooling works slightly better (and doesn't suffer from Django's typing dead-ends). So, I am not all sure that I would be prepared to throw away static typing in exchange for lisp.

[+] mping|4 years ago|reply
I would love if clojure had a solid gradual typing story just as ta has. There is core.typed library but I haven't tried nor is it widely employed.

However I think its a valid trade-off, and with sufficient experience you learn to navigate the issues just like in JavaScript.

[+] rjerue|4 years ago|reply
Wow, idk if I should be honored or ashamed. Lots of great discussion/things for me to think about! Thank you for that . I made a HN account just for this, want to make a few things clear.

- I'm new to Clojure, but did a lot for my formative work in Scala, and employed a pretty functional model using JS.

- While I really enjoy Clojure, I'll admit that JS is still my muse; it's the language that I dream in. React is the mental model I use to think of UIs. A perhaps naive summary may be that I'd give is what partly motivated me to write this is that JS and React are star crossed lovers. JS is by no means perfect, and I'd love to see it grow.

- I've been following elm for a while. It was after I met Jesse Tomchak from https://player.fm/series/javascript-to-elm at a conference a few years back that I started reading about it. When I tried it, the tooling wasn't quite where I wanted it yet and I'm a guilty of being a tooling primadonna.

- When I say I used JS, I really mean I used TS too! Speaking of being a tooling primadonna, VSCode's TS interop was a thing of beauty. Yes, it's 100% easier up front to look at a TS type and see what's going on compared to spec. I miss types. I think Clojure needs more in spec and for a large part, it continues to be neglected by the Clojure team. Spec vs. Types could be a whole essay/talk on itself.

- This isn't a call to action to use ClojureScript or Clojure. It's my critique of what I was using for years (and still will continue to use). Use what tools you feel most confident in to solve the problem in front of you.

[+] agentbellnorm|4 years ago|reply
I just wish more people would adopt clojure and clojurescript. The world would be a better place.
[+] christophilus|4 years ago|reply
Fellow South Carolinian and former Charlestonian. I nearly applied to Reify for the same reasons. Clojure is one of my favorite languages. However, I suspect I’d dislike it in a large product / team setting. Having moved from C# to Rails to JavaScript and now to TypeScript, I think I’d miss static types too much.

I’d be very interested in a follow up blogpost after you’ve been at Reify for a year or two.

[+] rjerue|4 years ago|reply
> I’d be very interested in a follow up blogpost after you’ve been at Reify for a year or two.

Me too!

[+] raspasov|4 years ago|reply
Congrats on the new job! Clojure(Script) is great. I remember how excited I was when I first "understood" Clojure.

One small note:

"There are also @ symbols for expressing async behavior."

@ is just a reader macro, aka shortcut, for (defer ...) in Clojure(Script). https://clojuredocs.org/clojure.core/deref

[+] rjerue|4 years ago|reply
Haha, the number of folks who have told me this! Yeah, probably a byproduct of the era I learned in. I just reach for the macro first, just what it's programmed into my head as
[+] raspasov|4 years ago|reply
EDIT (typo): "(deref ...)" not "(defer ...)"
[+] mikojan|4 years ago|reply
1. Redux docs encourage you to use @redux/toolkit which drastically reduces boilerplate. And even if you do opt into using vanilla redux: Typescript for some time now has supported string literal types which render enums obsolete almost.

2. Mutability is not at all a problem. Those types of bugs are obvious. On the other hand, mutability allows for a type of fine-grained control you will absolutely need. It is Javascript after all.

3. Redux _does_ allow "users to create complicated compounded subscriptions in a performant way using materialized views" which is a rather convoluted way of saying "memoize derived values." You are looking for the createSelector-API you had been shunning in the opening statement.

4. What in the name of god almighty is this:

        function Component({ children }) {
          const { data, loading, error } = useGetMyData();

          return (
            <div>
              {(function () {
                if (loading) {
                  return (
                <div>loading...</div>
              );
                }
                if (error) {
                  console.error("Uh oh!");
                  return (
                    <div style={{ color: "red" }}>{error.message}</div>
                  );
                }
                return (
                  <>
                    <div>Data:</div>
                    <div style={{ color: "green" }}>{data}</div>
                  </>
                );
              })()}
              {children}
            </div>
          );
        }

We are encouraged to pour yet another layer of abstraction on top of Javascript pulling in clones of the very libraries we had already been using. Only now, our knowledge about performance testing and tuning in ReactJS/Javascript is basically useless.

At this point, why not do away with all "boilerplate" and just use Elm?

[+] prezjordan|4 years ago|reply
I assumed (4) was the author's frustration that if-statements aren't expressions, and nesting ternaries is rough.
[+] beirut_bootleg|4 years ago|reply
I can't decide if that IIFE is the sign of a non-React dev trying to jam in concepts from other languages, or just done in bad faith to force a point.
[+] swordbeta|4 years ago|reply
What is wrong with the component?
[+] tiborsaas|4 years ago|reply
Refactored the ugly JSX code to be neat JSX.

    function Component({ children }) {
      const { data, loading, error } = useGetMyData();

      return (
        <>
          {loading && <div>Loading...</div>}
          {error && <div className="red">{error.message}</div>}
          {!loading && !error && <>
            <div>Data:</div>
            <div className="green">{data}</div>
            {children}
          </>}
        </>
      );
    }
> However, HTML is too declarative of an S-expression to be efficient in constructing the DOM.

This is statement is also stinking. HTML is meant to describe the DOM and does a great job at it.

[+] lobstrosity420|4 years ago|reply
JSX is easily the worst part about writing React apps to me. It tends to get convoluted and hard to read really fast. Seeing someone trying to represent it with LISP syntax I now realize it could be 100x worse. Don't get me wrong, Elixir sold me on a lot of the stuff the functional paradigm brings to the table and Closure peeked my interest in having some of that in front end land, but please lose the LISP XML tree, it is a crime against God and nature.