Benefits of a hosted language vs the host, beyond syntax?

Clojure is the best of hosted languages (someone please inform me of other contenders for this title). Being a hosted language and the details of that are a subject for somewhere else, but here I am concerned with the discussion with stakeholders who argue for the host language. For example, “why use ClojureScript when I could use plain JavaScript?” or “Why use Clojure when I could use [Java, SQL, CSS, etc]?” There must be answers beyond “Clojure has better syntax” (which is, again, a statement for another place).

The short answer to both of those questions (cljs > js, clj > java), for me, is: “By using clj/cljs we can get the same functionality with fewer lines of code, and with fewer lines of code we’ll have fewer bugs, and it will also be easier to make changes to our software in the short, medium, and long term.”

It’s not about syntax, it’s about leverage.

hth

2 Likes

Thanks for the reply. Fewer lines of code has advantages to debugging, etc, but what about

  • concerns about operating efficiency (___ is SO MUCH FASTER without Clojure)
  • getting help (copypasta, Stack Overflow especially; we are talking about code/technical system so not personnel on this thread)
  • ease of utilizing the ecosystem (actually a strength of Clojure, but still possibly an argument from supervisors who would just like to see copy-paste and npm-install and follow the documentation of the thing)

What would convince your interlocutors? Do we know whether they even can be convinced? More often than not these questions are decided by non-technical factors.

3 Likes

Immutability – eliminates a whole class of bugs, which makes concurrency easy as well, which can allow for “faster” code more easily.

As for performance in general, there’s no reason why a Clojure program should be slower than a Java program (unless you’re doing heavy numeric work perhaps?), and on the ClojureScript side I believe there are benchmarks showing Reagent/re-frame is faster than raw React.js? (again, due to immutability allowing for faster delta computation in rendering)

As Dave says tho’, the key issue is really going to be to figure out why these folks are really objecting and what angles would convince them? If they seem to keep moving the goalposts every time you address a specific concern, then they’re probably just determined that “no Clojure” and no amount of arguing is going to convince them, unfortunately.

4 Likes

Great points. I am fortunately dealing with people who, though having a stance, are ready to be convinced

When you are talking about a hosted language, would JS on the browser be counted? The browser is written in c++ and the scripting language in js. Or say Lua in World of Warcraft/Roblox? I’d recommend Lua if that is the case.

Though we’re really talking about Clojure here. For me:

  1. built-in immutable collections,
  2. syntax and macros
  3. repl/live eval.

1 is exclusive to clojure. 2 & 3 are properties of lisp. If people do not buy into immutability, it’ll be hard for them to buy into it at all. Agree with Sean about concurrency being a breeze though I’m super skeptical about reagent benchmarks.

JVM Clojure is pretty special. It makes the JVM easier to use, and because its dynamically generating bytecode, one lives in the JVM as opposed to the IDE. I learnt a lot JVM characteristics purely from working in the repl.

CLJS - I’m not convinced for a lot of reasons. You really need buy in for clojure to be convinced of it. And the biggest problem for ui development is maintainability - is it possible to find the devs to maintain a growing ui with something like > 1000 custom components - which is pretty standard for even small apps.

To be honest… tools for Java, SQL and CSS are extremely mature and if most people on the team are used to those tools, most will stick to what they are good at. Clojure has pretty weak tooling on the front end side and RAD tools are databases are pretty damn good. Why would stakeholders bother if the learning curve for those tools are so much easier and one gets more bang for their buck?

In general though, you’d want to look at how to solve the problem with the simplest/most elegant solution. And if that solution involves Clojure (the syntax helps a lot here), then hurrah. It’s not the technology that’s important to convincing others. It’s the plan.

2 Likes

excellent answer. Several people have mentioned macros, which is funny to me because I write less than one macro per year. But immutability and REPL are major benefits; thanks. Long-lasting projects, maintainability, speed of shipping, and flexibility are major virtues in my work place. The Clojure culture is a big help with several of these, and what you’ve mentioned about immutability and probably repl are others. Thanks for the insight!

If those two things are your top concerns, then you have made your tradeoff decision already. Clojure will not magically have more stackoverflow Q/As than Java or JavaScript. And Clojure will not magically be more easy than the language people have already spent considerable time learning in school, and it won’t magically have more guides and tutorials than JS or Java.

  • concerns about operating efficiency (___ is SO MUCH FASTER without Clojure)

Well this is just not true, so not sure what claim they’d make. You could say mutability is so much faster than immutable, or unchecked math is so much faster than checked math. That would be more representative of the trade-off you’re planning to make, which is trading performance for correctness and safety.

Clojure is a language that makes the default solutions favor correctness, safety, terseness, interactive development, and flexibility over raw performance, ease of getting started and familiarity. It does so up to acceptable performance levels, so that it can still be used in most real world use cases and meat the acceptable performance targets.

If you favor raw performance, ease of getting started, and familiarity over correctness, safety, terseness, interactive development, and flexibility, than use the host default language, otherwise use Clojure.

If you favor a different mix, use a different language tailored to a different mix. For example, Scala would probably be more like: correctness, safety, familiarity and performance. Where it does not provide the terseness, interactive development, and flexibility of Clojure. And similar to Clojure is also not easy to get started with.

Probably the most important feature to me right now is reader conditionals. I started out with a ClojureScript only client-side app which has now grown to be quite massive. I knew that at some stage I would need to develop the server-side component, but what I didn’t realise at the time was how easy it would be to share much of the existing code base between the client and server. I can’t emphasize how much of an amazing time saver this has been, both in development and ongoing maintenance. A good majority of the code base now resides in .cljc files.

One key aspect of this is being able to create the same data structures, over and above the default vector, map and set, on both client and server, and easily being able to serialize between the two and between the server and persistent storage.

Other very useful features not found in Java or JavaScript:

  • polymorphism via multimethods and protocols (sure Java has interfaces which are the next best thing to protocols and +1 to it for that, but all the other benefits Clojure provides far outweigh anything Java gives me)
  • the associative protocol: worth singling out since I used it so often without even thinking about it; I have a lot of deeply nested data structures which contain a mix of vectors, maps and records, all of which are associative; I can easily swap out any one of these for another in most situations and they will just work; I can’t do that in Java, JS or most other languages
  • tree walking: again, something which is incredibly useful and which is incredibly useful and only really possibly due to immutable data structures
  • serialization: this is something which again is much easier due to immutable data structures; I use transit for client-server data transfer and nippy for server-persistent storage, and the whole process is extremely simply; compare this with trying to (de)serialize objects, which is anything but straightforward when you have even a small object graph
4 Likes

I respect people that build (is there a link to the app?) so I hope this doesn’t come across as me being a sourpuss.

What bothers me about ui is just how difficult it is and how quickly it can change. I think it’s much easier to reason about the backend than the frontend and so it’s entirely possible for a single person/small team to do the non visual parts of an app. As much as people sell themselves as being a full stack developer, the client facing team actually needs to compartmentalise in order for an app to really scale.

In terms of comms, I’d recommend json purely because most languages support it. If you are not working exclusively within a clojure team, you will need to provide edn serialisation - which tends to be about 2 to 10 times slower than json depending on language. Even in jvm clojure, json is about 2.5 times faster than edn using jackson. I just don’t think it’s worth the extra steps. Do you really need sets?

Tree walking and serialisation are pretty trivial with js objects, lua tables and python dicts. conversion to json is pretty much built in in most languages or in the case of lua, a C-based library that everyone uses.

Code sharing is definitely important but the browser runtime (promises) and the jvm runtime (threads) are really different so I think that vision is way overrated. If that’s a priority, why don’t people just use cljs for the entire stack? I actually think reader conditionals are really ugly and makes the code unreadable. The code ends up looking more like C code rather than clojure.

@Webdev_Tory. Macros are important. You might not write them now, but you’re definitely going using ones other people have written.

1 Like

@Webdev_Tory I hope you come back after the dust settles and let us know how it went.

1 Like

That’s a really good question! I’d say the biggest advantage is the quality of the language design. JS and TS are designed by a committee reacting to the constantly moving goalposts of browsers, and dev demands, bugs, and personal grievances. Clojure(Script) was designed by looking at those languages, evaluating what works, removing what doesn’t, and adding many improvements. Clojure by contrast is a very principled language, it leverages a lot of practical functional programming principles but without being dogmatic. The goal was to make development simpler.

A huge boon with ClojureScript is the live repl experience. My biggest gripe with TypeScript after working with it for the past 3 years, is that it requires me to pretend I know everything about the world before the program can even run. This means a lot of npm packages are encoded with assumptions about the world that may or may not be true, and if those prove incorrect, you have the same kind of issues as if it were written in vanilla JS except you don’t have as great of tools to dive in and find out where\why it broke. With the REPL integration, I can test each line of code against my running app.

For example, despite the backend being written in Go and the frontend in TypeScript, a backend engineer and I wasted a day trying to find out why a cookie sent from the backend was not verifiable on the frontend. Both systems had static types, but they did not prevent this bug. To help find the issue, I opened up a node-babashka repl against a new node instance and started manually testing verifying the cookie signature directly with node’s crypto.verify. I wrote regular cljs code, then pressed Ctrl-C Ctrl-C in my editor to send it to the node repl to eval and show the result.

Within a few minutes I had tested a dozen or so scenarios and proved it wasn’t directly a frontend issue. That got us looking at the browser responses and then we found out that a cookie set on the root domain was overwriting the one on the subdomain which was the one we were expecting.

Having a live repl to work with means you have the freedom to explore a problem, its realities, alternative solutions, data, and various possibilities without having to pretend I know everything about the system before it even compiles. It’s the freedom to move fast and be wrong without it being an obstacle to progress.

The second important distinction to me, also part of the language design, is how nicely commonly used pieces of code fit together.

(defn sum
  [cart-map]
  (->> cart-map
       (:items)
       (filter #(not= (:type %) :subtotal))
       (reduce #(+ (:total % 0)) 0)))

Clojure uses polymorphism to enhance safety while still making it easy to work with. The equivalent TS would be something like:

type Item = { 
  type: "item" | "subtotal" | "label"
  total?: number
}
type Cart = { items: Item[] }
function isItem(item: Item): item['type'] is "item" {
  return item.type === "item"
} 
function sum(cart: Cart) {
  return cart.items
    .filter(isItem)
    .reduce((total, item) => {
      if (item.total) return total + item.total
      return total
    }, 0)
}

It’s just so much ceremony for such a trivial concept that still doesn’t handle any surprises well. If the cart-map doesn’t have items, the function will still return 0, or if item doesn’t have a :total field, it will still do the correct behavior at runtime. If something at runtime calls the TS function sum with an object that doesn’t have cart.items, it will just throw an error and crash.

I do get the argument that the polymorhpism in Clojure can be a footgun, but at the same time, it gives me the developer the option to determine the importance and prioritize which problems I’m focusing my time on. In this case, having my UI display 0 temporarily before state initialized or in some edge case is more preferred than totally crashing the app. If I am worried about incorrect values, I can leverage tools like spec to enforce function contracts during dev with continuous checks, and at runtime by coercing and testing values as well.

6 Likes

I feel like that’s a pretty dogmatic statement in itself. No language is perfect - not even Clojure. In your anecdote, even though Clojure helped saved a day of debugging, your team still went with Go and TS. I’d be interested in why this is the case.

The great thing about javascript is that people shit on it all the time and yet, it’s everywhere. There is a book “Javascript - The Good Parts”. I find the majority of Clojure language decisions to be really good. For me, Clojure 1.3 is as good as it gets. There have been many things added after 1.3 that I find not so good - cljc being one of them. cljc was a reinterpretation of Kevin Lynaugh’s cljx. This is one of many cases of the language eating into its developer base - as I had pointed out before - and results in a great deal of frustration for the original dev. Also, at the time, there were maybe 3 to 4 projects for converting clj into cljs which I found more elegant. cljc killed those too.

I find the JS proposal process The TC39 Process pretty damn good to be honest. It’s definitely chaotic but it allows a lot of people to evolve an extremely complex ecosystem all at the same time. Whilst the JS approval process take longer to have more features because of babel and web pack plugins, people can opt-in to them and results in a great ecosystem because ideas are left alone to compete.

It’s funny to me that a lot of the original innovators who were attracted to the language because of the lack of bureaucracy also left because of the lack of bureaucracy when they hit that ceiling.


@eccentric_j - Anyways. The repl experience is awesome. I don’t know why more languages don’t do the repl experience because it can be part of the tooling. It’s not something exclusive to clojure, or really anything to do with the language. You could have opened up the node repl and tested those scenarios too. It may be a little bit slower and you won’t get emacs integration but it’s doable.


A lot has been said about how cljs makes js more sensible to use. I agree with all of those points. Not a lot has been said about the downsides. One is the google closure dependency and the second is that it also has to play catch up to the hosted language (JS). Playing catch up is terrible for library developers - I don’t ever want to write a cljs library ever again given my previous experience.

This downside is also there in JVM Clojure where there is so much innovation happening in Java that a lot of the really cool and innovative stuff clojure provided when it first came out is now out of the box. Having said that, I’m still using the language because it’s a fantastic tool. It gets the job I need done. Like I said before - there’s a lot of Good Parts. But it’s worthwhile talking about Clojure’s Bad Parts. My anecdotes might not sell but hopefully it can save some people a day or two.

This is such an important but difficult question. It makes me wonder why choose a language over any other.

Clojure has a few advantages over Java. And ClojureScript has some over Java.

  • The repl
  • Immutability by default
  • Concurrency (on JVM)
  • The huge and high quality standard library

ClojureScript used to have a big advantage over JavaScript, but with async/await, JavaScript closed the gap enough. Core.async is still better, but not better enough to make the difference.

Then there’s the style. Clojure encourages a more functional style, which I think is an advantage. Even with discipline, it’s hard to do in Java and JavaScript.

You could also list Java and JavaScript’s advantages over the Clojure languages. I won’t. But it’s a useful exercise.

I also want you to think about your motivations for using Clojure. Is it personal, because you like it? Or will it help the product?

I think the main question is about who you want to hire. Do you want to hire Java programmers or Clojure programmers? Do you want JavaScript programmers or Clojure programmers?

Java/Script programmers have many reasons to learn those languages, but a driving one is the availability of jobs. That means they may be learning it not for interest in the language, but to make a good living. That’s fine. Not problem with that. But Clojure is not a language with a reputation for having lots of jobs .
Clojure programmers, in general, are learning it for some other reason, often an interest in the ideas. I think this makes a big difference. Hiring a JS programmer is easy. Hiring a good one is harder. More competition from other jobs and more applications to wade through. You should really think about the kind of application you are building and whether you can build it with menial programmers.

Clojure’s features are not easy to use. Learning to use a repl effectively takes time. Learning concurrency takes time. Learning immutability is not so hard, but I’ve seen people make messes. These are real costs.

In the end, I believe the quality of the programmers and their practices make the difference, not the language. I would rather hire 10 Clojure programmers to work in Java than 10 Java programmers to work in Clojure.

4 Likes

I’d add the conciseness and data-oriented approach that is encouraged. Having a fifth or so of the amount of equivalent java code is a huge advantage, in my mind.

I think they go hand-in-hand, to some extent. I enjoy writing Clojure more than any other language I’ve used (and I’ve used many). It aligns well with how I think, and in turn this makes me significantly more productive (I do a bit of C# at work, and the difference is noticeable), so I guess the fact that I like it also helps the product…

Yeah, I like it, too. Thanks for bringing that up. The important question is: How important is it to the people you are trying to convince? The main argument is that # of bugs is proportional to # of lines of code, regardless of language. That has to be balanced against the other concerns.

One could make that argument about any language. The conclusion is that you should hire people who like the language you’re going to use (or use the language the people you have hired like).

1 Like

It is objectively slower since it trades that for correctness. However, Clojure is fast enough and gives you the ability to reason correctly about the Big-O performance, which is mostly what matters these days. In theory, Clojure is easier to use for a wider range of programmer skill when it comes to concurrency and a few other areas.

This is a big concern. I tend to write things myself (who wants to reuse somebody’s ugly, buggy React UI component?). But I’ve talked with managers who have told me something to the effect of, “Listen, my team is okay but they’re not superstars. They can’t build their own drop down widget. They need to find the npm project with the most stars and use that. They can glue stuff together but building it themselves? I don’t think so.”

They can copy-paste an error message into google and find the stack overflow answer. Can they debug down through two stacks (Clojure + JVM)?

If you’re on this forum, chances are you’re above average. Half of the programmers out there are below average. And they’re working on below average teams, building below average software. Managers need to think about those teams.

And it might be in the future. They might be thinking “crackshot team today to get it off the ground, then hand it off in 3 years to humdrum programmers to maintain.”

Clojure is a great language, and the hosted design is a great benefit. It’s a really sharp tool that can do great things in the right hands. But it does come at a cost. Your best bet is understanding the needs of the business and dialing in the argument based on that. But it might turn out they shouldn’t use Clojure :slight_smile:

6 Likes

I don’t think Clojure is a perfect language, and I am not suggesting that it is, but it is a thoughtfully designed one that I really enjoy working with every chance I get.

EDIT: While my statement may or may not be dogmatic, I would say Clojure’s design is not. It doesn’t care if you call out to side-effectful Java\JS API. I don’t have to wrap IO operations in an IO monad to maintain purity. It makes a functional approach a default, but it takes into account that real world is unpredictable and has escape hatches to deal with those situations as needed. For example, I could write a cljs app that just operates on vanilla js objects and arrays, that’s perfectly valid cljs.

The stack existed before I joined the team. Plus I don’t think forcing a team to use any language would work well no matter how good the language is. From what I understand, if the backend team were to start over, they would go with Ruby instead of Go. Teams should use what they feel they can do their best with. If I can find more Clojurists that can tolerate TS to hire, then maybe we could incorporate it more. That said, I was able to leverage ClojureScript for a tool on my employer’s public site, so I’ve got my garden to tend to when not working in the weeds :laughing:

I find it to be a mess personally as it continues to sprawl in nearly every direction all at once, I’m especially disheartened that people keep rejecting adding in features like a pipeline operator and I’m not happy with how much OOP it has adopted. Additionally, most proposals seem like they’re compensating for a lack of macro capabilities.

That’s another point I probably should have mentioned for the OP: macros are really important. It lets users define new syntax and evolve the language on a project-by-project basis. I love working with libraries like GitHub - funcool/promesa: A promise library & concurrency toolkit for Clojure and ClojureScript. or GitHub - athos/kitchen-async: A Promise library for ClojureScript, or a poor man's core.async for promises. Regular users can create new macro libraries, and people can opt-in to them.

That’s what bothers me a lot about the TC39 process, is that a very formal proposal has to be created first, then maybe a tooling package like babel or TS have to implement it before it can even be test driven. With a macro system, anyone can create a macro library and the good ones will organically get passed around and really good ones can sometimes make their way into core.

That’s not quite true. It’s not unique to Clojure, but lisp languages are much better at it. If I want to redefine a const or a function, I can’t do that. I have to restart the shell or rerun the script.

> const x = 5
undefined
> const x = 7
Uncaught SyntaxError: Identifier 'x' has already been declared
(def x 5)
(def x 7)
x
;; => 7

The browser REPL got better about that, as earlier it would also throw that kind of error. Still though, that lisp binding system offers a lot more precision for how to change an application at runtime that JS doesn’t have the facilities for. Hot Module Replacement is close, but it’s not built into the language and is often not very reliable. Modern tools like Remix seemed to have dropped support in favor of just faster compile times and full page reloads. Additionally, you can use the clojurescript repl to interact with TUI applications running in a terminal, and it can also interact with electron apps at runtime. It’s possible to get HMR working for electron apps, but it comes out of the box with cljs.

Another use case came up the other day, I wanted to create a virtual midi interface, then use the clojure script repl to play notes. The repl experience made it much easier as I never had to restart the virtual midi device once I got it working, which was really important because I had 2 other programs reading from it, and was able to keep redefining my abstractions as I improved my understanding by rapidly trying to figure out how to play notes.

While tool dependencies like Google Closure are definitely showing their age, projects like GitHub - babashka/nbb: Scripting in Clojure on Node.js using SCI have made writing node projects much simpler without requiring any compilation step or Google Closure dependency. Similarly, projects like GitHub - squint-cljs/squint: Light-weight ClojureScript dialect and GitHub - squint-cljs/cherry: Experimental ClojureScript to ES6 module compiler are giving us more options to use ClojureScript without Google Closure, targeting modern JS. For very simple web projects, I’ll even use GitHub - babashka/scittle: Execute Clojure(Script) directly from browser script tags via SCI.

I agree, that Clojure(Script) is not a perfect language, but I can’t say its rough edges have really hindered me compared to the day-to-day limitations I experience with JS\TS.

6 Likes

I agree with you for the most part (and that music app is super cool). The only thing I want to point out is that you have a really broad definition of cljs.

I’ll keep coming back to Lua because it’s by far my favourite algol language. It works because it has a highly restrictive syntax and super simple. I can write a simple lua function without dependencies and it’ll work on about 100 different runtimes. There are quirks with lua like 1-based indexing but to me those things are irrelevant, lua does everything in the simplest way possible and it does them right. That means lua/luajit is extremely easy to embed and there is consistency in the code. When you’re writing lua, you have a guaranteed contract for your syntax. The same thing can be said for js minus all the proposals. If you stick to javascript 1.0, you’re probably going to be fine.

It’s not like that at all with cljs/clje/cljr/sci/jank/cherry or whatever flavour of clj some lone ninja has come up with. There is no standard and no one seems to care that there is no standard. I don’t want to deal with these inconsistencies or to learn about new ways code can f up because of some feature incompatibility. Feature incompatibilities will happen a lot more on a hosted platform because you’re dealing with two runtimes and not one.

I just want my code to work. If you want to fiddle around, great. I’ve done it before and I don’t want to anymore.