Does clojurescript use or will use new ES6 features (map, set, etc)?

Hello,

Clojure(script) has several advantages over javascript in terms of data structures. However javascript has implemented several new data structures that may be very good suited as a base for clojurescript structures.
Of course browser support varies, but almost all modern javascript build/compile tools allows you to specify what the generated code should target (ej node8, chrome >49, etc).

This will be very beneficial specially for node environments, where the set of features is perfectly controlled.

No. The JS collections are all mutable. CLJS collections are all immutable.

5 Likes

Only use mutable data structures for performance reasons. Before switching to them, make sure you tried transients first, and if that is still too slow for your use case, than you can switch to mutable data structures. That’s the general best practice.

Now to answer your question. Yes, ClojureScript supports everything the target JavaScript environment supports that Google Closure compiler supports. And Google Closure compiler supports ES6 as an input language, and automatically polyfills everything to ES5 or ES3.

This means you can use ES6 features from ClojureScript, and they will also work in environments that only support ES5.

Only the features that can not be polyfilled are not supported. I’m not sure if there are any, but Map, Set, WeakMap and WeakSet are supported.

(def es6-map (js/Map.))
(.set es6-map "a" 1)
(.get es6-map "a")

Have a look here: https://clojurescript.org/reference/compiler-options#language-in-and-language-out

You can choose what language to emit from ClojureScript, and what language to get back out from Google Closure. You can also activate polyfill if you want: https://clojurescript.org/reference/compiler-options#rewrite-polyfills

Maybe we should extend this question, besides data structure, will ClojureScript benefit from ES6, including those features:

https://codetower.github.io/es6-features/

I just blogged about the features of ClojureScript here: https://www.rubberducking.com/2018/04/overview-of-clojurescript-110-features.html

It showcases most of the features of ES6, obviously, they have different syntax and semantics, but they’re pretty much all there.

1 Like

Sorry, it’s obvious that I haven’t been clear.

What I wanted to say is: does ClojureScript use ES6 features to implement it’s data structures and control flows? And if not will it do at some point? Could it improve if doing so? Basically what @jiyinyiyong said.

I didn’t meant if I can use ES6 Co structures inside ClojureScript, I’m not interested for now on that.

I’m a little confused now what you’re asking.

ClojureScript is implemented in ClojureScript and Clojure. So it doesn’t use any JavaScript for its implementation, as far as I know.

I doubt it ever will move away from being implemented in itself, since that’s the golden graal for programming languages.

Moving to a JavaScript, even ES6 implementation would probably be detrimental, for the same reasons why you would prefer to choose ClojureScript over JavaScript. Productivity, readability and lower defect rate. So I doubt it would improve if it was implemented in ES6 instead.

If instead you meant, are there features ClojureScript is not able to support because of the target JavaScript VM’s lack of support, that an ES6 VM would add support for… Then yes. I don’t think there’s many limitations currently, but I can think of implicit tail call optimization being one of them. I can’t think of any other. Maybe Threads, but no ECMA proposal that I know are adding threads.

Otherwise ClojureScript is pretty much on par with Clojure. So I don’t think any new feature is needed from the JavaScript VMs.

No. ClojureScript emits ES3 JavaScript. The benefits of using ES6 features are small and none of the syntactic features would change anything. Only WeakMap/WeakSet and maybe Proxy could be useful. Everything else we can already do (or use directly).

The CLJS datastructures do not benefit by JS datastructures in any way. For the most part the CLJS datastructures however implement the JS datastructure “read-only” interface where applicable. So if some JS code expects a Map you can pass it a CLJS Map since that supports .get, .has, .forEach and a few others.

Fun fact: There is an ancient React issue to use the Map interface for props which would make CLJS interop easier given that we wouldn’t need to convert props. That never went anywhere though. If anything it is the JS world that would need to adopt more ES6 features, not CLJS.

3 Likes

I’m a little confused now. Why is it that I can use Promise, WeakMap, unicode regexes, WeakSet, Proxy, Reflection etc. from ClojureScript 1.10? If it emitted ES3, shouldn’t I not be allowed to use these?

These work for both browser target, and node target.

(def wm (js/WeakMap.))
(def o #js{:a 10})
(.set wm o 100)
(.get wm o)
;;=> 100

(js/Number.isNaN ##NaN)
;;=> true

(let [obj #js{:a 1}]
  (js/Object.defineProperty obj "b" #js{:value 2})
  (set! (.-c obj) 3)
  (js/Reflect.ownKeys obj))
;;=> #js ["a" "b" "c"]

(let [target #js{:foo "Welcome, Foo"}
      proxy (js/Proxy. target
                       #js{:get
                           (fn get [receiver name]
                             (str "Hello " name))})]
  (.-foo proxy))
;;=> "Hello foo"

I was under the impression these were ES6 only features. And I thought Google Closure polyfills them for the browser target, and leaves them untouched for the node target.

What am I missing in the equation? And what is this https://clojurescript.org/reference/compiler-options#language-in-and-language-out compiler option for then?

I meant that the Compiler generates ES3 JavaScript Syntax. It does not in any way alter the native interop you do. If you use (js/WeakMap.) that is just new WeakMap() for CLJS. It is just a class to CLJS doesn’t matter where it comes from.

The ES6 rewriting that possibly happens is all done by the Closure Compiler and controlled by the :language-out setting mostly.

I guess I’m having a hard time conceptualizing the consequences of this in terms of ClojureScript.

What it means is as if you were coding in ES6, but you by convention limited yourself to only use the syntax of ES3.

So you could use all the new ES standard library features, such as WeakMap, Proxy, the new unicode regex engine, etc.

But you couldn’t use the new syntax constructs like generators, shorthand object notation, function keys, arrows, block scopes, let, const, etc.

But, because you are using ClojureScript syntax anyways, none of this is relevant.

I guess the only place where this could matter is interop. I am not aware of any interop limitations between ClojureScript and ES6+ though.

So effectively, this doesn’t limit ClojureScript in any way, and as far as I know ClojureScript is fully compatible with all es-next features. Which makes me feel like it’s fare to say that ClojureScript supports and can make use of all es-next features.

I don’t understand why is been so hard to make myself being clear.
Some of you understood the point, at the same time that you misunderstood it. I say this because some people have expressed some branching on their understanding of my question.

I’ll try to explain it by parts to see if I can make my question clearer:

  • ClojureScript is implemented on itself, which is cool, but at the end of the day what gets executed on the browser and on node is just plain javascript
  • Because what the CLJS compiler spits out is just javascript, the features needs to be implemented one way or another on the host language, which is javascript. So at the end of the day, a record is nothing but a javascript class and a map is just a javascript object.

With those first tow points in mind, some of the advanced features CLJS has should be implemented in javascript making use of what javascript provides. There are some features that has been added to the language recently which a) has better performance or b) has better features or both.
Some simple examples that I can think of (and some people already did) are sets, maps, weakSets, weakMaps, the new regex features, proxies which are awesome and that stuff.
Let’s say for example that a clojurescript set uses a js array under the hood, and each time you try to add something to the set it has to check if the element already exists on the set. Making such check on a JS array has bad performance, and clojurescript may have to do some obscure hacks. But if it could just use a normal JS set then things would be much more nicer and efficient.
In any case I tried to ask if I can use ES6 features inside ClojureScript, I took that for granted because the cool interop.

Someone said that ClojureScript compiles down to ES3, and someone else have said that you can specify the target JS version. I’m also interested o knowing if this is possible and how, along with my previous question.

Hope it is clearer now.

1 Like

I do think people in this thread understand you. It isn’t a good idea for the reasons already stated.

Hey, it makes more sense now, sorry I couldn’t understand the first time around.

So, I’m not the most expert, so a maintainer of ClojureScript would know better. But this is my understanding.

The ClojureScript’s compiler only requirement is to emit Google Closure compatible JavaScript.

So in most cases, that means ES3, but it doesn’t have to be strictly, since Google Closure support has extended beyond that, and can take ES5 as well as some ES6 compatible JavaScript code as an input, and output ES3 compatible code from it.

ClojureScript does want to make it that the end JavaScript, from the Source -> ClojureScript -> Google Closure -> Output JavaScript pipeline, if specified by the user, be ES3 compatible.

Thus it will not venture in territories which would break this requirement. That said, I don’t believe that currently any trade offs are made due to this, since Google Closure is awesome, and has kept pace with the JavaScript world.

You can see some of this in the source code: https://github.com/clojure/clojurescript/commit/b14eda36ca7249983f5c8f05b97e79bb45623a0e

Now, from my understanding, it is actually harder to optimize the more modern es-next variants, which is why VMs, like V8, are still lagging behind. This is also true for the optimizations that Google Closure performs, and why it restricts the input JavaScript to only a subset of its features.

As I understand it also, no new version of ECMA, have added any new primitives, or changed the memory model. So all the new features are higher level, either syntactic, or added higher constructs which might be more optimized, like its new data-structures. By higher level, I mean that no new VM level support was needed.

ClojureScript though, has always made use of highly optimized higher level constructs from the Google Closure library such as StringBuffers, Google Arrays, Google crypto, etc. When it make sense.

In this regard, I’m not sure it needs any of the ES6 ones. You might even wonder who’s are more optimized, the es-next standard ones, or the Google ones. Anyhow, they probably get close in performance.

Finally, could the compiler emit more efficient code. Probably in some places, but I think that’s possible even with keeping to ES3 code, its just about being smart in how it converts the code to JavaScript. I don’t feel there’s anything in es-next that would change that. Only underlying VM changes would, so new primitive types, support for unboxed values, or contiguous memory blocks, or a change in the memory model, etc. None of that has happened to my knowledge since ES3.

You can geek out even more on this by exploring the source. This is the magic for the compiler in terms of what it emits: https://github.com/clojure/clojurescript/blob/3a6e71e4bb01f97c7a86f46bfed003a602ed5ad3/src/main/clojure/cljs/compiler.cljc#L176

This multi-method, for each thing the analyzer returns, as it walks through the AST of the ClojureScript source, this multi-method is called to emit JavaScript, which is mostly emitting a string of JS code to be concatenated all together into the resulting JS output.

This https://github.com/clojure/clojurescript/tree/3a6e71e4bb01f97c7a86f46bfed003a602ed5ad3/src/main/cljs is where the standard ClojureScript library is implemented. If you inspect it, and believe certain things would be faster if they used es6 constructs, like a WeakMap, or a Proxy, etc. I think you could make a patch to the JIRA, if its compatible with Google Closure compiler, I think it would get merged in.

That’s my 2 cents.
David Nolen would obviously know better then poor me :stuck_out_tongue:

3 Likes

This is simply incorrect. A map is definitely not just a javascript object. A persistent map even less so. I’d suggest you read up on some of the information available about the Clojure Data Structures. There are plenty good introductions like this one https://hypirion.com/musings/understanding-persistent-vector-pt-1

3 Likes

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.