CLJS: Setting a JS property to the result of calling a method on it - clojurescript

I'm looking to modify some existing text in a web app, which I'm accessing with expressions like this:
(.-innerHTML (.getElementById js/document "myElementId"))
What I specifically want to do is mutate that text via a search/replace:
(set! (^that whole thing) (.replace (^that whole thing) "old" "new"))
And I'd ideally like to do that without having to repeat the property-access expression. Is there any existing shortcut for this? I'm envisioning something like Raku's object .= method(args) shorthand for object = object.method(args); maybe a good clj name/pattern would be (.set! (access-expression) method & args). I can always write a macro, but was wondering if I'd overlooked something already there.

I was curious too and looked in the CLJS CheatSheet and they link to the CLJS Oops library which provides similarly looking functions.
It seems to be that the most robust solution would be to just rely on the Google Closure Library (which is always at hand) and use a combination of goog.object/get and goog.object/set (no need for a macro), something like:
(require 'goog.object)
(defn update-obj! [obj field f & args]
(let [old-val (goog.object/get obj field)
new-val (apply f old-val args)]
(goog.object/set obj field new-val)))
;; Example:
(update-obj! (.getElementById js/document "my-div") "innerHTML" (partial str "it works: "))
This should work in both development and optimized output.

Related

Clojure/Clojurescript: Argument to resolve must be a quoted symbol

I'm attempting to use a string saved in a variable to call a function like so:
(defn update-product-list [] "Test")
(defn handle-state-change [action]
((resolve (symbol action))))
(handle-state-change "update-product-list")
However, this gives me the following error: Assert failed: Argument to resolve must be a quoted symbol
I've also tried changing the above line to:
((resolve (quote (symbol action))))
But this still gives an error. I also tried changing it just to:
((resolve 'action))
But this gives a different error I don't quite understand: js/action is shadowed by a local. I don't want to override the function just call it. Not sure where I'm going wrong. I've looked at a few examples, but can't see to pin it down.
ClojureScript supports :advanced optimization, in which Google Closure Compiler will rename, inline, or eliminate (unused) functions in order to implement minification. In short, the name of the function you want to look up will, in general, simply no longer exist under :advanced.
Because of this, ClojureScript's resolve is a compile-time facility (a macro requiring a literal quoted symbol).
If you are using :simple or self-hosted ClojureScript, more options are available to you because the support needed persists into runtime. For example Planck has a planck.core/resolve that behave's like Clojure's resolve. A similar approach is possible in Lumo, and similar facilities can be fashioned if using :simple.
In general though, given :advanced, if you need to map strings to a set of functions, you need to somehow arrange to have a static mapping constructed at compile time to support this (the set of functions must be known a priori, at compile time).
If you have a namespace (the name of which is statically known at compile time) which defines functions that need to be dynamically called via strings, you could consider making use of ns-publics:
cljs.user=> (ns foo.core)
foo.core=> (defn square [x] (* x x))
#'foo.core/square
foo.core=> (in-ns 'cljs.user)
nil
cljs.user=> (when-some [fn-var ((ns-publics 'foo.core) (symbol "square"))]
(fn-var 3))
9
This will work under :advanced. The mapping constructed by ns-publics is static; built at compile-time. If you have multiple namespaces that need such treatment, you could merge several calls to ns-publics to build a larger map.
The advantage of this approach is that the code involved is pretty short and requires little maintenance. The disadvantage is that it dumps all of the public vars of the namespace (foo.core in this example) into your generated code (and the generated code for vars is somewhat verbose). Another disadvantage is that you need to statically know the namespace(s) involved at compile time.
If you need to further minimize generated code size, you could just build / maintain a simple static map from string to function value as in
(def fns {"square" foo.core/square})
and use it appropriately, keeping it up to date as your codebase evolves.
Another option would be to mark the functions that you need to access using ^:export meta, and then to call those functions using JavaScript interop. For example if you define the function this way
(defn ^:export square [x] (* x x))
then you can use strings / interop to lookup the function and call it at runtime. Here's an example:
((goog.object/getValueByKeys js/window #js ["foo" "core" "square"]) 3)
The use of ^:export and :advanced is covered here. If you know that you are using :simple or less, then you can simply use JavaScript interop to call the functions of interest, without needn't to use ^:export.
Note that there is no general solution that would let you look up a function by name at runtime under :advanced without somehow putting some aspect of that function into your code at compile time. (In fact, if a function is not referenced in a way that Google Closure Compiler can statically, see, the function implementation will be completely eliminated as dead code.) In the above, ns-publics grabs all the vars for a namespace at compile time, rolling your own lookup map sets up static code to refer to the function value, and using ^:export statically arranges to make the name of the function persist into runtime.
You need to use it like this:
((resolve 'inc) 5)) => 6
or, deconstructed a bit:
(let [the-fn (resolve 'inc)]
(the-fn 7))
=> 8
If you have the function name as a string, use the symbol function to convert from string => symbol (from clojuredocs.org):
user=> ((-> "first" symbol resolve) [1 2 3])
1
And, never forget the Clojure CheatSheet!

Can name munging be avoided for an interop call in ClojureScript?

In advanced compilation
(js/console.log "HELLO"
js/window.navigator.msSaveBlob
(.. js/window -navigator -msSaveBlob)
(aget js/window "navigator" "msSaveBlob")
js/console.log)
=>
HELLO undefined undefined function function
I think this means that js/console has some provided externs, but navigator does not (or at least not the ms specific stuff).
AFAIK the only way to avoid this is to create some externs? But this seems unnecessarily obtuse; why would you ever want js/anything to be munged?? Wouldn't it make make more sense to never munge js/anything interop?
System functions are not munged; only your own functions are. You probably want (.log js/console ...) ?
For de-munging your own functions, place ^:export between the defn and the function name to export its name intact.
Here is more information.
All see the section called "munging" here.

Is the Macro argument a function?

I am trying to determine whether a given argument within a macro is a function, something like
(defmacro call-special? [a b]
(if (ifn? a)
`(~a ~b)
`(-> ~b ~a)))
So that the following two calls would both generate "Hello World"
(call-special #(println % " World") "Hello")
(call-special (println " World") "Hello")
However, I can't figure out how to convert "a" into something that ifn? can understand. Any help is appreciated.
You might want to ask yourself why you want to define call-special? in this way. It doesn't seem particularly useful and doesn't even save you any typing - do you really need a macro to do this?
Having said that, if you are determined to make it work then one option would be to look inside a and see if it is a function definition:
(defmacro call-special? [a b]
(if (#{'fn 'fn*} (first a))
`(~a ~b)
`(-> ~b ~a)))
This works because #() function literals are expanded into a form as follows:
(macroexpand `#(println % " World"))
=> (fn* [p1__2609__2610__auto__]
(clojure.core/println p1__2609__2610__auto__ " World"))
I still think this solution is rather ugly and prone to failure once you start doing more complicated things (e.g. using nested macros to generate your functions)
First, a couple of points:
Macros are simply functions that receive as input [literals, symbols, or collections of literals and symbols], and output [literals, symbols, or collections of literals and symbols]. Arguments are never functions, so you could never directly check the function the symbol maps to.
(call-special #(println % " World") "Hello") contains reader macro code. Since reader macros are executed before regular macros, you should expand this before doing any more analysis. Do this by applying (read-string "(call-special #(println % \" World\") \"Hello\")") which becomes (call-special (fn* [p1__417#] (println p1__417# "world")) "Hello").
While generally speaking, it's not obvious when you would want to use something when you should probably use alternative methods, here's how I would approach it.
You'll need to call macroexpand-all on a. If the code eventually becomes a (fn*) form, then it is guaranteed to be a function. Then you can safely emit (~a ~b). If it macroexpands to eventually be a symbol, you can also emit (~a ~b). If the symbol wasn't a function, then an error would throw at runtime. Lastly, if it macroexpands into a list (a function call or special form call), like (println ...), then you can emit code that uses the thread macro ->.
You can also cover the cases such as when the form macroexpands into a data structure, but you haven't specified the desired behavior.
a in your macro is just a clojure list data structure (it is not a function yet). So basically you need to check whether the data structure a will result is a function or not when it is evaluated, which can be done like show below:
(defmacro call-special? [a b]
(if (or (= (first a) 'fn) (= (first a) 'fn*))
`(~a ~b)
`(-> ~b ~a)))
By checking whether the first element of the a is symbol fn* or fn
which is used to create functions.
This macro will only work for 2 cases: either you pass it a anonymous function or an expression.

Is there a function that takes a list of argument lists and applies each list to a given function?

I.e., something like:
(defn dowith [f & arglists]
(doseq [args arglists] (apply f args)))
Is there a built-in function like that in Clojure?
I write things like that moderately often; its just so short that it's not really worth wrapping:
(map #(apply myfun %) list-of-arglists)
I use map most often so i get the results and keep it lazy. of course if you don't want it lazy and don't want the results then doseq is fine also.
No, there is no built-in function that does that.

What function/macro to check what namespaces are currently 'used' and how to check if one in particular is used?

Is there a function/macro in Clojure to check what namespaces are currently 'used'? Which one? So I don't mean 'what namespace are we currently in' but for example, I want to find out if 'clojure.contrib.string' is currently 'used' as in (use 'clojure.contrib.string). I don't mean 'required'.
How does one write a function that give true when for example 'clojure.contrib.string (the name of the namespace provided via a symbol) is used and false otherwise?
(defn ns-used? [name] (...)
all-ns as suggested by kotarak, is close to what you are looking for, but it also gives you all the namespaces that have been required without having been referred.
Otherwise ns-refers gives you a map of all the functions that are referred in the namespace. Similar and possibly also of interest are ns-map and ns-imports which gives you a mapping of all the imported classes or records.
If you want to strictly find all the namespaces from which functions have been referred, you could do something like this:
(defn ns-used? [ns]
(let [referred-namespaces (set (map (fn [[k v]] (:ns (meta v))) (ns-refers *ns*)))]
(contains? referred-namespaces (find-ns ns))))
If you are happy to check if the namespace has been required or used you could do
(defn ns-used-or-required? [ns]
(contains? (set (all-ns)) (find-ns ns)))
You will have to pass the symbol in quoted:
(ns-used? 'clojure.core)
all-ns for some suitable definition of 'used'.
ns-refers for some other definition of 'used'.