Hacker News new | ask | show | jobs
by Naomarik 3177 days ago
Your claims are baseless. If there are any semantic differences between clojure and clojurescript they are so minute that I'm not aware of them. I'm a full stack developer and use both daily. There are no performance issues inherent with using clojurescript either. I say this having made a non trivial UI and pushing loads of data through it and seeing what everyone else has made. Check out precursor app.
1 comments

There is a significant amount of divergence in semantics. I’m not sure why you would try to convince anyone otherwise...the clojurescript developers acknowledge this completely upfront.

https://clojurescript.org/about/differences

In my experience, all of the stated benefits of clojurescript become more and more tenuous as your intensity of use increases. Hello world seems fantastic, todomvc still seems pretty cool, but by the time you’re doing full size apps of meaningful complexity, you really start to see the limitations. In order to get any meaningful performance out of it, I ended up having to rewrite and experiment like crazy...the code looked like nothing I would have written in Clojure, using completely different idioms and data structures. And while the prototyping was fast, in the end it took me far longer to write and then iteratively optimize than it would have to write an equivalently fast version in scalajs or typescript. Maybe your experience is different, but my experience is my experience, and it most definitely is not baseless.

I've never had to make a context switch between clojure and clojurescript. If differences in semantics means I have to change how I think about solving the problem at hand, then that has been pretty much non existent in my experience. Of course with one you have browser APIs and the other is java, but when it comes to building up and transforming datastructures everything I've been doing works the same on both.

This is also why most the time you can just tack on a "c" on the extension and you have something that works on both clojure and clojurescript.

Performance is not an inherent problem of clojurescript. It was just the way you coded you prototyped your app. Your experience is valid but don't blame the problem on the tool when it was the way you solved your problem before you optimized it.

I absolutely mean no disrespect to you but I don't want your comment to dissuade others from trying out what has essentially been a utopia for me. It has allowed me to build non trivial webapps without having any familiarity with functional programming or lisp. The feedback loop is so damn tight that I was productive without knowing much of anything.

But maybe that's okay. If the everyone else is constantly updating their build tools to every flavor of the month and having to learn new versions of javascript, that gives the few of us who are mastering clojure/script a bit of an advantage.

> I've never had to make a context switch between clojure and clojurescript. If differences in semantics means I have to change how I think about solving the problem at hand, then that has been pretty much non existent in my experience. Of course with one you have browser APIs and the other is java, but when it comes to building up and transforming datastructures everything I've been doing works the same on both.

In clj:

> (+ 1 "1")

java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Number

In cljs:

> (+ 1 "1")

"11"

Now I get it...not that big of a deal, right? That's what I would say given a trivial example of something like this. Sure, this problem might not come up very often. In fact, for a team that I worked with, it only ever came up once...it's just that that one time that it came up, it corrupted 3 months worth of data. Would tests have caught this? Of course...but if you are confident in your knowledge of the clojure language, and you assume clojurescript is semantically equivalent, you would expect an exception to pop up in your repl during development, so if it works fine in clojure, why write a test for it in clojurescript?

I understand why the cljs designers would do such a thing...think of the performance implications of having to generate type checks code for every possible arithmetic operation! But it absolutely was a context switch for that team (full stack clj/cljs), and the consequences of the developers not correctly context switching was horrific. Nothing quite burns bridges with entire languages like silent errors do.

I'm fine with people trying clojurescript. FWIW, my personal experience with it was never as bad as that one team's experience. Maybe they'll never come across such a dangerous situation, hopefully they don't. But on the rare occasion that some third party web service dependency decides to serialize BigDecimals as Strings instead of Doubles, they deserve to be warned.

To be fair, in your example, the compiler does warn you:

      WARNING: cljs.core/+, all arguments must be numbers, got [number string] instead. at line 1 
Also, Clojure.spec would enforce it.
I have to agree that it is much more likely the code was not written well or idiomatically if there were performance problems. Still, I'd be very surprised that the problems actually were there.

And I actually test my clojurscript lines that I write in a JVM repl! Semantics between the two languages are virtually identical, I can test out my ideas for the JS target in a Java environment. Perhaps the OP has a different definition of "semantics".

I have two questions.

1) You wrote:

> It’s just too hard to replicate one dynamic language’s semantics in another dynamic language without embedding huge and slow runtimes along with it.

I don't understand this. Clojurescript's "runtime" itself most compiles away in Closure advanced optimizations stage (in fact a compiled Hello World Clojurescript app is only 1 line of JS -- no added runtime at all). I don't understand what about the Clojurescript runtime process should be "huge" and "slow." This contrasts with other languages like Elm that do indeed embed a large runtime with the program, but Clojurescript does not do this.

2) I am genuinely curious what performance problems you had. I'm working on a very large Clojurescript app that processes dozens of real-time incoming data points every second over a websocket and performs real-time analytics and graphical display. If ever there was a test for performance, this would be it. Aside from the rare and simple bottleneck that needed some extra thought (maybe 2 or 3 such moments in 3 years of development on the project), there have been nearly no obstacles relating to runtime speed for the app. The 2 or 3 bottlenecks I mention had minor solutions and would have occurred in any language, and were very easy to resolve. And Clojurescript's fast, immutable data structures have inspired major libraries and even other languages entirely. They are battle-tested and performant. So I'd like to know what specific issues you had, as that would be educational.

> I don't understand this. Clojurescript's "runtime" itself most compiles away in Closure advanced optimizations stage (in fact a compiled Hello World Clojurescript app is only 1 line of JS -- no added runtime at all). I don't understand what about the Clojurescript runtime process should be "huge" and "slow." This contrasts with other languages like Elm that do indeed embed a large runtime with the program, but Clojurescript does not do this.

Clojurescript now does a good job with Closure's advanced optimizations, but it has upper limits to how well it can do. For a full sized app, you can expect clojurescript's runtime code to bloat to 100k to 150k...which isn't the end of the world, but it certainly isn't good either.

While clojurescript and javascript are both dynamically typed, their type systems are semantically different. In order to approximate the semantics of the clojure type system, clojurescript has to build its own dynamic type checking system into a system that is already dynamically type checked. FWIR, the reason that clojure.spec was built into the standard distribution of clojurescript (whereas it is an optional library in clojure) was so that the compiler could perform type erasure on code with type hints...something done by default in static languages like bucklescript, scalajs, and typescript.

So while clojurescript does a good job with dead code elimination specifically, thanks to the closure optimizer, it is still a language that is inherently hard for compilers to otherwise optimize.

> 2) I am genuinely curious what performance problems you had. I'm working on a very large Clojurescript app that processes dozens of real-time incoming data points every second over a websocket and performs real-time analytics and graphical display. If ever there was a test for performance, this would be it. Aside from the rare and simple bottleneck that needed some extra thought (maybe 2 or 3 such moments in 3 years of development on the project), there have been nearly no obstacles relating to runtime speed for the app. The 2 or 3 bottlenecks I mention had minor solutions and would have occurred in any language, and were very easy to resolve.

The internet is full of examples of clojure programmers asking how to optimize code up to the already pitiful levels of performance that you can get out of javascript. Every language has it to some degree, but for some reason, it was always a 10x-100x difference for me. Collections in particular were always really bad for me. And the suggestions I received from more experienced clojurists always ended up pushing me away from what I considered to be idiomatic in clojure. Use defrecord instead of hashmaps, use javascript arrays or vectors instead of seqs, use goog.stringbuffer instead of str, use type hints whenever possible, etc.. Fast clojurescript and "idiomatic" (whatever that is supposed to mean in a lisp) clojure were far enough apart that it might as well have been scheme vs clojure.

> And Clojurescript's fast, immutable data structures have inspired major libraries and even other languages entirely. They are battle-tested and performant.

Clojurescript's immutable data structures are indeed pretty fast for immutable data structures run on a javascript runtime. But they are hardly fast. In fact, here[0] is an example of what I was talking about before: cljs written in an idiomatically clj style that performed terribly when used in cljs. And the answer on how to speed it up was to make it more like javascript!

I'm well aware of the rounds of praise that Om got when they showed how using cljs' native immutable data structures sped up React code over that written in javascript. I'd be careful to extrapolate too far with that. That optimization happened to involve a niche benefit of immutable structures (equality checking can use O(1) reference equality instead of O(n) structural equality) addressing a niche bottleneck in React (equality checking in the virtual dom). Overall, immutable datastructures tend to be about the same speed on most operations, but drastically slower on some operations, and drastically more memory hungry to boot. On servers with knowable processing power and knowably large quantities of jvm memory, that's often a tradeoff worth making for the semantic benefits of immutability. On some random user's computer using whatever javascript VM that they likely chose without consideration for how I would use it, I always felt bad making that tradeoff.

[0] https://stackoverflow.com/questions/21721028/how-to-improve-...