Fork me on GitHub
#clojurescript
<
2022-04-20
>
didibus02:04:22

How would I go about using performance.now from the NodeJS standard lib: https://nodejs.org/api/perf_hooks.html#perf_hooksperformance

didibus02:04:13

It looks like: (-> js/performance (.now)) works, but I'm not sure I understand why. The NodeJS doc says the object is perf_hooks and performance is on that, so why is performance exposed as a global?

lilactown05:04:29

it looks like you can access it both ways in node.js

didibus05:04:59

Hum, ok that explains it. If I couldn't access it through js/performance, how would I go about accessing it through perf_hooks? I couldn't figure it out

lilactown07:04:32

(.. js/perf_hooks -performance now)

thheller09:04:47

performance is a standard global, so maybe the node docs are just outdated or forget to mention that part. its also available in the browser

didibus15:04:45

Oh, maybe docs were wrong, cause when I tried js/perf_hooks it said can't find JS for that name

borkdude15:04:22

@U0K064KQV What are you using this for? I recently made an async version of time - wondered if you also needed that maybe :)

borkdude15:04:55

user=> (nbb.core/time (p/delay 1000 :hello))
"Elapsed time: 1000.643083 msecs"
:hello

didibus16:04:03

That was my use case ya. Wanted to time how long two go blocks took to run to completion. I'm surprised promesa doesn't have its own async time, might be a good addition to it

borkdude16:04:19

Indeed, that only occurred to me now

roklenarcic14:04:09

which closure compiler version should we be using? I am getting errors with v20220301

dnolen14:04:08

@roklenarcic generally you should never alter the version of Google Closure that ClojureScript depends on, this was reliable in the old days, but simply not possible in the past 4-5 years

roklenarcic19:04:26

hm cljs dep doesn’t seem to bring in closure compiler as a dep

roklenarcic09:04:18

Ok then why is this happening?

internal.js:114 Uncaught TypeError: str.trim is not a function
    at Object.goog.string.internal.trim (internal.js:114:18)
    at Object.clojure$string$trim [as trim] (string.cljs:199:4)
Seems like goog library doesn’t have trim function that cljs expects it to have?

thheller09:04:17

looks like you are calling (clojure.string/trim not-a-string) when it expects a string

thheller09:04:25

might just be nil, or a keyword or something like that

roklenarcic09:04:01

oh that is one confusing type error then

roklenarcic09:04:08

thanks, I had a regression in my code

victorb12:04:47

Usually when you come across Uncaught TypeError: X is not a function in JS (not just CLJS) and you're sure you're calling the right function, it's because you're calling the function on a unexpected object (`nil/null` instead of String for example), first thing to check would be that the type in runtime is what you expect it to be

sova-soars-the-sora16:04:41

Can I use unicode characters (japanese words specifically) as keys in a map in cLJS?

sova-soars-the-sora16:04:18

I am under the impression any [ol'] string will work ... But I am getting null when (.log js/console (get @atom "もの"))

p-himik17:04:41

It doesn't matter what the key is. Let alone what the contents of a string key are.

=> (def m {"もの" 1})
#'cljs.user/m
=> (m "もの")
1
So your @atom doesn't have that key or its associated value is nil.

sova-soars-the-sora17:04:40

Okay thanks for confirming that @U2FRKM4TW

sova-soars-the-sora17:04:45

Also, I didn't know we could do (m k) that's a mind explode for me

sova-soars-the-sora01:04:42

-_- there was an invisible character at the end of the key

sova-soars-the-sora01:04:05

😅 brain melt 🫕

johanatan18:04:31

is there some diff in behavior between clojure and clojurescript with respect to lazy sequences? i'm getting call stack size exceeded errors for both of the following common definitions:

(def primes (remove
             (fn [x]
               (some #(zero? (mod x %)) primes))
             (iterate inc 2)))

(def primes
  (lazy-seq
   (filter (fn [i] (not-any? #(zero? (rem i %))
                             (take-while #(<= (* % %) i) primes)))
           (drop 2 (range)))))

johanatan18:04:51

for (take 1 primes)

johanatan18:04:37

$ lumo 
Lumo 1.10.1
ClojureScript 1.10.520
Node.js v11.13.0
 Docs: (doc function-name-here)
       (find-doc "part-of-name-here")
 Source: (source function-name-here)
 Exit: Control+D or :cljs/quit or exit

cljs.user=> (def primes (remove
       #_=>              (fn [x]
       #_=>                (some #(zero? (mod x %)) primes))
       #_=>              (iterate inc 2)))
#'cljs.user/primes
cljs.user=> 
cljs.user=> (take 1 primes)
Maximum call stack size exceeded
	 cljs.core.Iterate.cljs$core$ISeq$_rest$arity$1 (NO_SOURCE_FILE <embedded>:1125:318)
	 Object.cljs.core._rest (NO_SOURCE_FILE <embedded>:361:87)
	 Object.cljs.core.rest (NO_SOURCE_FILE <embedded>:504:396)
	 cljs.core.LazySeq.fn (NO_SOURCE_FILE <embedded>:1147:176)
	 cljs.core.LazySeq.sval (NO_SOURCE_FILE <embedded>:792:151)
	 cljs.core.LazySeq.cljs$core$ISeqable$_seq$arity$1 (NO_SOURCE_FILE <embedded>:798:255)
	 Object.cljs.core._seq (NO_SOURCE_FILE <embedded>:388:89)
	 Object.cljs.core.seq (NO_SOURCE_FILE <embedded>:503:180)
	 cljs.core.some (NO_SOURCE_FILE <embedded>:927:52)
	 (evalmachine.<anonymous>:3:23)

cljs.user=> 

johanatan18:04:00

$ clj
Downloading: com/cemerick/clojurescript.test/0.2.3-SNAPSHOT/maven-metadata.xml from clojars
Clojure 1.10.3
user=> 
user=> 
user=> 
user=> 
user=> 
user=> 
user=> 
user=> (def primes (remove
             (fn [x]
               (some #(zero? (mod x %)) primes))
             (iterate inc 2)))
#'user/primes
user=> 
user=> 
user=> 
user=> (take 1 primes)
(2)

p-himik18:04:05

Huh, I'm really surprised the CLJ version doesn't blow up in the same exact manner.

johanatan19:04:24

i'm not. that's how lazy sequences are supposed to work

hiredman23:04:31

was just looking over http://ask.clojure.org and ended up here

hiredman23:04:42

the explanation given in that blog post is nonsense

hiredman23:04:58

primes is not closed over anywhere

hiredman23:04:06

global names are not closed over

hiredman23:04:16

the reason it terminates is because some is short circuiting

johanatan23:04:33

Ok well that blog post is just one of the two provided implementations with this problem and even if his reasoning is off (which I'm not saying it is since I didn't look into it), the problem that this works on Clojure and doesn't work on cljs still remains

johanatan23:04:06

Although yes I agree that if he says that primes is closed over, then he's wrong on that

johanatan23:04:46

And it's not “corecursive” either lol

johanatan23:04:53

It's just plain recursive

johanatan23:04:42

It did happen to be the shortest implementation I found and I like brevity (despite whatever misunderstandings its author apparently seems to be under)

hiredman00:04:38

for example, in clj you can do

(def primes (remove (fn [x] (some #(zero? (mod x %)) @(resolve 'primes))) (iterate inc 2)))
where it will resolve and take the value of primes over and over, obviously nothing being closed over or cleared, and it works fine

johanatan00:04:38

Yes I agree there's no closure. There's also no corecursion. He's confused

p-himik19:04:44

Yeah, found that article in the annals of Slack, reading it now. "Supposed" is a bit of a strong word though given that Clojure doesn't have a standard. And while the JVM implementation is definitely a reference one, this particular tidbit might easily end up being an implementation detail. But I could be wrong.

seancorfield19:04:46

Is it possible that mod and zero? are producing different values on cljs due it using floating point instead of integers?

p-himik19:04:45

It doesn't even get to calling those function - it falls into the first iteration of some time and time again.

p-himik19:04:24

So it seems that it works in Clojure due to a {:once true} function being used. It's unclear to me whether it's actually intended or not, as I can easily see how {:once true} could be introduced only as an optimization due to the promise that the inner fn of LazySeq will be called only once (which is not true in this case). In any case, CLJS doesn't have {:once true}, so anything that relies on it in CLJ must either be written in a different way in the core of CLJS itself or it will not work.

seancorfield19:04:26

NM, in lazy-seq

p-himik19:04:53

It's from the linked article. But remove uses lazy-seq which clears its inner function's locals on the first call - so the second call unconditionally returns nil. In CLJS, it doesn't.

p-himik19:04:31

@alexmiller Could you please confirm whether the above example with (def primes ...) is something that works intentionally and not by accident? If it's intentional, then something should be done on the CLJS side - either a change to how it works or an addition to the "Differences from Clojure" page. In this case, I'll create an Ask summarizing the discussion. If it's not intentional, then... Not even sure.

Alex Miller (Clojure team)20:04:00

Sorry, what's the question?

p-himik20:04:26

This code

=> (def primes (remove
             (fn [x]
               (some #(zero? (mod x %)) primes))
             (iterate inc 2)))
=> (take 1 primes)
works without issues on CLJ but fails with stack overflow on CLJS. It works on CLJ only because remove uses lazy-seq which, in turn, uses fn* with {:once true}. CLJS doesn't have any analog to {:once true}, so lazy-seq doesn't work that way. The question is, whether the behavior observed on CLJ is something intentional or a happenstance.

p-himik20:04:30

The docstring of lazy-seq in both implementations says: > yields a Seqable object that will invoke the body only the first time seq is called The difference is, in CLJ entering a function counts as an invocation in this case, and in CLJS exiting a function does. So in CLJ, a lazy seq can refer to itself as above, but in CLJS it cannot.

Alex Miller (Clojure team)20:04:15

how can it not be intentional with the special metadata handled by the compiler?

Alex Miller (Clojure team)20:04:44

but I'm not sure you've described this properly. the once is a hint that allows reference clearing (because it will only be invoked once), not sure if that's the difference you're actually describing

Alex Miller (Clojure team)20:04:10

since the head of primes is held in a var here regardless, I don't think that actually matters

Alex Miller (Clojure team)20:04:34

the outer primes var and the inner primes reference to the var refer to the same sequence instance, guarded by the same locks, this is all inherent in how seqs are implemented. I don't know how CLJS differs from this.

johanatan20:04:19

note that there were two reference implementations provided. the other one also works on clj, fails on cljs and perhaps does not rely on {:once true} (I haven't drilled into those details to understand it yet or not)

johanatan20:04:41

but given that we have two completely independent and quite different implementations that both "work in clj" and "fail in cljs", i am inclined to think nothing here is "by accident"

p-himik20:04:33

If you mean the other way of implementing primes you mentioned above, then it also seems to rely on :once because it also uses lazy-seq.

johanatan20:04:35

yes they both use lazy-seq

johanatan20:04:12

but also they should both work regardless of any implementation detail

johanatan20:04:18

from an abstract perspective

johanatan20:04:28

like just by reading their code and reasoning what should happen

johanatan20:04:41

and then you have the fact that that is actually what happens in clojure itself

johanatan20:04:59

how else would you expect lazy-seqs to work?

Alex Miller (Clojure team)20:04:09

I don't think either "relies" on :once - that's just allowing local clearing to clear the head and avoid head holding, but you're head-holding regardless, so don't understand how that matters

👍 1
p-himik20:04:00

I have implemented the above code without using :once. It started to fail with stack overflow on CLJ as well.

hiredman23:04:58

the reason this stack overflows has nothing to do with the problem being discussed, it is because you swapped remove with filter a not around some

hiredman23:04:04

some can short circuit

hiredman23:04:21

not some cannot

hiredman23:04:28

some is there exists

hiredman23:04:44

not some is for all not

hiredman00:04:51

obviously too late in the day for thinking about this stuff

🙂 1
hiredman00:04:03

it is :once that makes the difference, but primes is not closed over, so :once doesn't matter for it, :once only applies to the sequence produced by iterate

p-himik20:04:20

(defmacro my-lazy-seq
  [& body]
  (list 'new 'clojure.lang.LazySeq (list* 'fn* [] body)))    
=> #'dev/my-lazy-seq

(defn my-filter [pred coll]
  (my-lazy-seq
    (when-let [s (seq coll)]
      (if (chunked-seq? s)
        (let [c (chunk-first s)
              size (count c)
              b (chunk-buffer size)]
          (dotimes [i size]
            (let [v (.nth c i)]
              (when (pred v)
                (chunk-append b v))))
          (chunk-cons (chunk b) (filter pred (chunk-rest s))))
        (let [f (first s) r (rest s)]
          (if (pred f)
            (cons f (filter pred r))
            (filter pred r)))))))
=> #'dev/my-filter

(def primes (my-filter (fn [x]
                         (not (some #(zero? (mod x %)) primes)))
                       (iterate inc 2)))
=> #'dev/primes

(take 1 primes)
Error printing return value (StackOverflowError) at clojure.core/seq (core.clj:139).
null

p-himik20:04:33

CLJS' implementation of filter and lazy-seq is almost identical. Except for :once. Given that, and the above demonstration, I fail to see how the difference is not caused by :once.

Alex Miller (Clojure team)20:04:55

I dunno, don't have time to think about it any more rn, sorry

p-himik20:04:59

The inner primes becomes a local, right? It's not the var, it's a local binding. On the second call of the :once function (which happens only when we go beyond the realized part of the lazy-seq), it becomes nil - there's nothing to iterate over. That's why this pseudo-recursive iteration over a lazy-seq never unrolls itself from inside out. In any case, I'll create a question on http://ask.clojure.org then.

johanatan01:04:15

i just read the ask and your explanation regarding :once but I think that's kind of missing the point. Laziness is broken if this doesn't work as written. Try it in a language like Haskell where laziness is also default and it will work fine.

johanatan01:04:33

So, sure, if you wanna say that the fix is to add :once to CLJS, that's fine. It's an implementation detail that really doesn't matter from the perspective of the user however. All the user wants is for laziness to work.

p-himik07:04:20

I'm describing the cause in the post, I'm not offering any solutions let alone implementation details.

johanatan00:04:13

Looks like you're wrong about anything being closed over though. See the reply.

johanatan00:04:21

As @U0NCTKEV8 stated, the reason this works is the same reason the user expects it to work, namely the short-circuiting nature of some. So I think I was correct in objecting to your explanation & trying to persuade you away from the pontificating (which did not sit well with my gut).

hiredman01:04:50

The stackoverflow is the result of once, but the only thing once actually effects is the sequence created by iterate

johanatan01:04:59

And what sequence would that be?

johanatan01:04:47

I still consider that an implementation detail though. I'll bet this works in Haskell without the :once keyword appearing anywhere (Haskell doesn't have keywords so this is a sure bet) 😂

hiredman01:04:24

The sequence created by iterate that is the argument passed to remove

johanatan01:04:26

Yes but how is that sequence different between an impl containing :once and one that doesn't ?

hiredman01:04:49

That I am not sure about

hiredman01:04:41

It isn't that that sequence is different, what is different is remove or filter

johanatan01:04:39

Hmm interesting

hiredman01:04:43

They behave differently with or without once, and the difference is in regard to the sequence passed in as argument, the one created with iterate in this case

johanatan01:04:52

There is a closure in that code though: the lambda inside the anon fn closes over ‘x’

hiredman01:04:17

Sure, it closes over the argument to filter

hiredman01:04:28

The seq created by iterate

hiredman01:04:46

It does not close over primes

johanatan01:04:17

Then this actually is co-recursive with one of the fns just being anonymous

hiredman01:04:35

It refers to primes via the var, and there is no locals clearing of it

johanatan01:04:36

Unnamed co-recursive

hiredman01:04:24

My entire reason for wading in on this is just people kept repeating the thing about closing over primes

johanatan01:04:30

If you pulled the fn closure out into a defn, this would be obvious

hiredman01:04:39

Which at least in the clj case is nonsense

johanatan01:04:58

But if that fn doesn't close over primes, then it isn't a closure as there's nothing else for it to close over

hiredman01:04:22

Or the example I gave, replacing primes with deref and resolve

roklenarcic09:04:18

Ok then why is this happening?

internal.js:114 Uncaught TypeError: str.trim is not a function
    at Object.goog.string.internal.trim (internal.js:114:18)
    at Object.clojure$string$trim [as trim] (string.cljs:199:4)
Seems like goog library doesn’t have trim function that cljs expects it to have?