Fork me on GitHub
#clojurescript
<
2023-08-22
>
itaied10:08:28

How can I run a function property from js? I have a lib client with a function name start. Executing the following code works:

(.start client "2")
But this doesn't:
((.-start client) "2")
What am I missing about js interop?

Roman Liutikov10:08:04

what does it mean that the latter doesn't work?

Roman Liutikov10:08:39

I guess, most likely it's a runtime error coming from JS and is related to this

Roman Liutikov10:08:46

in this case it's not an interop issue, but JS semantics in accordance to how this works, read more on this in JS and method binding

itaied11:08:03

im receiving this error

:repl/exception!
; 
; Execution error (TypeError) at (<cljs repl>:1).
; this.start is not a function

itaied11:08:46

It does seems like because it loses the scope of this. Can I run it dynamically in cljs somehow? I want to receive the name of a function and call it from a js class client lib

Roman Liutikov11:08:27

same as in JS: get the reference to the function from an object/instance, bind it, done

(let [f (aget object "function-name")
      f (.bind f object)]
  (f 1 2 3))

itaied11:08:37

thanks, bind did the trick

p-himik12:08:26

A tangential note - don't use aget to get properties from objects. For function names that aren't valid identifiers or are only known at run time, there's js-invoke (which also doesn't have the this problem since it uses .apply). For properties, there's goog.object/get.

itaied14:08:48

When I'm requiring a library from js (using shadow) do I need to add it to another place other than package.json? I'm facing this problem: Module not provided: module$node_modules$$fusionauth$typescript_client$dist$fusionauth_typescript_client_min

(:require ["@fusionauth/typescript-client" :refer [FusionAuthClient]]) 

p-himik14:08:35

You shouldn't have to do anything else. No clue where the error is coming from, perhaps there's a configuration issue or the NPM package is doing something weird.

p-himik14:08:27

Might be relevant: https://github.com/thheller/shadow-cljs/issues/840 But I don't see any dynamic import or require calls in the file here: https://unpkg.com/browse/@fusionauth/[email protected]/dist/fusionauth-typescript-client.js I'd try changing the :require to "@fusionauth/typescript-client/dist/fusionauth-typescript-client.js". If that doesn't work, maybe "@fusionauth/typescript-client/build/index.js" will.

thheller14:08:35

not sure why you are getting that error. works fine for me.

thheller14:08:54

I mean the loading the code part, no clue if you actually need to use it some way to trigger the error

thheller14:08:46

which shadow-cljs verison do you use?

gnl20:08:39

Hey everyone, since it just came up on the #clojure channel, this is my one (and probably only) attempt to campaign for upvotes on this issue: • https://ask.clojure.org/index.php/6574/letfn-collisions-across-namespaces TL;DR – if you're using letfn in ClojureScript you're liable to get burned by broken scoping and name collisions at some point, so come add your vote.

😨 8
hifumi12322:08:10

since the example calls defn outside of the top level, i dont know whether this is really a bug or simply UB

hifumi12322:08:36

is there an example of this problem occurring when "properly" using def/defn?

isak22:08:57

I don't think this usage is improper, it still is basically the top level

phill22:08:21

For comparison: The following works ok (does not mix up the fns "c" in the globally defined functions' respective closure):

dev:cljs.user=>   (def d 
    (letfn [(c [] 42)]
      (fn []
        (c))))
#'cljs.user/d
dev:cljs.user=> (d)
42
dev:cljs.user=> (def dd 
    (letfn [(c [] 47)]
      (fn []
        (c))))
#'cljs.user/dd
dev:cljs.user=> (dd)
47
dev:cljs.user=> (d)
42

👌 2
hifumi12323:08:28

@U08JKUHA9 There is no “basically top level”. One either uses defn at the top level or doesn’t. I am not sure if Clojure treats forms within a top-level let form as top-level forms, but this is important to note because def is intended to be used specifically at the top level. Any other usage working is incidental at best and not an intended use case.

gnl00:08:37

I have to disagree @U0479UCF48H, there are plenty of perfectly valid use cases where defn isn't used at the top-level: • letfn (or let) is used at the top-level to define a very specific helper function one doesn't want to pollute the namespace with, for a nested defn. • An alternative defn-like macro that evaluates to a do block that does things in addition to the nested defn (possibly also adding additional nested defns), as used in Guardrails and Ghostwheel for example. • A macro that transforms a re-frame or other function registration (subscription, event, etc.) by extracting the anonymous fn into a defn and passing that to the registration function (both of which are nested in a top-level do block) or alternatively replacing the nested fn with a @(defn. This will be used in the next version of Playback to support subscription/event replay in an identical manner to defn replay. This is just off the top of my head. I don't think this is remotely comparable to the https://clojure.org/guides/faq#keyword_number which was a case of "you were holding it wrong, but now that you did, we're going to keep supporting it". Nested defns are legit and I think this is a very clear case of a grave ClojureScript-specific local-scope-breaking compiler bug.

hifumi12300:08:28

Per https://clojure.org/reference/special_forms#def > Using def to modify the root value of a var at other than the top level is usually an indication that you are using the var as a mutable global, and is considered bad style. Consider either using binding to provide a thread-local value for the var, or putting a https://clojure.org/reference/refs or https://clojure.org/reference/agents in the var and using transactions or actions for mutation.

gnl00:08:23

I'm inclined to agree when it comes to def, but not defn.

hifumi12300:08:56

But isnt defn equiavlent to a def anyway? From the docstring of defn, >

clojure.core/defn
> ([name doc-string? attr-map? [params*] prepost-map? body] [name doc-string? attr-map? ([params*] prepost-map? body) + attr-map?])
> Macro
>   Same as (def name (fn [params* ] exprs*)) or (def
>     name (fn ([params* ] exprs*)+)) with any doc-string or attrs added
>     to the var metadata. prepost-map defines a map with optional keys
>     :pre and :post that contain collections of pre or post conditions.

hifumi12300:08:50

Maybe someone from core can help clarify, but this docstring signals to me that defn should be treated as a specific kind of def . So in particular, not using defn at the top-level is not an intended use case.

gnl00:08:18

While defn does in fact desugar to def, my understanding of the http://clojure.org documentation you posted is that it refers specifically to the direct usage of def, not "anything that desugars to def". I've seen too many perfectly legitimate usages of nested defns to interpret it otherwise. I can not think of any examples where that would be the case for def. But in a way, this is beside the point. Even from the documentation it's clear that even nested defs are supported, it's just usually a sign that you're doing something wrong (and I agree).

hifumi12300:08:39

I see. The reason I am bringing this up at all is because there are some instances where people report bugs, but core interprets the situation as “garbage in, garbage out”. Off the top of my head: • providing indices to a vector past INT_MAX: https://ask.clojure.org/index.php/11080/get-find-assoc-vectors-overflows-key-when-passed-large-longs • sorted sets throwing exceptions when looking up elements with mismatching type https://ask.clojure.org/index.php/11409/somewhat-confusing-sorted-set-behavior • providing arbitrary strings to #uuid tagged literal (e.g. #uuid "foobar") — works in CLJS, not in CLJ, but this is not considered a bug • and so on Overall, I am trying to probe whether the reported behavior of letfn is a legitimate bug or a case of “garbage in, garbage out”.

gnl00:08:22

I see where you're coming from. I'm arguing that yes, it is very much a legitimate usage, there's a ton of widely used macro code out there that couldn't be easily written (or at all) without nested defns.

gnl00:08:27

...and I believe the documentation also supports this by stating that basically "if you're doing it like this, it's usually a bad idea"

gnl00:08:36

And like I said, I also think that the def vs defn distinction does matter here and I would go as far as to say that while a nested def is usually an indication that something's off, a nested defn, especially as described in the letfn examples above, is usually not.

👍 4
hifumi12300:08:29

Thanks for taking the time to explain this, btw. These arguments are pretty convincing, so I’m going to upvote the askclojure issue, in hopes that core takes a closer look at it 🙂

gnl00:08:44

This was a pleasantly non-confrontational disagreement, thank you as well, and also for supporting the cause. 🙂

gnl00:08:14

And on a side note, this bug clearly irks a lot of people, because a couple of hours after posting it, it is now the number one open ClojureScript issue on http://ask.clojure.org – thanks everyone for upvoting.