Hacker News new | past | comments | ask | show | jobs | submit login
ClojureScript 1.10.866 Release (clojurescript.org)
152 points by tosh on May 25, 2021 | hide | past | favorite | 42 comments



Mostly a maintenance release, but the and/or compiler pass thing is somewhat neat. First, it might seem that such an optimization would not be that useful, but actually it's one of the most critical ones - we want to write the persistent data structure code in a language idiomatic way yet know that performance sensitive JavaScript code will be generated.

Here's the problem.

In Clojure `and` and `or` are just macros that expand to `let` + `if`, simple and elegant:

    (defmacro and
      ([] true)
      ([x] x)
      ([x & next]
       `(let [and# ~x]
          (if and# (and ~@next) and#))))
... except when you target JavaScript - which is not an expression oriented language. So `let` will be wrapped in an immediately invoked function expression (IIFE). Google Closure does elide these when it can, but it will give up after only a few levels of nesting. For an IIFE to appear in the middle of a conditional is a performance killer.

Another complication is that it's not safe to just use `&&` and `||` blindly because of `0` and the empty string and the other cases which are not false-y in Clojure(Script). Checks for JavaScript false-y values are a performance killer.

So years ago we implemented a simple form of type inference which annotates the AST with type information as a compiler pass on every node when possible. As long we know the JavaScript `if` will receive a boolean value we can elide the JS false-y value check.

But this is also exactly the information we need to safely run a following pass that looks for the above and/or syntactical patterns and optimize it. As long as each nested `if` is guaranteed to return a boolean, we can remove the local and use `&&` or `||` instead.

The and/or optimization pass ended up being ~120 lines of code with no actual dependencies on anything else in the ClojureScript analyzer or compiler because the ClojureScript AST is just plain EDN - https://github.com/clojure/clojurescript/blob/master/src/mai...

Happy to answer any further questions about this!

UPDATE: Also ClojureScript turned 10 years old this month :)


Thank you and all contributern for your amazing work on clojurescript.


Happy birthday ClojureScript and congrats & thanks you for all your high quality work!


I haven't used clojure/clojurescript since more than 3-4 years. How far the performance of CLJS [:optimization :advanced] has come close to writing core js code ?

I still remember good old memories of using CLJS (around 2017-18), we were rewriting the android app for one of our clients ethanol selling company in Africa/kenya (which took roughly more than 6 months to write, test and deliver, still was crappy) in cordova/phonegap hybrid mobile app using CLJS, reagent and materialize.css. We did it in only 2 and half weeks with extensive test coverage. In my team only i knew clojure and played little bit with cljs and reagent while creating a to-do list app.

We never used to test the app after creating an android build, we were relying all the time chrome dev tools by hosting it as SPA app, writing test cases and fixing css & functionality issues if any. We were so confident that it will work as expected after exporting to .apk after simple codova/phonegap build process. More than 95% of the time, it always did.

I had to teach my other 2 teammates the basics of cljs and whole project development handling. It was the most challenging as well as productive 2 and half weeks of entire career. I never could imagine we could it develop it so fast with completely new language onboard. I had to play many tricks to allure my upper management to invest in a lisp dialect. I promised them to rewrite in under 6 weeks and ship to production. From 6 weeks commitment to 2 and half, we were on cloud 9. We also worked on weekends almost full time during that release.

Unfortunately due to some restrictions of supporting older android versions than 4.4 in kenya region we had to abandon the idea of hybrid app using cordova/phonegap after 3 months as more clients demanded.


I don’t have exact numbers but I recall some stats from years ago where it was 10-30% slower.

TLDR Almost identical performance.


Why is a script compiled to another script up to 30% slower than its target?


As with all benchmarks, a lot of caveats apply. ClojureScript gives you a lot of power, and a lot of tools to easily reason about your code in many asynchronous scenarios. I believe that you if you wrote JavaScript that gives you the same amount of power and immutability guarantees that ClojureScript provides, it will also be slower than a simplistic benchmark that mutates everything all over the place.


Everything is something compiled to something else and eventually machine code, but some do run faster/slower than others.

Usually it's mostly about that generated code is more bloated to manually crafted code. Compare generated assembly with manually crafted assembly and you'll see what I mean.


The irony is that for modern processors, generated assembly tends to be faster, because the required optimization is so specific, manually crafting it can almost never match an optimizing compiler.

But I get the spirit of what you said.


> The irony is that for modern processors, generated assembly tends to be faster, because the required optimization is so specific, manually crafting it can almost never match an optimizing compiler.

That's interesting, haven't heard of that before. You happen to know of any comparisons that show the difference? Or some other resource on the subject.

> But I get the spirit of what you said.

Thanks for not being overtly anal about it, which tends to be the norm :)


The language wasn't mentioned but often the alternative to assembler is C. C compilers beating hand written assembly comes up in pretty much every discussion of the subject. You can still beat the compiler in many cases if you iterate and analyze enough, but that won't be the case in the first-pass code you'd write.

There are some exceptions in areas where compilers have weaknesses, like SIMD code.

The thread context is ClojureScript and JavaScript, I think people don't compare JSVM JIT code to hand written assembly that often (tip: node --print-opt-code --print-opt-code-filter=myfunction).


Clojure's immutable/lazy sequence abstraction involves some closures, it does not compile directly to e.g. array looping. I believe the JS VM also is tuned for imperative programming. I'm not sure how much it matters given most people are paying for a virtual dom & diffing, over querying, over-rendering, etc.


As Clojure programmer since Clojure 1.1 I am so impressed with ClojureScript. I never had much use for ClojureScript but I always follow what going on. swannodette is the peak language maintenancer and helped push the initial idea further then I thought possible.

I remember the original talk by Rick and so much work was left. Congrats to the team.

P.S: Is there a real good ClojureScript library to work with maps and map data? Or a demo of how to do that kind of thing?


If you're looking to embed maps in a web page I've had good experiences with using both Mapbox and Google Maps' js libraries directly from cljs. On the geospatial side JSTS (https://github.com/bjornharrtell/jsts) has been super helpful.

Like you, I was looking for a cljs wrapper for these things, but ended up not needing them in the end. The js interop ended up being pretty straightforward.

Happy to chat further, email in bio.


> P.S: Is there a real good ClojureScript library to work with maps and map data? Or a demo of how to do that kind of thing?

It's a bit ambiguous what you're asking for. I'm guessing you're asking for geographic maps data, and not maps as in Clojure Maps (https://clojure.org/guides/learn/hashed_colls#_maps)?


But swannodette also produced om, which became abandonware, which became a lot of headache for a company I used to work at. At least we didn't eagerly adopt om.next.


> which became abandonware

I always understood abandonware to be commercial software that no longer is supported by the company who created it. I have a feeling open source can't be abandonware, as support was never guaranteed in the first place. Maybe a nicer way to put it is that Om was "completed" :)


To be fair there were definitely still bugs in Om as well as om.next (and both were marked as being experimental and of alpha quality) - however Fulcro came along and more less took the ideas and took them further and far as I know is actively maintained. I don't use it - but I suspect there was/is probably some kind of migration path.


Well om might indeed be complete, but it in fact requires React under the hood, and there's always some work needed to get the completed om to work with new React releases.


om was from the beginning an experiment and this was very clearly stated.


How is Google's Closure Compiler these days?

The last time I tried CLojurescript about 5 years ago it was the source of many a cryptic error, non-existent documentation, and no connection to any of the Javascript outside of Google-flavor javascript (such as not understanding either `require` or the then-new `import`s)

It looks like the documentation is as non-existent today, but it would be interesting to know how it fares in modern world.


I wonder if there's any interest from the ClojureScript community in pairing it with Deno for an easy-to-deploy Clojure alternative.


Guess I'm a part of the ClojureScript community, what exactly would Deno bring me? Seems Deno is just NodeJS + TypeScript so little to gain there. Dependency management is even worse in Deno than NodeJS as well, something I didn't think I'd see in my lifetime. It hardly gets easier than deploying JAR files either, even for someone like me with 0% Java experience.


Well, you'd get single binary deployment with no other requirements on the server using deno compile [1]. I understand there is an alternative for that with GraalVM, but Deno feels more lightweight.

[1] https://deno.land/manual/tools/compiler


> no other requirements on the server

Seems not quite right, the binary does have some dependencies at least. It's no Java, I give you that.

    $ deno compile https://deno.land/std/examples/welcome.ts
    Check https://deno.land/std/examples/welcome.ts
    Bundle https://deno.land/std/examples/welcome.ts
    Compile https://deno.land/std/examples/welcome.ts
    Emit welcome
    
    $ ldd welcome
     linux-vdso.so.1 (0x00007ffefc6de000)
     libdl.so.2 => /usr/lib/libdl.so.2 (0x00007fc154c63000)
     libgcc_s.so.1 => /usr/lib/libgcc_s.so.1 (0x00007fc154c48000)
     librt.so.1 => /usr/lib/librt.so.1 (0x00007fc154c3d000)
     libpthread.so.0 => /usr/lib/libpthread.so.0 (0x00007fc154c1c000)
     libm.so.6 => /usr/lib/libm.so.6 (0x00007fc154ad8000)
     libc.so.6 => /usr/lib/libc.so.6 (0x00007fc15490c000)
     /lib64/ld-linux-x86-64.so.2 => /usr/lib64/ld-linux-x86-64.so.2 (0x00007fc1586b2000)
    
The question is, is it worth using Deno just because it can produce binaries? For me, it's not. If I want that I'd go for Golang or Rust, but in general I just want it to be simple to program and maintain, so I always go for Clojure and ClojureScript as both are miles ahead any other programming language I've tried. TypeScript would probably be in the bottom of languages to use :)


Random question: Can ClojureScript be compiled in the browser? I only looked at it when it first came out and at that time the ClojureScript compiler only ran via the JVM.


There is a new thing, and it's kind of a toy [0]. But it lets you load clojurescript from a script tag, so there is practically no setup needed!!

[0] - https://borkdude.github.io/sci-script-tag/index.html


Worth keeping in mind that that's Small Clojure Interpreter (SCI) which while very cool, isn't ClojureScript. Seems to be running Reagent perfectly fine though, so not sure what the exact differences are, AFAIK, SCI is a subset of Clojure.


Wow sci can run reagent out of the box? That’s not a small feat, excellent work @borkdude!



ClojureScript is self-hosted and thus can be compiled in a browser (see https://clojurescript.io/ for an example).

For most serious usage and projects you will want to use the JVM though.


I think ClojureScript has been able to compile itself since ~2015 if I remember correctly. There is some helper libraries that makes the experience nicer nowadays too, tools like KLIPSE (https://github.com/viebel/klipse)

Here are some starting points:

- https://clojurescript.org/guides/self-hosting

- https://practicalli.github.io/clojurescript/quickstart/self-...

- https://blog.klipse.tech/clojurescript/2016/04/04/self-host-...


Yes.

ClojureScript is self-hosting, and there are helper tools out there like klipse that make it easier to set up.

A more lightweight option is borkdude's SCI (Small Clojure Interpreter) running inside HTML script tags:

https://github.com/borkdude/sci-script-tag


When I work on ClojureScript app I am actually modifying application that is running in the browser, without ever reloading the page. That would not be possible if it could not compile or at least interpret it in the browser.


Hot reload doesn't imply compilation in the browser. For all you know lein could be recompiling via the JVM and sending updated JS files to the browser.


That sounds pretty cool, is there a guide to setting that up?


Sure. I use fighwheel. https://figwheel.org/

Actually, when I develop with clojure, I typically have both backend and frontend running at the same time and I am concurrently modifying both applications without restarting them. This is way, way more convenient than having to restart application and then to bring it to a state to run your test.

This is youtube video from which I learned about fighwheel: https://www.youtube.com/watch?v=j-kj2qwJa_E

Have fun!


(I just don't understand this bracket)


its(not, that, hard)


but why clojure have this, reason? i am not criticizing it but i just want to know why clojure take this decision.


The syntax becomes small, predictable and, crucially, is in itself a data structure. This means that everything composes in the same way, without special cases, and it lends itself well to meta programming when necessary.


isnt it why clojure is called clojure I guess. That the reader can take closure look of there code.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: