The Further Adventures of Starting a new ClojureScript project in 2022

I think the issues you’re talking about, is that most use-cases for JavaScript are really using it as a scripting language to configure a framework of some sort (browser DOM, react native, electron, etc.)

Since you don’t actually use JavaScript to model concurrent information systems, powerful immutable data-abstractions aren’t as useful, and then manipulating frameworks with an added layer of translation maybe just adds friction.

In general, UI and UX code, it’s often about just figuring out the right hook, the right setting, and the right call, to get the UI framework to render what you want, reflow how you want, layout how you want, etc.

But if you had business logic, it would still benefit from being pure no, for which immutable data-structures would then come in really handy no?

I’ve not used it for such things, but if it was used for it in a Node backend for example, or inside an app for the business logic core layer and data access, wouldn’t the pure, functional, immutable structures have the same benefits as Clojure JVM? And you wouldn’t mind as much about bundle sizes, async/await support, back and forth js->cljs conversions, etc.

I have fairly strong feelings about languages and JS is just a deal-breaker for me for any serious development

Haha, same, might be one of the few languages I’ve turned down an offer because of. But I think maybe ES6 JavaScript has improved quite a bit, and I should probably give it another chance.

then having something like shadow leverage NPM to sort out all the bundling /modulesand externs is a big advantage

I’ve always wondered, if shadow-cljs can automatically figure out the externs, couldn’t something automatically bundle NPM packages as cljsjs packages?

I definitely don’t do serious Clojurescript use, don’t use it for work, so only some of my personal projects I’ve used it for, but cljsjs I’ve always found an overall better experience even when compared to NPM dependencies through shadow or the newer Clojurescript bundle NPM support.

Not having to deal with NPM at all, and just adding deps, requiring as normal, it’s so much nicer. The issue is you can’t find everything as cljsjs, but if there was like an auto-importer, when you wanted something form NPM, you’d run it, it convert it to cljsjs and publish it to Clojars, then just add is as a normal deps. If this was possible, wouldn’t it be much nicer? Or would there still be reasons to favor NPM ?

Yep… that’s absolutely what I’m talking about. The right tool for the right job. The clojurescript repl was and still is a game changer for ui development. The immutable datastructures are not.

Can you give an example of some business logic that might be super complex?

There are definite use cases for immutable data things compilers but I don’t see how something like ‘Pay Money’ + ‘Get Receipt’ benefits from using immutable data over a mutable version, especially if you’re just calling a web api (which is pretty much immutable since the output of the call can’t really change the results of the input).

Even in backend applications like a node server, it’s pretty easy to live without immutable datastructures as people working in other languages can attest to doing all the time. If the data transformations are not too difficult and it’s just a server passing requests to a database, then it’s possible to make do and the added bonus is that the application will be faster and there will be significantly less GC activity.

Ring middleware is a really good case for immutable datastructures because of how complicated the request post processing may be but it really depends on the webstack and how services are split up. Sometimes it’s better to have 2 or 3 lightweight services to handle traffic in stages rather than a heavy middleware based app to handle everything.

The real use case I see for immutable data is for code generating code. For example, these repos have been 100% generated.

You can pull it down and use make to build it but everything in the repo is an output from a function - the readme, the config files as well as the lua and js source files.

1 Like

That’s cool. What did you generate them with?

Well, most of my work is being the person who implements all these APIs you might be calling :stuck_out_tongue_closed_eyes:

So actually paying money is a very complicated process of quite a lot of business logic probably layered in more and more microservices.

I work in other (mutable) languages and attest to the opposite :man_shrugging:

My experience is that it’s one of the most pernacious cause of some of the most impactful issues, and hardest to root cause, and since I’m a DevOps, I feel the pain of that first hand at 3am. I also find it a leading issue when it comes to code structure, design and maintainability in a large code base with lots of engineers working on it over multiple years of time. The place-orientedness always seem to turn relatively new services into “legacy” on a “deprecation” path, because it gets too confusing to follow the data-flow as it is at runtime and make sure that the right thing will be in the right place at the right time and everything will be synchronized properly against it all.

It seems inscompicuous at first, until that one for-each-loop that mutate its own collection as it iterates, and does so from inside of a setter of a method that’s called from another layer which is passed the collection from inside the loop.

Or untill people start to “conveniently” modify what they need too in order to deliver their story and the behavior changes they’re wanting to make, but do so in an obtuse place down the call stack, which eventually becomes an unknown effect to anyone else working on the code base.

And so on…

The worst offender is really the simple case of passing a mutable thing to something else, that will unknowingly to the caller, mutate it, so that you’re assumptions of what you have after it returned are no-good, forcing you to take special care to drill-down into the call and validate yourself what is actually done to it almost every time.

But you’ve also got things like testing made so much easier with immutable data-structures. The simple act of having to reset the state after each test versus just passing the same immutable starting state to multiple tests, the latter is so much quicker.

And obviously if you throw in shared concurrent access, well that’s a whole other ballgame.

I’ve never really found mutable variants better unless I need performance to be honest. There’s a small argument to make about the use cases where the benefit is to modify a part of a larger thing in-place, such as Var redef being the magic trick of REPL hot-reload without having to reload everything, so those cases mutable is also useful. And I think this latter use-case might also apply a lot for UI, and why it generally relies on mutability, though FRP ideas are interesting.

But in general I find immutable much easier to reason, work with and maintain.

Edit: I want to add, I don’t think it’s the panacea of software maintainability and safety. I think it’s only one small thing in fact, in Clojure it combines with many other small things to create a whole bigger than each of its parts. Which is why I’m not super excited that JS is maybe getting some immutable data-structures. The way I see it, immutable is just slightly simpler than mutable and let’s you reason about things more independently and in smaller chunks. If you combine multiple things that are all slightly simpler and more independent and easier to reason about, eventually you get that the entire code base is a lot simpler, with better divide and conquer, composed of smaller pieces that you can independently reason about, and that’s when you start.to get really 10x multiplier in overall benefits. It’s also why I’m not one of those make everything final always Java person, because in Java that is just not enough to really end up having an impact, unless you also take multiple other measures along with it.

4 Likes

its an experimental transpiler. not ready for prime time but works decently well.

1 Like

Sounds interesting.

An s-expression to JS transpiler is something that cherry can easily be turned into (in fact, it started as a fork of scriptjure which is exactly that). I just don’t know what the API would look like, e.g. if you do (assoc {:a 1} :b 2) would this create a mutable object, and mutate that thing in place? I guess it wouldn’t be Clojure anymore? Would this be something people are interested in using?

I’ve also experimented with compiling to ImmutableJS: this gives you a smaller bundle (65kb of data structures instead of several times more in CLJS) but I’m sure there are caveats there too.

1 Like

I get what you are saying but it’s not the problem with mutable state. It’s more of a culture and unit testing problem as those issues will be picked up right away with the proper tests. The Clojure runtime is literally just one big global, mutable state but it works really well when it’s used in the right way. I don’t think there’s a simple solution to bad code besides testing as many parts of the code base as often as possible. If new functionality needs to be added, then all regression tests need to be passed and new test cases also double as the communication to affected team members.

Microservices are probably the most place-oriented design ever. What happens when a microservice fails? Well there is a watcher service that restarts it. What happens when that fails? There is another emergency service that performs the recovery. What happens when that fails? … You get my point. I feel that micro services has become the ‘industry standard’ because devs were given too much choice and time to blog. Come work at our company, you can program in any language you want because we use Microservices.

Yep. cherry is great and goes the way of ‘code generating code’.

I’d be interested to get your thoughts on a ‘clojure subset fork’ proposed here:

So basically… if one wanted to implement something in C and to run code that cherry will also be able to run. What features would one need to implement without looking to recreate clojure in it’s entirety.

I have a branch of cherry here which emits “direct” JS without the immutable stuff.

What I’ve done:

  • keywords are mapped to strings
  • map notation maps directly to js objects
  • vector notation maps directly to js arrays
  • assoc! and dissoc! map to in-place mutation of js objects
  • keys destructuring works (emits more boilerplate than needed, could emit spread operator directly)

Demo in this tweet

Examples:

The code could be more optimized by inlining assoc! and dissoc! behavior which can be accomplished by making those macros.

I’d be happy to pursue this further if it seems promising enough :slight_smile:

Edit: I now published this to NPM: cherry-cljs@0.0.0-mutable-alpha.53

Ah screw it. I made a repo here:

And published it to NPM. Let’s have some fun.

4 Likes

Big disagree. Tests are not a replacement for immutability. Deep equality makes many things possible that aren’t otherwise. Some of us actually do need to get real things done in the browser, where better-than-a-ball-of-mud semantics become beneficial.

Edit: but sure, if I had to write js, I’d prefer to do it with a thin sexp wrapper with macros. But we’ve had options for that for years. Eg. GitHub - wisp-lang/wisp: A little Clojure-like LISP in JavaScript

4 Likes

Could there be a way to make the Clojurescript standard library an actual library?

I’m wondering, technically you can replace cljs.core and have an alternate one where hashmap and all create their mutable js equivalent.

Only the literal I’m not sure if that has to be in the compiler.

Though I might be thinking of Clojure here.

But the language core are the special forms, and the literals mostly no? Everything else you can swap for an alternate clojure.core (I’m assuming similarly for a cljs.core)

Maybe it’s best having them seperate, but I’m wondering if it can’t be a bit like Racket where at the top of the file you go:

#semantics :js

or

#semantics :cljs

And based on that it replaces what the literals evaluates too and swaps a different cljs.core

Just some thoughts.

1 Like

I never said that. For some one who is quoting Plato, at least get your set theory right.

Btw… it’s definitely a culture thing. Immutability are not a replacement for tests either.

Shall I ask you whether God is a magician, and of a nature to appear insidiously now in one shape, and now in another–sometimes himself changing and passing into many forms, sometimes deceiving us with the semblance of such transformations; or is he one and the same immutably fixed in his own proper image?

Obviously God is blue, plays the flute and hangs around with 10000 beautiful princesses.

Is that immutable enough?

And things which are at their best are also least liable to be altered or discomposed; for example, when healthiest and strongest, the human frame is least liable to be affected by meats and drinks, and the plant which is in the fullest vigour also suffers least from winds or the heat of the sun or any similar causes.

These Plato immutability quotes are pretty great lol

And will not the bravest and wisest soul be least confused or deranged by any external influence?

And the same principle, as I should suppose, applies to all composite things–furniture, houses, garments: when good and well made, they are least altered by time and circumstances.

Then everything which is good, whether made by art or nature, or both, is least liable to suffer change from without?

2 Likes

A little template for when I need to start a new cljs project: GitHub - dimovich/deps-cider-cljs-reagent: Minimal deps.edn + CIDER + ClojureScript + Reagent + Figwheel

1 Like

Thanks. I’ll check this out

had to step away a while from religious debates (old habits).

the plato quotes are pretty awesome btw.

1 Like