Fork me on GitHub
#clojurescript
<
2022-08-17
>
Yehonathan Sharvit10:08:17

A question related to the performance of using a constant map inside a function. Imagine I have a code like this:

(defn foo [x] 
  (let [a {:a 1}]
    (get a x)))
In JVM Clojure, the map {:a 1} is a static member of the class that represents foo. Therefore the map is not created on each function call. While in ClojureScript (at least according to Klipse), the map is created on each function call, like this:
cljs.user.foo = (function cljs$user$foo(x){
  var a = new cljs.core.PersistentArrayMap(null, 1, [new cljs.core.Keyword(null,"a","a",(-2123407586)),(1)], null);
  return cljs.core.get.call(null,a,x);
});
Here is the Klipse http://app.klipse.tech/?cljs_in=(defn%20foo%20%5Bx%5D%20(let%20%5Ba%20%7B%3Aa%201%7D%20b%20%7B%3Ab%20x%7D%5D%20%5Ba%20b%5D)). Is there a performance improvement opportunity here or are the common JS engine smart enough to optimize the code automagically?

dnolen13:08:35

@viebel I think there’s a JIRA ticket around somewhere about doing this

skylize14:08:02

In the meantime, if you have a real need, wouldn't this mildly messy looking closure capture the initial map for you?

(def foo
  ((fn []
     (let [a {:a 1}]
       (fn [x]
         (get a x))))))

(foo :a) ;; => 1

Yehonathan Sharvit14:08:44

But then the code is unreadable!

p-himik15:08:58

I'd instead do

(let [a {:a 1}]
  (defn foo [x]
    (get a x)))

skylize16:08:11

Top level fn defined not at the top level vs anonymous fn inside a self-invoking function. Conceptually, the top level let somehow feels more gross to me. But it is definitely easier to read and understand, and requires building fewer objects. I guess pick your 🤢 poison. Either way, you could always hide the ugly behind a macro.

dnolen13:08:30

something similar is already done for keywords, historically Closure had issues removing top level constructions if they were a bit complex so we didn’t bother

Yehonathan Sharvit15:08:32

You meant Clojure. Right? What is already done with keywords?

dnolen15:08:03

no ClojureScript, we optimize keywords in advanced compilation

zimablue18:08:50

is it safe to use set! in :advanced compiled cljs in the way that one would use intern in clojure, to redefine vars from other namespaces? if not is there a workaround?

Ertugrul Cetin18:08:26

Hi all, I have 2 .CLJC files one is for view, the other one is for common stuff (macros etc.);

(ns panel.common)

(defmacro print-me-in-cljs []
  `~(read-string {:read-cond :preserve} "#?(:cljs (println \"Hey!\"))"))
Also, I tried this one as well;
(defmacro print-me-in-cljs []
  `~(reader-conditional '(:cljs (println "Hey!")) false))
-
(ns panel.views
  (:require-macros
   [panel.common :as common]))

(common/print-me-in-cljs)
The problem is that when I open the web page, I can not see the "Hey" output inside the browser's console. I'm trying to create a macro that expands readers conditionals so in .CLJC files (view ns) I don't have to define #?(:cljs ... :clj ...) (macro handles it for me)

👋 1
zimablue18:08:18

I think that might be impossible because reader conditionals happen BEFORE macroexpansion but not sure

Ertugrul Cetin18:08:17

If that's the case, it's very sad 😞

Alex Miller (Clojure team)19:08:23

reader conditionals happen in the reader, which is before macroexpansion

zimablue19:08:35

it's probably possible to do what you are trying to accomplish, but not the way you're trying to accomplish it, but I would test the order of these things because intuitively/vague memory it can't be done

zimablue19:08:38

fuck yes I knew something

zimablue19:08:52

if you want something to happen in clojurescript but not clojure, it can definitely be done and probably quite flexibly but the reader way isn't good, instead can you use the reader or something else to create a runtime variable to know which environment you're running in and dispatch based on that?

Alex Miller (Clojure team)19:08:29

I think if you search here, you'll find this is a common question (I'm not an expert in this area to tell you what it is)

Ertugrul Cetin19:08:43

@U63D7UXJB Yes, I need something like this - I'll think about it, thanks for the tip!

Alex Miller (Clojure team)19:08:51

I think also, what you actually want is conditional writing (the macro emits code that varies depending on its runtime environment)

zimablue19:08:36

not clear to me what you're really trying to do but that would work for some things at runtime? (def env #?(:cljs :cljs :clj :clj)) then use that? could also work at macro-expansion time I think, but the information might already bein the &env parameter that gets passed to all macros (?) https://clojure.org/reference/macros

Ertugrul Cetin19:08:33

> I think also, what you actually want is conditional writing (the macro emits code that varies depending on its runtime environment) (edited) Yes, I need this I guess

Ertugrul Cetin19:08:48

> not clear to me what you're really trying to do but that would work for some things at runtime?... Let me check this

Ertugrul Cetin19:08:09

@U63D7UXJB this did the trick!

(defmacro print-me-in-cljs []
  `(if '~(:js-globals &env)
     (println "ClojureScript")
     (println "Clojure")))

zimablue19:08:29

Nice! Not sure whether the long term goal is advised for your health but the sooner it's implemented the sooner the damage can be calculated xD

seepel04:08:20

I went hunting for something similar and found some prismatic code here https://github.com/plumatic/schema/blob/master/src/clj/schema/macros.clj#L13-20 though in the end it turned out I didn’t actually need it so never tried…

timothypratley07:08:14

FWIW here's something that might interest you: https://github.com/cgrand/macrovich (and it uses the (:ns &env) trick as well).