Fork me on GitHub
#clojurescript
<
2018-07-18
>
john00:07:45

nope, some aspects of the worker API are sufficiently different that it's failing silently

urbanslug08:07:56

@lee.justin.m heh redirecting 404 to index was a bad hack that broke a lot of relative links for some reason

justinlee15:07:44

Oh yea I wasn’t suggesting you actually do that 🙂 Just linked to it as evidence that you can’t do what you want to do on github pages

mf09:07:09

Hi, I just came across some strange behaviour that I can't explain, regarding the use of the not function in ClojureScript. Using a ClojureScript (1.9.946) REPL If I run:

[(true? ((comp not empty?) "foo"))
 (false? ((comp not empty?) ""))
 (true? ((not empty?) "foo"))
 (false? ((not empty?) ""))]
I get the result: [true true true true] I would have expected this code to raise an exception. The first two expressions are valid, however the last two expressions don't use comp and just apply not to empty?, which returns false. I would expect this to break as false is not a function (to my knowledge) and thus can't be applied to a string. Interestingly if I run this code in a Clojure (not ClojureScript) REPL, I get an error that I would expect:
java.lang.Boolean cannot be cast to clojure.lang.IFn
If I compile this code with the ClojureScript compiler using advanced optimizations then I get a runtime error, which is more like what I would expect:
Uncaught TypeError: b.call is not a function
Why does ((not empty?) "foo") not raise an exception when compiled with ClojureScript using no optimizations?

roman01la09:07:22

@mf this is indeed interesting ((not empty?) "foo") emits this JS code !(cljs.core.empty_QMARK_).call(null,"foo"); which is not corresponding to CLJS code

roman01la09:07:46

it should be (!cljs.core.empty_QMARK_).call(null,"foo"); instead

mf09:07:57

@roman01la ok that's interesting. Presumably the JS code that is emitted when optimizations are turned on, is valid

mfikes11:07:40

Yeah @mf, probably worth a JIRA. We've had bad code gen related to missing parens before (https://dev.clojure.org/jira/browse/CLJS-2585). This seems similar.

mf11:07:47

Thanks @mfikes I will create a JIRA ticket as suggested.

👍 4
kennytilton12:07:52

OK, here is a strange situation. I developed a big reactive library in Common Lisp and ported it to CLJ as an exercise, then noticed CLJC and targeted that to get it on the client, and all that was so easy (week or two) that I said, what the heck, and ported it to JS. But, hang on! Now a CLJS version could be had by using interop with the JS version! So which would be more appealing to a CLJSer? A native CLJS version, not insignificantly guaranteed identical to the CLJ version, or a JS interop wrapper of almost exactly the same thing?

mauricio.szabo12:07:41

I would prefer the CLJS version

👍 4
nicoschneider13:07:54

the file in question, checkouts/framework-core/src/cljc/framework_core/app.cljc is obviously a cljc file. i’ve checked it ten times now for weird reader conditionals or anything that might be causing issues like macros or whatever, it seems to be fine (like i said, clojure/jvm side works)

nicoschneider13:07:52

using lein cljsbuild once app (clojurescript version 1.10.339) in the example project produces the error i posted. does anyone have any pointer at all as to how to debug this further?

mauricio.szabo13:07:58

Do you have any macro declared on these cljc files?

mauricio.szabo13:07:37

macros that would be used both in clojure and in clojurescript?

nicoschneider13:07:06

no, in app.cljc there’s none, but it requires log.cljc containing logger macros, but those are all wrapped in #?(:clj …)

mauricio.szabo14:07:15

Macros run in Clojure, so it'll run the :clj reader path, not the :cljs

mauricio.szabo14:07:29

Maybe this can be your issue?

nicoschneider14:07:36

the macros are not defined for cljs

nicoschneider14:07:46

only :clj is in the reader macro

mfikes14:07:11

I've never seen this kind of issue. You may need to share a minimal repro.

nicoschneider14:07:31

phew, i’d be happy to but it could be significant effort

nicoschneider14:07:43

since i don’t actually know why this happens

mfikes14:07:25

Right. Is there more to the stack trace indicating where the constant is coming from?

nicoschneider14:07:27

i’ll try replacing all usage of my log.cljc with printlns, maybe that gets me somewhere. there’s no macros otherwise

nicoschneider14:07:37

yeah, it’s the ns form of the file, line 1

mfikes14:07:07

Can you share that form?

nicoschneider14:07:23

(it doesn’t say line 1, but it’s the only occurence of the namespace)

nicoschneider14:07:15

>>> (ns framework-core.app (:require [clojure.spec.alpha :as s] [com.stuartsierra.component :as component] [framework-core.bootstrap :as boot] [framework-core.error :as error] [http://framework-core.io :as io] [framework-core.ioc :as ioc] [framework-core.log :as log]))

mfikes14:07:12

I can't see anything wrong with that.

nicoschneider14:07:59

yah mate the struggle is real with this one. i’ll see if the log cljc messes anything up

nicoschneider14:07:38

and since we’re there, can macros be defined in cljc files using #?(:clj ...) ?

nicoschneider14:07:32

would it help you if i posted the full stacktrace?

mauricio.szabo21:07:21

For me, it's seems that somehow, the clojure.lang.Namespace is leaking into Javascript implementation. Try to comment each import on your file and check where the problem occurs. Then, check if that namespace that you imported is not bringing framework-core/app into the project. Probably framework_core/app.cljc have some kind of macro or other construction that forces CLJS to bring Namespace to your compilation...

mauricio.szabo21:07:42

Trust me, work with cljc with ClojureScript is a pain... sometimes it's better to avoid it at all. I also have a similiar problem in my test library check, and I'm almost quitting and creating two namespaces for the macros.

nicoschneider21:07:32

i have it resolved. it was actually some issue in another namespace. i removed my logger wrapper macros and replaced everything with printlns to test it, and it worked

nicoschneider21:07:11

the only macros there were the usual wrappers for ns capture. still an odd error.

nicoschneider21:07:37

star_ns_star that is 😋

kennytilton13:07:27

I think the CLJS community fell in love with ReactJS for two reasons. First, the declarative/functional paradigm of components. I believe @dnolen called that out as huge (and it is wonderful). Second, and this is just a guess, the immutable state of CLJS was a perfect fit for knowing when not to update. To a degree, that let CLJS wrappers of ReactJS cure its original sin, viz., dissing the importance of managing state. R/atom especially delivered a superb reactive/dataflow implementation much like CLJS Javelin, JS MobX, and binding.scala, all of them simpler and more powerful than Flux/Redux (which seems already obsolescent thanks to Relay/GraphQL). So the question is…

kennytilton13:07:00

If you still had declarative/functional views, would you miss ReactJS in a new CLJS web framework? I am trying to decide if I should toe the Facebook line or carry on with my thin HTML/CSS layer.

dnolen14:07:53

going with React also means leveraging that ecosystem

dnolen14:07:57

if you don’t need it - no big deal

nicoschneider14:07:56

before discovering reagent i’ve written some dom stuff using pure cljs - it’s not bad actually even compared to something react-ish, coming from the good ol’ jquery days. actually a great way to get familiar with cljs as well. with react/reagent, the whole experience is so non-intrusive and makes the dom stuff just go away basically. with anything more advanced than the bare basics it was much easier to port it to reagent than to reinvent the wheel 🙂

idiomancy14:07:54

so, this article is telling me that equality checking on large data structures is something to keep an eye out for in re-frame performance.. https://github.com/Day8/re-frame/blob/master/docs/Performance-Problems.md#2--on-big-structures and it makes me wonder exactly how equality checking works in clojurescript. I was under the impression that a deep walk of the data wasn't required for immutable data, so how exactly does the degree of nesting of a data structure contribute to the time complexity of an equality check?

idiomancy15:07:02

side note, I think the example they use is kind of bad, because the example is nonperformant because any change in game-state data triggers a rerender for all components. The equality checking doesn't seem to factor into why that example is bad

mikethompson23:07:31

The article explains that there are certain pathological cases where = can take a while to return false. And that this can combine badly with circumstances where a lot of unnecessary = checking is done repeatedly. This is an article about what can go wrong, right?

spinningtopsofdoom15:07:53

The issue is one of checking equality vs finding the difference of two data structures. Checking if two data structures are equal is a pointer comparison in ClojureScript. If the data structures are not equal then you'll need to do a deep walk to find where the data structures differ.

spinningtopsofdoom15:07:45

In the Reagent article you linked to finding out that two different 19 x 19 go boards are different is a fast check. Finding where they differ to render the change is where the equality check expense comes in.

spinningtopsofdoom15:07:39

It's the difference between = and clojure.data.diff

idiomancy15:07:12

hmm. see, when Iooked at the implementation of = it seemed to check identical? -- which would not return a true value for two different but equal values

idiomancy15:07:31

or rather, equality tries to check identical?, then falls back on equality

idiomancy15:07:38

^(source code of equality in cljs)

spinningtopsofdoom15:07:15

Ah I see, if identical? fails then a deep walk occurs using sequences and equality checks. For example (list 1 2 3) [1 2 3]` are equal but require using ClojureScript's seq and = abstractions to confirm this.

idiomancy15:07:19

really? that seems crazy to me! because "in clojurescript identity checking is cheap and equality checking requires a deep walk" seems like a vastly different statement than "equality checking is cheap in clojurescript"

idiomancy15:07:27

identity checking is cheap in every language!

val_waeselynck15:07:39

> Checking if two data structures are equal is a pointer comparison in ClojureScript. @spinningtopsofdoom Not always! For instance (= (range n) (range n)) will operate in linear time. Immutability does not guarantee that equality is always a pointer comparison; however, it enables us to infer from pointer equality that nothing changed. With mutable data structures, we have to be more pessimistic, which is what makes change detection usually more costly.

idiomancy15:07:16

so, I'm confused, because

(let [a [1 2]
      b [1 2]]
      (identical? a b))
=> false
so how does this pointer check come into play?

val_waeselynck15:07:48

@idiomancy in this case, it doesn't

val_waeselynck15:07:56

but consider this:

val_waeselynck15:07:01

(let [ns (range 1000000)
      a {:numbers ns}
      b {:numbers ns}]
  (= a b))

val_waeselynck15:07:47

this will be very fast, because even though a and b are not identical?, their content is

spinningtopsofdoom15:07:23

Consider this case

(defn do-something [a] a)

(let [a [1 2]
        b (do-something a)]
  (identical? a b))
=> true

idiomancy15:07:21

hmmm, interesting. So it's only going to deep walk until it finds an identical structure

spinningtopsofdoom15:07:35

do-something could easily have return a different data structure. If we were using mutable data structures then we'd have to do a deep walk any time a potential change happened

idiomancy15:07:31

huh. that's fascinating. yeah, that makes sense actually!

unbalanced15:07:32

okay so low level, what would trigger a pointer change?

unbalanced15:07:40

it seems like the worst case scenario is in a large nested data structure there is a change at the leaf level

unbalanced15:07:52

which would require a full walk in order to determine identity

unbalanced15:07:13

I guess another way of asking this is

spinningtopsofdoom15:07:16

(let [a {:a 1}
       b (assoc a :a 1])
 [(identical? a b) (= a b)]  

=> [false true]

unbalanced15:07:36

I don't understand why a walk is ever necessary ... ohhhh

unbalanced15:07:08

omg re-frame using = is horrible

unbalanced15:07:20

... or is it

unbalanced15:07:27

:thinking_face:

spinningtopsofdoom15:07:37

Having used React and redux ClojureScript wrappers around react make updating your state so much nicer.

unbalanced15:07:09

no the = behavior makes perfect sense

unbalanced15:07:16

just need to understand it

spinningtopsofdoom15:07:44

You end up having to do what = does under then hood but customized for every single change. Add making sure your updates don't introduce mutable shared state and well...

justinlee15:07:41

that go-board example is an interesting one. i guess the point here is that identical? can tell you whether structures are definitely the same, but it can’t tell you whether they are definitely different. because in that example there were 361 components, all of which received different datastructures, that requires a deep equality check to make sure. it probably would have been faster in that case to use identical? and just rerender the components given that they are pure (obviously you don’t do that, but it’s an interesting thought experiment). the example is an interesting case where the work in the vdom is more expensive than what you save through reconciliation

kennytilton16:07:04

@dnolen Yes, the React ecosystem is a big consideration. But there was a lot of code written for HTML/CS/JS before ReactJS, so there may even be an advantage to going “native”. My other thought is that a code base targeting React via sth. like reagent or re-frame might be easier to extend to ReactNative, which AirBnB aside seems the preferred route to mobile. But if a new framework works out, methinks a bridge to RN could hide the RN lifecycle as reagent does and the bulk of the code base would work unchanged. My unrelated thought is that VDOM was a mistake, why perpetuate it?

justinlee16:07:54

One reason to perpetuate it is that you are arguably in the minority view. 🙂 The VDOM is great and makes it easy to write correct code. It is, imo, a much more natural fit for functional programming than doing some kind of observer model thing where you mutate the dom directly.

Garrett Hopper16:07:36

I have a :foreign-libs library for a Google API. gapi is the :global-exports, and I put it under the google.api namespace. The problem is that I need to access static functions under gapi, and I can't seem to find any way to do it. 1. I can't import google.api.auth2 (I didn't export that namespace, so this makes sense.) 2. I can't just use google.api.auth2/$staticFn 3. Using (.staticFn (.-auth2 google.api)) runs into problems, because it's not treated like a static function using the $ns/$fn syntax as far as I can tell.

dnolen16:07:31

jQuery components? Backbone components? Closure UI Components

dnolen16:07:45

I don’t think you’d want to use that stuff for the most part

dnolen16:07:11

Reagent doesn’t really succeed at hiding React

kennytilton16:07:20

All the above?

kennytilton16:07:06

With a thin wrapper any existing code should drop in, or port mechanically.

dnolen16:07:10

you could use that stuff but I don’t know what value you’d be getting out of it - they won’t go away because of legacy maintenance

kennytilton16:07:22

qooxdoo did a lot of nice work.

dnolen16:07:36

but I don’t think they’ll necessarily be any better

kennytilton16:07:00

No, but we are not in a desert of an ecosystem is all I am saying.

dnolen16:07:04

I’ve been doing JS since the 2005, I don’t have anything good to say about qooxdoo or these other rich UI libs - your mileage may vary

kennytilton16:07:42

So who else besides React has done good work?

dnolen16:07:49

in the end yes you can wire these mutable JS UI libraries together

dnolen16:07:04

but they don’t really jive that well with ClojureScript for the obvious reasons

kennytilton16:07:28

Well we are still talking about declarative/functional views, so we are not slipping into mutation Hell or amything.

dnolen16:07:07

I don’t see you can avoid that with traditional UI components

dnolen16:07:30

at least not without a lot of meaningless work anyway

dnolen16:07:23

VDOM conceptually is just computer graphics stuff - I don’t see what the problem is

kennytilton16:07:58

What I did was create a truly reactive layer with glue that drove qooxdoo. So deep down there is mutation (just like React once it has diffed the vdom, but the library user never goes there.

dnolen16:07:31

ok then sure - if you want to do your own thing - then of course - what’s the problem

kennytilton16:07:07

VDOM is a kludge needed to create the illusion of functional immutable declarative etc. And it costs.

dnolen16:07:21

“kludge”

dnolen16:07:29

in about the same way computer science

dnolen16:07:58

the cost model isn’t even that interesting

dnolen16:07:12

you can always layer better reconciliation

kennytilton16:07:16

Well I am talking about a new CLJS framework, so I am trying to decide if I should wrap React or if the community is committed in some way to React.

dnolen16:07:47

I would say >70% are using Reagent at this point, maybe even >80%

dnolen16:07:39

but people are writing new stuff all the time

kennytilton16:07:44

Well, the cost drove them to include (I forget) “dontUpdateMe” in the lifecycle forcing the user to decide that question with their own state management, so I think what happened was it did not scale.

dnolen16:07:03

but I don’t think you’re going to convince large number of people to use some other thing

dnolen16:07:15

Reagent/React is not that problematic and you have platform effects

dnolen16:07:23

the downsides really aren’t that big or or that interesting

kennytilton16:07:40

Oh, wow, I heard re-frame was the go-to CLJS solution. Good to know.

dnolen16:07:55

my primary complaint about React/Reagent has nothing to do it per se

dnolen16:07:07

rather JS build tools kinda stink

kennytilton16:07:07

Right, I am offering an upside.

dnolen16:07:30

and that holds true React or no

kennytilton16:07:31

Don’t get me started on tooling!

dnolen16:07:51

I mean specifically long dep chains + lack of tree shaking

dnolen16:07:02

less about some perceived UX around the tools

kennytilton16:07:22

I think the only tooling I want is good ol’ CLJS->JS.

justinlee16:07:07

@hiskennyness re your comment above: re-frame is built on top of reagent

kennytilton16:07:18

Thanks, @lee.justin.m I know. And yes, I imagine the 70-80% @dnolen cited included re-framers. Makes more sense now.

Jp Soares19:07:50

Is anyone using http://www.webcomponents.org ? I tested importing one component and it seems that different components might need different ways to import. Are there some known issues or tips for using them with re-frame?