Fork me on GitHub
#clojurescript
<
2017-09-07
>
driphter00:09:13

There's probably no simple way to use a macro that has nodejs dependencies, huh? I'd like to have some constants calculated at compile time, but they're calculated using a couple nodejs modules.

deg06:09:57

@mfikes @dnolen Thanks. So, does `^:export prevent inlining? Or, more precisely: does it guarantee that future version of Closure will never inline the function? A quick search just now was non-conclusive. Background: This is for https://github.com/deg/re-frame-firebase, a Firebase wrapper for re-frame. Firebase has the notion of subscribing to database changes, which I wrap it in a re-frame subscription. Sometimes, I will want the Firebase subscription to remain active for the entire lifetime of a large component, even though I actually use it only in a small inner component. Re-frame handles this nicely, guaranteeing that each subscription is only created once. So, I just need to create the subscription in the outer component and all behaves as expected. But, of course, it's not enough to just create it; it must be used by the mounted component. Naively, I could just write out the entire contents of the database to an invisible div, but that has obvious, ummm, issues. So, I wanted to write a function that depends on the subscription, but generates minimal output. The null function is the obvious end-point of this process, and works perfectly when not optimized. But, obviously, any optimizing compiler would be 200% reasonable to inline it down to nothing.

lsnape08:09:40

Hello, anyone know why specifying closure-defines with optimisations set to whitespace has no effect?

mfikes11:09:56

@deg ^:export doesn't prevent inlining. It provides a stable external name for a function so that it can be called. See https://clojurescript.org/reference/advanced-compilation#access-from-javascript

mfikes11:09:38

(The exported name is stable, and will point to the internally renamed name, and that internal function could be inlined into other code being compiled with :advanced that might also call it.)

deg11:09:44

Ok, thanks. And, I presume, there is no other directive that prevents inlining?

mfikes11:09:10

No. Closure doesn't give you control over inlining. But it doesn't appear that that's what you are interested in controlling.

mfikes11:09:24

You want to control the lifetime of a function, to ensure it is available?

dnolen11:09:56

fwiw @deg I still don’t even understand the problem description

dnolen11:09:08

what you’ve said above doesn’t really make much sense to me

deg11:09:24

No. I want the optimizer of the calling function to believe that the parameter is needed, by not being able to peek into null-op. So, either I want to prevent inlining of null-op or some way of declaring that a local variable must be preserved even though it appears to be unused. De facto, the current null-op seems to do what I want, in one test case of advanced compilation. But, based on what you said, I can't rely on that.

dnolen11:09:59

@deg ok, if I understand what you are saying now … why does this even matter?

dnolen11:09:15

why does something need to be preserved if your code never uses it?

dnolen11:09:20

what will use it?

deg11:09:27

So, either I'll just put it into a hidden :div. Or, probably better, I'll take a closer look at how re-frame or reagent decide when to keep it. Possibly, they are code-walking before optimization, so all this is moot.

deg11:09:39

@dnolen. For the sake of the lifetime of the subscription. I'm handwaving here on very thin ice because I don't actually know how re-frame is working this mojo. This one line hack worked empirically, so I asked here to see if made sense that it is working.

dnolen11:09:48

I would put all of these other details aside as they don’t seem relevant - my question is trying to get at the real requirement here

dnolen11:09:00

@deg “for the sake of the lifetime of the subscription”

dnolen11:09:11

what does this even mean?

dnolen11:09:31

what do compile builds have to do with component lifetimes ??

deg11:09:36

You may have missed a line above... See my "background", about a dozen messages up. Restating:

dnolen11:09:51

pretty sure I read all that

dnolen11:09:54

still doesn’t make any sense

deg11:09:47

I should probably stop here. I have something that is working de facto, but I don't understand enough about what re-frame is doing.

deg11:09:02

Your reaction strongly suggests that I'm driving down a long pier based on a wrong assumption.

dnolen11:09:10

right so my understanding so far is that you believe there is an issue where none exists

deg11:09:22

Could well be.

dnolen11:09:39

but that’s mostly because I can’t parse what you are saying … yet 🙂

dnolen11:09:24

I just don’t see how advanced compilation can interact with component lifetimes

deg11:09:35

What I observed is that my :on-dispose (in the code above) is called when the inner component is unmounted; if and only if the outer component "sufficiently" uses the subscription

deg11:09:48

My vagueness is around the word "sufficiently".

dnolen11:09:15

right so you trying to explain what you’ve observed … problem is without something minimal it’s hard to say

deg11:09:26

By observation, doing a re-frame/subscribe, dereferencing it, and holding the result in a local variable is not sufficient.

deg11:09:44

However, if I also pass it to the null-op function, that is sufficient.

dnolen11:09:34

so this conversation is becoming very confusing

deg11:09:35

My assumption now is that re-frame is doing a code walk which, now that I think about it, is obviously pre-closure. So, this all may just work.

dnolen11:09:43

because you aren’t talking about a real problem as far as I can tell

dnolen11:09:57

but a perceived problem and you’re messing around with perceived solutions?

deg11:09:20

I agree. Now that I've typed it out, it's pretty clear that my worry was a red herring.

deg11:09:10

re-frame's code walker must run before the closure optimizer. So, whatever cleverness it is doing, it can't be hurt by the optimizer.

deg11:09:11

For my own curiosity, I should now dive into the re-frame, and maybe reagent, code and stop treating them as a black boxes. But, that's my problem challenge/homework. 🙂

dnolen11:09:34

yeah I’ve never thought about how advanced optimization may interact with my reagent/re-frame code and I haven’t seen any real issues around it myself

deg11:09:40

Yup. I was diving down to a level that was new to me... first time using reg-sub-raw, etc. And the external connectivity to the database made me more concerned about performance; then some trace prints raised concerns and ... before you know it.... deep down the rabbit hole.

deg11:09:47

FWIW, I would love it if anyone has time to code-review https://github.com/deg/re-frame-firebase. It's at a big enough intersection of interests that it may become relatively widely used. I'm not convinced that I didn't make any mistakes that might bite other users. ... I was definitely learning as I wrote it.

hlolli14:09:18

I can't see why I'm getting undefined reference errors from (and js/symbol (contains? (.keys js/Object js/symbol) "key")) I basically want to know if symbol is loaded as boolean (false/nil instead of errors)

dnolen14:09:24

@hlolli you mean from the browser

dnolen14:09:36

if symbol doesn’t exist in the JS environment that’s going to be an error

dnolen14:09:07

you can check whether something exists w/o error with (exists? js/symbol)

hlolli14:09:17

@dnolen yes from the browser

hlolli14:09:38

nice! Was going to give try a try. This is what I'm looking for! thanks

dnolen14:09:03

exists? is a macro for the JS pattern typeof symbol != 'undefined'

hlolli14:09:49

I had diffuculty interoping the typeof function

> (.typeof js/asdf)
#object[ReferenceError ReferenceError: asdf is not defined]
nil
js/typeof or .typeof js/window did the same

dnolen14:09:13

typeof isn’t a function

dnolen14:09:17

it’s an operator

juhoteperi14:09:49

The interop call will be compiled to asdf.typeof, which clearly shows that even if such method existed, it wouldn't work if the object is null

hlolli14:09:08

so from clojurescript side exists? is the only way to do this safely?

hlolli14:09:22

besides this

(core/defmacro exists?
  [x]
  (bool-expr
    (core/list 'js* "typeof ~{} !== 'undefined'"
      (vary-meta x assoc :cljs.analyzer/no-resolve true))))
🙂

juhoteperi14:09:01

you can call typeof through goog/typeOf

dnolen14:09:38

@juhoteperi right but that won’t work for undefined global things

dnolen14:09:56

@hlolli it’s just how JavaScript works, and exists? is a short cut for that pattern since we don’t support direct usage of typeof operator

hlolli14:09:22

yes, I'm happy with this, bit unintuitive at first, you often see var a = a || {}; patterns in js.

samedhi15:09:10

It appears that it is the requirement of cljs.spec.test.alpha that causes the inclusion of clojure.test.check.generators in Clojurescript. In this segment https://clojure.org/guides/spec#_generators it appears to be clojure.spec.gen.alpha -> cljs.spec.gen.alpha that would cause clojure.test.check.generators to be included.

pwrflx15:09:18

how to do an (apply and myseq)? it complains that and is a macro..

dnolen16:09:13

@samedhi to be expected no?

samedhi16:09:00

Based on the documentation, I expected that cljs.spec.gen.alpha would bring in clojure.test.check.generators, but it actually seemed to be cljs.spec.test.alpha that did so.

dnolen16:09:27

@pwrflx and is a macro, there’s no way to do that directly, make an anonymous fn

dnolen16:09:53

@samedhi I wasn’t aware that any documentation implied anything here about the ns dep graph

dnolen16:09:59

it’s all implementation details

samedhi16:09:21

For sure, I was just translating what I was seeing here https://clojure.org/guides/spec#_generators

samedhi16:09:32

into cljs equivalent

dnolen16:09:40

if nothing is explicitly said, I wouldn’t have any expectations

dnolen16:09:48

esp. about Clojure / ClojureScript differences here

samedhi16:09:41

Sure thing, is this slack unlimited (that is, will my comment stay here forever) or is it on the cutoff free plan?

richiardiandrea19:09:51

no it will be thrashed so that is why there is a big debate going on concerning what are the alternatives...

samedhi16:09:52

Might be useful to others

Aron16:09:13

it's the free plan

pwrflx16:09:02

when using map-indexed is there a way of avoiding writing an anonymous function just to return the two params as it is? (eg I did not find a two-parameter identity fn)

pwrflx16:09:33

@dnolen thanks, good idea. I like that better than the (every? identity ..) solution.

pwrflx16:09:12

@moxaj damn, right 😄 did not think of that

ayidi20:09:25

Is there a way to convert an ast back to cljs? tools.analyzer.js was abandoned and can't find anything in cljs.api

dnolen20:09:55

@ayidi yeah doesn’t exist far as I know

hlolli22:09:39

Is it possible to pass in :arglists to a function meta that already takes args, alter-meta! is not working and neither does this

(defn aarg
  {:arglists '[whereami]}
  [& {:keys [:a :b :c]
      :or {:a 1 :b 2 :c 3}
      :as env}]
  (+ 2 2))
=>  {... elided... :end-line 1,
 :arglists ([& {:keys [:a :b :c], :or {:a 1, :b 2, :c 3}, :as env}]),
 :doc nil, ..elided... }

noisesmith22:09:59

is the missing ^ a typo?

hlolli22:09:14

no, adding metadata without it has worked so far without ^ I try adding it.

hlolli22:09:21

doesn't make a difference if ^ is there or not

noisesmith22:09:44

cljs.user=> (defn ^{:arglists '([fake]) :other-key :OK} aarg [])
#'cljs.user/aarg
cljs.user=> (meta #'aarg)
{:other-key :OK, :ns cljs.user, :name aarg, :file nil, :end-column 49, :column 1, :line 1, :end-line 1, :arglists ([]), :doc nil, :test nil}

noisesmith22:09:00

the other meta is there to ensure that at least that part is correct

hlolli22:09:16

ah ok, you do it before the name, wait

noisesmith22:09:26

it still fails though

hlolli22:09:28

yes, my eyes were blurry there

hlolli22:09:49

I was really able to add arglists when I had no arguments in the function, then '[whereami] would show in the arglists. I have destructured args for the function I want to modify the arglists for.

hlolli22:09:54

(defn aarg
  {:arglists '[whereami]}
  []
  (+ 2 2))
=>  {... :end-line 1,
 :arglists (whereami),
 :doc nil, ...}

noisesmith22:09:32

wow, I wasn’t aware that a hash-map before the args vector was a thing that worked…

noisesmith22:09:55

the doc calls it attr-map

hlolli22:09:17

yes, I believe this is a later addon, saw it from a more recent code in the wild

noisesmith22:09:51

this appears to actually work

(ins)cljs.user=> (defn aarg {:arglists '([fake])} [a])
#'cljs.user/aarg
(ins)cljs.user=> (meta #'aarg)
{:ns cljs.user, :name aarg, :file nil, :end-column 11, :column 1, :line 1, :end-line 1, :arglists ([fake]), :doc nil, :test nil}
(ins)cljs.user=> (defn aarg {:arglists '([fake])} [[a b]])
#'cljs.user/aarg
(ins)cljs.user=> (meta #'aarg)
{:ns cljs.user, :name aarg, :file nil, :end-column 11, :column 1, :line 1, :end-line 1, :arglists ([fake]), :doc nil, :test nil}

hlolli22:09:16

hm, and with destructureing 🙂

hlolli22:09:57

(defn aarg {:arglists '([fake])} [& {:keys [:a :b :c] :or {:a 1 :b 2 :c 3} :as env}])
not the correct :arglists here in the metadata.

hlolli22:09:03

smells like, I dare to say, a bug?