Fork me on GitHub
#clojurescript
<
2017-05-27
>
john02:05:26

Why is it that some of the methods I add to a deftype work and others don't? I have a deftype with this in it:

IHash
  (-hash [this] (goog/getUid this))
and doing (-hash thing) returns a value. But I also have my own IDable protocol defined, with this in the deftype:
IDable
  (-id [this] (do-stuff this))
And (-id thing) errors out in Figwheel with "Use of undeclared Var my_ns/-id" What am I missing about protocols and deftypes?

john02:05:38

However, if I make a function within that namespace: (defn get-id [thing] (-id thing)) then calling (get-id thing) works.

john03:05:51

Oh, the methods are only resolving when I'm in that namespace that the deftype was defined in...

john03:05:53

I don't understand why (-hash thing) does work from the other ns though

noisesmith03:05:31

because the namespace that defines IHash is already in scope?

nathanmarz04:05:21

what is cljs.core/NONE and does it need to be public?

john05:05:34

If I have this in one namespace:

(defprotocol IThingable
  (-thing [t]))

(defn thing-impl [t]
  (str "thing " t))

(deftype Thing [thing]
  Object
  (toString [_] (pr-str thing))
  IThingable
  (-thing [t] (thing-impl t)))

(defn thing [t] (-thing t))
And this in another namespace:
(defn custom-thing-impl [t]
  (str "custom " (-thing t)))

(deftype CustomThing [cthing]
  Object
  (toString [_] (pr-str cthing))
  IThingable
  (-thing [_] (custom-thing-impl cthing)))

(thing (CustomThing. (Thing. 1)))
;=> "custom thing 1"
(thing (CustomThing. (Thing. 2)))
;=> "custom thing 2"
(thing (CustomThing. (Thing. 3)))
;=> "custom thing 3"
I'm having to require/:refer all four of IThingable Thing -thing thing, whereas I thought I'd need only Thing and thing. Is that how it's suppose to work? Is there a way to automatically bring in the methods associated with an object?

john05:05:39

@nathanmarz sorry, didn't mean to bump your question off the page 🙂 what is cljs.core/NONE and does it need to be public?

noisesmith05:05:08

@john why not just namespace the functions instead of refer them?

noisesmith05:05:37

if you use the method syntax, you aren't using the functions, and you don't need to namespace or refer it

noisesmith05:05:52

but the function syntax is calling a function, the function needs to somehow be in scope

noisesmith05:05:54

-thing is a function that belongs to the namespace that defines IThingable

noisesmith05:05:15

and Thing is an object in the package corresponding to that namespace, but it's constructor functions are functions in that namespace (deftype auto-creates constructors right?)

noisesmith05:05:57

so th/->Thing is a function

noisesmith05:05:20

and some.package.Thing is a class in a package that looks a lot like your namespace but isn't exactly it

john05:05:25

But I don't need to use th/-thing?

noisesmith05:05:38

you mentioned needing to refer -thing

noisesmith05:05:44

th/-thing is a function

noisesmith05:05:51

(.-thing x) is a method call

noisesmith05:05:05

you need to somehow scope functions, method calls are looked up when called

noisesmith05:05:00

@john the defprotocol defines both the method and the function, and the function follows the same rules any other function you use does

noisesmith05:05:14

and the method follows the method rules etc.

john05:05:00

@noisesmith but the deftype doesn't appear to be creating .-thing method calls

noisesmith05:05:32

deftype doesn't create method calls

noisesmith05:05:52

what I'm saying is that to call a method on an object that implements a protocol, you have two choices

noisesmith05:05:02

a) call the method, b) call the function with the same name as the method

noisesmith05:05:48

a means follow the rules for java interop (you get reflection if the type isn't clear in context etc.), b means follow the rules for clojure functions (the compiler complains if you don't scope things properly)

john05:05:34

How do you go about using methods then?

john05:05:34

If deftype doesn't create them?

noisesmith05:05:40

it doesn't call them

noisesmith05:05:44

defprotocol creates them

noisesmith05:05:48

deftype implements them

noisesmith05:05:52

+user=> (defprotocol Frobable (baz [this]))
Frobable
+user=> (deftype Thing [] Frobable (baz [_] "bz"))
user.Thing
+user=> (ns other.ns)
nil
+other.ns=> (.baz (user.Thing.))
"bz"

noisesmith05:05:05

notice I did this all via basic interop, no function or namespace features used

john05:05:54

Not working for me in CLJS

noisesmith05:05:01

after making an ns alias, I can use the functions defined to do the same thing

+other.ns=> (alias 'u 'user)
nil
+other.ns=> (u/baz (user/->Thing))
"bz"

noisesmith05:05:12

alias is what gets called when you put :as in an ns form btw

noisesmith05:05:23

it's just that you can't require user :as u

noisesmith05:05:51

@john oh - then you need to use the function interfaces and rules I guess?

noisesmith05:05:06

sorry if I was dropping clj-vm only knowledge there

john05:05:12

I don't know. I could be doing something wrong

john05:05:50

#object[TypeError TypeError: (intermediate value).baz is not a function]

noisesmith05:05:38

funny - (Thing.) works in cljs, but not .baz

noisesmith05:05:07

sorry for the wild goose chase

john05:05:08

Well, it clarifies things a bit. I appreciate it.

john05:05:05

I just really didn't think you needed to refer in the methods/functions associated with a particular deftype.

john05:05:27

I should stop using refer

noisesmith05:05:41

that's a common misconception - in jvm clj it is a messy space because protocols are two things in parallel, an interface with methods on it (no ns operations needed to access) and a clojure protocol with protocol-methods (actually functions, accessed the same way you would access any other function). So in a haphazard way I was trying to clarify the root of what I think causes the confusion.

nathanmarz08:05:18

don't have access to a computer for a few days so will look into it more then

thedavidmeister11:05:15

did the compiler stop complaining about circular dependencies in namespaces at some point?

thedavidmeister11:05:35

my cljs started hanging and the only thing i can see that could have caused it is some ns changes 😕

thedavidmeister12:05:17

ok, yep, was definitely a circular dependency

thedavidmeister12:05:04

version 1.9.521 is not giving me any feedback, so i had to resolve it by walking through ns by hand 😞

miikka12:05:54

Heyyy, I've seen the same problem before with an earlier version, but I didn't ever have time to create a repro that I could share.

miikka12:05:54

It is still supposed to complain about circular dependencies.

dnolen14:05:17

@thedavidmeister it should work, but note we do not detect implicit circular deps

dnolen14:05:26

if it’s explicit we should be able to catch it

qqq16:05:53

https://login.amazon.com/website <-- are there any cljs bindings for this?

dnolen16:05:27

@nathanmarz that’s probably just a bug we never noticed before around core names & private vars

Lone Ranger16:05:08

anyone here at all familiar with datasync?

Lone Ranger16:05:01

going through the tutorial and I see this --

(ns your-app
  (:require [dat.sync.client]
            [dat.remote]
            [dat.remote.impl.sente :as sente-remote]
            [datascript.core :as d]
            [dat.reactor.dispatcher :as dispatcher]))
(def conn (d/create-conn dat.sync.client/base-schema))

(defn new-system []
  (-> (component/system-map
   ...

Lone Ranger16:05:26

but I don't see component declared anywhere

noisesmith16:05:01

system-map is defined by stuartsierra/component

noisesmith16:05:12

it's weird that they left that out of their require

Lone Ranger16:05:38

ah, I see ... typo then I guess. I just noticed in the sample code what you're talking about

Lone Ranger16:05:06

(ns dat.sys.app
  (:require-macros [cljs.core.async.macros :refer [go-loop]]
                   [reagent.ratom :refer [reaction]])
  (:require [dat.view]
            [dat.reactor :as reactor]
            [dat.remote]
            [dat.remote.impl.sente :as sente]
            ;; TODO Chacge over to new ns
            [dat.sync.client :as dat.sync]
            [dat.sys.views :as views]
            [dat.sys.events]
            [dat.reactor.dispatcher :as dispatcher]
            [datascript.core :as d]
            [taoensso.timbre :as log :include-macros true]
            [reagent.core :as r]
            [com.stuartsierra.component :as component]  ;; <--- oh hey there it is!
            [posh.core :as posh]))

miikka16:05:53

@dnolen, what's an implicit circular dep?

dnolen16:05:27

i.e. circular dep via macros, not actual ns imports

dnolen16:05:38

you shouldn’t use things never required explicitly

miikka16:05:02

right, okay

dnolen16:05:56

@miikka but if you have an explicit circular dep in your ns forms we should always be able to catch that

dnolen16:05:03

ns.foo -> ns.bar -> ns.foo

miikka17:05:50

Yeah, I had that and there was no message and the compiler got stuck (no CPU usage, nothing would happen). Or maybe it was boot-cljs that got stuck. Didn't have time to investigate back then.

miikka17:05:55

I'll see if I can still reproduce it.

john17:05:02

So, if I want to use -thing of Thing from ns thing-ns (`:as tns`) in ns custom-thing, should I use use tns/-thing or tns/thing and then provide a new def of thing in ns custom-thing that checks for instanceof? CustomThing to provide it's own implementation, otherwise call tns/thing on the passed in thing?

john17:05:29

I thought the idea was that I'd never have to redefine or provide new implementations thing

noisesmith17:05:14

tns/-thing is the normal way to do it - you pull in the namespace with the protocol to call the methods, you pull in the namespace with the type to create an instance

noisesmith17:05:35

the idea is that code that is written using the protocol methods never has to know anything about your actual type

noisesmith17:05:47

(only the code constructing an instance needs to know)

john17:05:03

okay I'll try that

john17:05:43

@noisesmith I'm pulling in IThingable, Thing and thing, and when I use -thing I get Use of undeclared Var custom-thing/-thing. It just seems surprising to have to use thing-ns/-thing with thing-ns/Thing.

noisesmith17:05:24

that's intentional

noisesmith17:05:49

you should be able to write code that uses a protocol's methods, without knowing about the code that implements the data type

noisesmith17:05:03

the methods belong to the ns defining the protocol

john17:05:18

So, suppose I want to create a MultiThing that can take either a ThingOne or a ThingTwo. I want to implement the -thing of MultiThing that handles -thing of both ThingOne and ThingTwo. I'd like to implement against the interface, without caring about the particular implementation of ThingOne or ThingTwo.

john17:05:57

I didn't think, for -thing of MultiThing, I'd need to handle both -thing of ThingOne and -thing of ThingTwo.

john17:05:00

If I have use namespaced versions of -thing, it seems I'll have to handle each new Thing type individually.

dnolen17:05:56

@miikka there was definitely a :parallel-build case where that could happen but I believe it’s been addressed and I haven’t heard much about that since

miikka18:05:34

@dnolen, if you have circular deps, optimisations are enabled and you use :parallel-build, the compiler gets stuck. It's not a race condition, it should happen always. Try this: https://github.com/miikka/cljs-circular-deps-repro

john18:05:36

All of the clojurescript examples of defprotocol/deftype I can find out there tend to do things all in one namespace. Hard to find examples of where polymorphism is leveraged across namespaces.

miikka18:05:10

@dnolen, the parallel compiler threads wait until all their dependencies are compiled before proceeding, but of course with circular deps this never happens. Before this it probably worked because this was run before the parallel compiling starts: https://github.com/clojure/clojurescript/commit/9ad6d5d61cb96a9f8552489c6811a479b93f864c

dnolen18:05:14

@miika ah ok a regression. File an issue in JIRA thanks will look at when I have time.

colbydehart18:05:21

So I have a reagent/reframe app and I have a JSON file of data that i would like to load into application state. Is there any way to read a static JSON file in CLJS? In ES6 i would just do import "./data.json" with an appropriate webpack loader.

Roman Liutikov18:05:44

@colbydehart you could use a macro for this

Roman Liutikov18:05:01

;; macro
(defmacro defjson [name path]
  `(def ~name (js/JSON.parse ~(slurp path))))

;; usage
(defjson data "data.json")

colbydehart18:05:57

thanks, I’ll play around.

Roman Liutikov18:05:45

@colbydehart don’t know if you are familiar with macros in CLJS, but keep in mind that they should be declared in a separate *.clj namespace

colbydehart18:05:09

so I can put that in my core.clj and then use it in my regular cljs files?

Roman Liutikov18:05:54

yes, just require it as usual function and use where needed

colbydehart18:05:39

oh awesome, thanks so much.

Roman Liutikov18:05:49

you are welcome 🙂

john19:05:34

Like, in core, deref is simply defined as a (-deref o) (https://github.com/clojure/clojurescript/blob/master/src/main/cljs/cljs/core.cljs#L1358) Which will polymorphically use the -deref as defined on o. But is that only working because all of the relevant -deref definitions are occurring in the same namespace? If object/deftype methods don't survive across namespace boundaries, I'm unsure of how to implement the advertised polymorphism.

noisesmith19:05:33

the method belongs to the protocol, code that wants to be polymorphic needs to utilize the namespace defining the protocol

john19:05:01

Right, and it seems as though because most code has core protocols and deftypes in scope, it seems quite easy to just make another object that has a -deref method and your downstream code can just be used by deref and @ transparently. But if different implementations of -deref exist in different namespaces, I'm just not grokking how to maintain that polymorphism.

noisesmith19:05:37

you don't need to care about the implementation namespace

noisesmith19:05:46

all that matters is the namespace with the protocol

noisesmith19:05:21

well, you need the implementation namespace in order to make an instance of the thing... but that's it

john19:05:17

That's my intuition as well. But I'm just too dense to translate that intuition into something that works.

noisesmith19:05:00

if your code uses the protocol methods, call them based on the namespace with the protocol definition

noisesmith19:05:24

ignore the namespace where the object was defined, it doesn't matter

john20:05:42

@noisesmith Okay I think I'm starting to understand...

john20:05:17

(ns thing)

(defprotocol IThingable
  (-thing [t]))

(deftype Thing [thing]
  Object
  (toString [_] (pr-str thing))
  IThingable
  (-thing [t] (str "thing " t)))

(defn thing [t] (-thing t))
(ns thing-one
  (:require [thing :as thing]))

(deftype ThingOne [t]
  Object
  (toString [_] (pr-str t))
  thing/IThingable
  (-thing [_] (str "one " (thing/-thing t))))
(ns thing-two
  (:require [thing :as thing]))

(deftype ThingTwo [t]
  Object
  (toString [_] (pr-str t))
  thing/IThingable
  (-thing [_] (str "two " (thing/-thing t))))
(ns things
  (:require [thing :as thing]
            [thing-one :as one]
            [thing-two :as two]))

(deftype Things [t]
  Object
  (toString [_] (pr-str t))
  thing/IThingable
  (-thing [_] (str "things " (thing/-thing t))))
(thing/thing (Things. (thing/Thing. 0)))
;=> "things thing 1"
(thing/thing (one/ThingOne. (thing/Thing. 1)))
;=> "one thing 1"
(thing/thing (Things. (one/ThingOne. (thing/Thing. 1))))
;=> "things one thing 1"
(thing/thing (Things. (two/ThingTwo. (thing/Thing. 2))))
;=> "things two thing 2"

john20:05:32

But that slightly surprises me, since Things's -thing is is calling the -thing of Thing, rather than -thing of ThingOne or ThingTwo, BUT the correct -thing of those deftypes are actually being called.

john21:05:30

Is that because thing/-thing in the Things definition above is actually refering to the -thing of the prototype, not the deftype, in the thing namespace?

john21:05:52

In which case, I may not need to require thing-one and thing-two in things ns (if I'm working with them in another ns) and it'll just do the right thing... I'll try that

noisesmith21:05:01

Thing doesn't own the method, I think it's clearer if the protocol namespace had no deftype in it

john21:05:26

aaaah, k

qqq21:05:51

can ajax only load xml/json, or can I also GET pngs, like GET https://the-site.com/static/img20.png

qqq21:05:01

it's from the same domain as the html/js files

john21:05:02

@noisesmith okay it's finally working. Thank you SO much for helping me finally understand this piece.

john21:05:32

been putting that off for way too long lol

crinklywrappr22:05:33

After generating the a basic luminus template with reagent, when I remove the call to render the navbar in core.cljs mount-components function, the navbar still gets rendered

crinklywrappr22:05:01

I can see Figwheel reloading the js, though

crinklywrappr22:05:29

I don't see any error messages anywhere, but maybe not looking in the right place

crinklywrappr22:05:59

can someone help me troubleshoot?

crinklywrappr22:05:23

(I'm new to cljs dev)

grierson22:05:01

How do I install and include Bootstrap into my project without relying on cdnjs for development?

dvingo22:05:39

you can npm install the package and the css and js will be under the node_modules folder

dvingo22:05:08

you can either add node_modules to your src paths or copy them to your current source paths

grierson23:05:43

So directly reference from the node_module within the HTML?

dvingo23:05:49

sorry, i think you want resource-paths (assuming you're using lein) https://github.com/technomancy/leiningen/blob/stable/sample.project.clj#L298

dvingo23:05:38

so you can add 'node_modules' to resource-paths and then the url would be /bootstrap/css/bootstrap.css

dvingo23:05:57

which the clojure code on the server should find

dvingo23:05:40

alternatively you can just copy it under the public/css directory

dvingo23:05:13

@doubleagent that's probably because an explicit call to unmount the node is needed in that case because that's a root level react render

noisesmith23:05:15

yeah, I wouldn't expect figwheel to unmount things on code reload - that sounds right now that you mention it