clojurescript 2025-02-19

what is a good way of calling a ClojureScript function by its fully qualified name given you have it on a string, which works both in the browser and nodejs ?

I see you can do stuff like this :

cljs.user=> ((js/eval "cljs.core.map") inc [1 2 3])
(2 3 4)
but then you need to deal with munging and manipulating that string

this seams to be working, but not happy about it

(defn cljs-resolve-fn [fq-fn-name]
  (let [chunks (-> fq-fn-name
                   (str/replace "/" ".")
                   (str/split #"\."))]
    (js/eval (str/join "." (map munge chunks)))))

(BTW, there's cljs.js/js-eval - same thing but more friendly for IDEs). Is the string known only at run time? How are you going to deal with advanced compilation that renames everything?

> Is the string known only at run time? yeah, kind of, so the thing is calling functions via websocket. It used to be a map from names to functions, but now I want that "api" be extended by users, so exploring this route also. > How are you going to deal with advanced compilation that renames everything? it is dev time only, for FlowStorm

Ah. I would most definitely just have an explicit registry of such functions. Extensible by users via some "static" API. No need to be clever when the solution can be dumb. :)

Similar to how e.g. HoneySQL handles things.

yeah, I think that is also the best option

Oh, regarding cljs.js/js-eval - I'm most likely wrong. I stumbled upon that function by accident, and after a more careful ("carefool?") look it seems that the whole ns is for CLJS itself and maybe for self-hosted solutions. Probably not for generic CLJS libs and apps.

👍 1

while we are here, there are other recommended options for "resolving" functions by name, using window on the browser for example, but for some reason in js you can do :

>> window["cljs"]
Object { core: {…}, spec: {…}, repl: {…}, user: {}, pprint: {…} }
but in ClojureScript :
clj -Sdeps '{:deps {org.clojure/clojurescript {:mvn/version "1.11.132"}}}' -M -m cljs.main --repl                
ClojureScript 1.11.132
cljs.user=> (aget js/window "cljs")
Execution error (InternalError) at (<cljs repl>:1).
too much recursion
🤷‍♂️

not going into that rabbit hole

Please don't use aget for that - it's for arrays only. Of course, not a big deal for a quick test, but I'd avoid it even there. You can use unchecked-get instead.

yeah, was just testing that, which I also get with unchecked-get :

clj -Sdeps '{:deps {org.clojure/clojurescript {:mvn/version "1.11.132"}}}' -M -m cljs.main --repl
ClojureScript 1.11.132
cljs.user=>  (unchecked-get js/window "cljs")
Execution error (InternalError) at (:1).
too much recursion

You'll also get it with (.-cljs js/global) (`js/global` is like js/window but more universal). I myself don't know why.

yeah, no idea either, and I don't feel like chasing that now

Don't sweat it - I am. :)

🤣 1

Ah, it's not getting itself that triggers it. It's printing. Case solved.

yeah, a def solves it

InternalError ... one of those error messages...

Oh, and unchecked-get or the window[...] syntax won't help by itself due to munging. I would still go the explicit registry route.

yeah, already going that route, thanks!

👍 1

FWIW there is (js/goog.getObjectByName "cljs.core.map"). the only thing you'd need to take care of is the munging that translates cljs names to JS

👍 1

Is this a bug? In Clojure:

user=> (case '(1) ((1)) :match :default)
:match
In CLJS:
cljs.user=> (case '(1) ((1)) :match :default)
Unexpected error (ClassCastException) macroexpanding cljs.core/case at (:1:1).
class java.lang.Long cannot be cast to class clojure.lang.Named (java.lang.Long is in module java.base of loader 'bootstrap'; clojure.lang.Named is in unnamed module of loader 'app')
It appears that the case clauses are being evaluated under CLJS when they should not be. The doc string says “The test-constants are not evaluated. They must be compile-time literals, and need not be quoted.” Yes, I understand that putting a list of items as a test clause inside a list of test clauses is unorthodox. This came up while working on clojure-test-suite (https://github.com/jank-lang/clojure-test-suite).

Also, this fails:

cljs.user=> (case '[s] ((s)) :match :default)
WARNING: Use of undeclared Var cljs.user/s at line 1 <cljs repl>
Execution error (TypeError) at (<cljs repl>:1).
Cannot read properties of undefined (reading 'call')
Clojure JVM returns :match.

Alex Miller (Clojure team) 2025-02-19T20:26:06.445689Z

this is a well-known difference in CLJS case

Alex Miller (Clojure team) 2025-02-19T20:27:02.773459Z

it's been discussed on here many times (maybe in #cljs-dev)

OK. I’ll go look there.

Alex Miller (Clojure team) 2025-02-19T20:34:38.049619Z

probably no picnic to search for, but in short , CLJS case evals constants, Clojure does not

OK, thanks. The doc string for CLJS case is wrong then. I’ll work it over on #cljs-dev Thanks.

A good place to mention this would be the Differences from Clojure page, https://clojurescript.org/about/differences. Currently it says something about def-with-:const-metadata, but that does not look like the case cited here by @droberts3

Hi @phill, @dnolen also opened a bug on this earlier.

Will type hinting, e.g (.resize ^js map) always mean the code works in advanced compilation? Or does it depend on something else, like how shadow-cljs or figwheel is configured.

Tangential but I’ve recently ran into a issues with type hints „getting lost“ in threading macros, prompting more excessive use of js-interop & similar

What is "js-interop", i'm hearing you say it like it's a library.

to the best of my knowledge ^js is doing the same thing as the js/ prefix, which is to say it prevents closure compiler from renaming the annotated symbol

I have never had any issues just using ^js

ben, are you saying that you could also do (.resize js/map) ?

no, sorry, that was unclear, the js/ prefix is for globals like console.log, i.e. js/console.log. Use ^js . I don't know how figwheel works tbh but shadow respects it uniformly

relevant shadow docs for type hinting, for my notes: https://shadow-cljs.github.io/docs/UsersGuide.html#infer-externs > The ^js typehint will cause the compiler to generate proper externs So it doesn't sound like it's specific to shadow in anyway, which is what i thought and hoped.... and then there is this: > To help deal with Externs the shadow-cljs compiler provides enhanced externs inference, So i'm back to thinking it's specific to shadow.

My experience using shadow-cljs is that unless you get a warning (which you will see in those little HUD popups) about inferring the type of an expression, you don't need to hint.

👍 1

So i'm back to thinking it's specific to shadow.
If you're referring to :infer-externs that's on by default

were using figwheel. Part of my confusion is i have to look up what the cljs compiler does vs what the shadow compiler and fig compiler does or just passes on to the cljs compiler.

so i have to look up if :infer-externs is a cljs compiler option, or if it exists just in shadow. looking now.

understood, I'm afraid I am in the dark on figwheel. but I'm sure someone else here can lend a hand.

👍 1

more notes, it's a cljs https://clojurescript.org/reference/compiler-options#infer-externs, hellers https://code.thheller.com/blog/shadow-cljs/2017/10/15/externs-the-bane-of-every-release-build.html... > David Nolen implemented the https://clojurescript.org/guides/externs for the ClojureScript compiler. This works pretty well but sometimes relies on the user annotating the code in a couple of places which is not something you’d do (well, me at least) naturally. So you typically still run into the ln.S is not a function issue, then enable “pseudo names” and then annotate your code. In a way thats still writing externs manually. So my guess is that shadow is just layering on top of :infer-externs, and type hinting still should always work.

oh, right, i forgot the applied science lib was called that, thanks!

to expand on this: yes, shadow-cljs is using the basic inference from the cljs.compiler, but layers a couple things on top since it will usually also be processing all other JS. so it has more information about what is going on and can generate a few more externs the other tools might be missing out on. It also added :infer-externs :auto as the default, which means it'll always show you extern inference warnings for all your namespaces. The default in other tools is to only show them if you opt into them for certain namespaces via (set! *warn-on-infer* true)

👍 1

^js hints otherwise should be rather reliable in all tools, with the known caveat that the core.async go macro tends to lose those hints, thus them not having any effect in those contexts

👍 1

We make pretty heavy use of core-async, I feel like that encourages me to use a library like js-interop as it seems to solve the problem on a per function basis, rather then requiring the user to understand a bit more about the context they are working in. This topic is equal parts fascinating and annoying 🫠

That being said, thanks Heller.

well yeah it should just be fixed in core.async, but I never had the time to dig in.