Fork me on GitHub
#beginners
<
2021-08-04
>
Njeri01:08:12

I’m writing a clojurescript library from scratch (i.e. without a leiningen template) and I’m getting the following error:

Failed to compile build :dev from ["src/cljs"] in 8.073 seconds.
----  Could not Analyze  dev-resources/public/js/out/cljs/pprint.cljs  ----

  Invalid :refer, var cljs.core/IWriter does not exist 
Can someone help me figure out why I’m getting this error? In a previous thread someone said this error came from a directory structure issue, but I can’t find how that applies to my project.

dpsutton02:08:30

Can you post your project.clj file? It kinda looks like you have the output on the classpath

Njeri02:08:59

project.clj

(defproject cljs-spotify-sdk "0.0.1-SNAPSHOT"
  :description "FIXME: write description"
  :url ""
  :license {:name "Eclipse Public License"
            :url ""}
  
  :dependencies [[org.clojure/clojure "1.10.3"]
                 [org.clojure/clojurescript "1.10.773"]]
  
  :plugins [[lein-cljsbuild "1.1.7"]]
  
  :clean-targets ^{:protect false}

  [:target-path
   [:cljsbuild :builds :app :compiler :output-dir]
   [:cljsbuild :builds :app :compiler :output-to]]
  
  :resource-paths ["dev-resources/public"]
    
  :figwheel {:http-server-root "dev-resources/public"
             :server-port 3449
             :nrepl-port 7002
             :nrepl-middleware [cider.piggieback/wrap-cljs-repl]}
  
  :cljsbuild {:builds {:dev
                       {:source-paths ["src/cljs"]
                        :compiler
                        {:output-to "dev-resources/public/js/cljs_spotify_sdk.js"
                         :output-dir "dev-resources/public/js/out"
                         :optimizations :none
                         :source-map true
                         :pretty-print true}
                        :figwheel
                        {:on-jsload "cljs-spotify-sdk.core/load-sdk"}}}}
  
  :profiles {:dev {:dependencies [[binaryage/devtools "1.0.2"]
                                  [cider/piggieback "0.5.2"]
                                  [figwheel-sidecar "0.5.20"]
                                  [nrepl "0.8.3"]]
                   :plugins [[lein-figwheel "0.5.20"]]}})

seancorfield04:08:21

(nm, figured out it was just weird layout)

dpsutton04:08:13

I think it’s because the output is into the dev resources but it’s been so long since I’ve set up cljs build

seancorfield04:08:42

Yeah, I'm super rusty. I last did serious cljs back in 2013/2014 and then tried to pick it up again maybe six months ago. Totally different world, hahaha!

pinkfrog14:08:26

What protocol do you use in a microservice setting, plain http or grpc? I’d like to know the performance boost of grpc is worth the effort.

delaguardo14:08:14

grpc runs on top of http.

pinkfrog15:08:19

What grpc library are you using?

JoshLemer15:08:45

Is there anything similar to the & rest vector destructuring, but for maps? Sort of like (but obviously not exactly)

(let [{a :a b :b & rest} {:a "a" :b "b" :c "c" :d "d"}]
  {:a a :b b :rest rest})

=> {:a "a" :b "b" :rest {:c "d" :d "d"}}

dpsutton15:08:17

you can use {:keys [a b] :as m} and m will be bound to the entire map. But maps with extra values usually flow through a system just fine

JoshLemer16:08:01

yeah that's kind of what I thought would be the philosophy, that there isn't usually need to remove keys. Thanks!

Panagiotis Mamatsis17:08:06

Hello everyone! I want to ask a question regarding the loop/recur form. Why people using Clojure prefer the loop instead of creating another recursive function? If my question sounds stupid please bear with me...I am trying to learn Clojure by myself! 😊

Russell Mull17:08:13

It's because Clojure does not have tail recursion, also called tail call elimination or tail call optimization.

Russell Mull17:08:12

If you write a recursive function, then the stack will grow with every recursive call

Russell Mull17:08:57

The loop/recur system is a workaround for this; it lets you write loops in the recursive style, without doing actual recursion on the function level

Russell Mull17:08:26

(Tail call elimination could not be implemented for Clojure because it would have broken compatibility with Java libraries)

Russell Mull17:08:34

Also, this is not a stupid question. It is in fact one of the first questions that many experienced lisp developers will ask about Clojure. So don't worry!

❤️ 4
clojure-spin 2
dgb2318:08:32

Also note that loop acts as a target for recur but it is not strictly necessary. If you omit loop then recur targets the enclosing function. But many recursive procedures have some initialisation that you don’t want to expose to the caller, instead of writing a separate function, you can simply embed loop for this purpose.

Jakub Šťastný18:08:32

@U7ZL911B3 could you please elaborate on "(Tail call elimination could not be implemented for Clojure because it would have broken compatibility with Java libraries)". I'd appreciate any details.

dgb2318:08:50

The JVM simply does not eliminate tail calls. So if you write a recursive procedure in tail position, it will create a stack frame for each recursive call.

dgb2318:08:37

tail call elimination would turn that into something like a while loop that doesn’t fill up the stack each time it loops around.

dgb2318:08:12

There is a group that is working to change this: https://wiki.openjdk.java.net/display/loom/Main And here is where SICP discusses this concept (scheme has TCO): https://mitpress.mit.edu/sites/default/files/sicp/full-text/book/book-Z-H-11.html#%_sec_1.2.1

Russell Mull18:08:27

The way Rich put it is that the target for Clojure is "stack compatibility" with Java - Clojure functions and Java functions can completely coexist on the call stack. Callbacks between the two languages work as expected. Since the JVM has no native tail call facilities, implementing them on the JVM would have meant Clojure functions would have to be something different: not JVM functions. This would significantly hurt interop.

👍 4
Panagiotis Mamatsis18:08:53

Good evening @U7ZL911B3 and thank you so much for your detailed explanation! I am really happy that my question made some sense. About the tail call optimization for the JVM I know that it's one of the things which are missing. But since it's not a feature which is there isn't the loop/recur form just syntactic sugar?

Russell Mull18:08:23

loop/recur is more than syntactic sugar - (recur) /must/ be a tail call, and tail call optimization /does/ happen in that limited context.

Panagiotis Mamatsis18:08:41

Oh! If let's say as a thought experiment, Clojure didn't care about compatibility with the Java libraries...this thing can be still implemented on the language level?

Russell Mull18:08:37

This important distinction is that 'recur' is not a function call. It can only exist in tail position, and it can only 'call' the enclosing loop point (either a loop form or a fn, which is an implicit loop point).

Russell Mull18:08:09

Yes, it could totally be done.

Jakub Šťastný18:08:21

Right, so basically JVM doesn't support it, that's the issue here?

Russell Mull18:08:36

That's the reason for the original design decision, yes.

Jakub Šťastný18:08:02

OK, got it. Thanks!

Russell Mull18:08:09

It's not clear to me what will happen if the JVM grows TCO support. loop/recur has turned out to address a whole lot of those use cases, without much fuss, but not all of them (mutual recursion is one). So it would be nice to have real TCE in clojure. But there are also significant backwards compatibility concerns, and Clojure likes to maintain backwards compatibility for quite a while.

Panagiotis Mamatsis18:08:10

Oh! I see! I am glad to find all these little happy surprises! I thought that tail call optimization could not be done in Clojure because the JVM didn't supported it and thus the loop/recur form was just syntactic sugar! I am super glad I was completely wrong about it!!!

Panagiotis Mamatsis18:08:54

Hi @U01EFUL1A8M and thank you also for your input! Yes...Project Loom will be opening up many possibilities for the JVM not only for TCE but also green threading too!!!

Noah Bogart18:08:35

is it possible to destructure a & blah rest argument? something like (defn foo [a & [b]] (println b)). i want to be able to pass in 1 or 2 arguments and have the second one be nil or the object passed in and not a list of the object

walterl18:08:28

Yes. Your example works exactly like that 😝

manutter5118:08:42

It’s possible but it may have unintended consequences.

manutter5118:08:34

I say that trying to remember the blog post or whatever that says why destructuring & args might cause you grief down the road.

walterl18:08:10

Also, it will allow you to pass in > 2 args too, and all those after the second will be thrown away

walterl18:08:36

Unless you do [a & [b & rest]]

manutter5118:08:54

Ah yes, that was one of the possible unintended consequences, you might drop arguments you didn’t intend to.

Noah Bogart18:08:55

huh, i must have mistyped something cuz i thought i tried it in my repl

Noah Bogart18:08:01

thanks! i’ll try it again

manutter5118:08:14

(defn foo [a & [b]]
  (pr-str {:a a :b b}))
=> #'user/foo
(foo 1)
=> "{:a 1, :b nil}"
(foo 1 2)
=> "{:a 1, :b 2}"
(foo 1 2 3)
=> "{:a 1, :b 2}"

Noah Bogart18:08:44

excellent, thank you!

manutter5118:08:43

(defn foo
  ([a] (pr-str {:a a}))
  ([a b] (pr-str {:a a :b b})))
=> #'user/foo
(foo 1)
=> "{:a 1}"
(foo 1 2)
=> "{:a 1, :b 2}"
(foo 1 2 3)
Execution error (ArityException) at user/eval39896 (form-init3538426389865140913.clj:1).
Wrong number of args (3) passed to: user/foo

manutter5118:08:24

(Just for comparison)

👍 2
Noah Bogart18:08:30

i’m working with a multimethod, so i can’t do overloading, otherwise i’d do it as you have

👍 2
walterl19:08:50

@UEENNMX0T With "overloading", do you mean multi-arity defs? Because those seem to work for multimethods.

walterl19:08:04

Unless you're working with protocols...

Noah Bogart19:08:50

do you have a demonstration? the docs don’t say anything about how it works

walterl19:08:25

Tried out destructuring and multi-arity, and it seems to work as expected:

🎉 2
Noah Bogart19:08:11

hot damn, that’s nice

walterl20:08:01

varargs don't work for protocol methods, though

Noah Bogart20:08:34

yeah, i saw that

Jakub Šťastný18:08:40

What is an uberjar? OK, it's the sources packed into .jar, but more specifically? What's the difference normal pack into jar/uberjar? I know very little about Java.

ghadi18:08:59

it's the sources, dependencies, plus usually *.class files from compiling your clojure code

Franco Gasperino19:08:57

any recommended tutorials on setting up a socket repl for production deployment tasks?

seancorfield19:08:21

@franco.gasperino What sort of "production deployment tasks" are you asking about?

Franco Gasperino19:08:30

I was hoping to launch an alias using :main-opts ["-m" "my.ns"] and pair it with :jvm-opts to launch the socket repl.

Franco Gasperino19:08:41

is it as straitforward as that seems?

seancorfield19:08:27

Yes, although we don't use the CLI to launch stuff in production. We build and deploy uberjar files and start them with java -jar and specify the Socket REPL stuff as a regular JVM option.

Franco Gasperino19:08:53

before i go uberjar route, im just attempting to run the application on jvm startup (eval all required forms, fn entrypoint, etc) while still providing an interface where i can probe the running application via repl

Franco Gasperino19:08:05

wow ok, it works as expected

Franco Gasperino19:08:28

that is great. my brain hurts from the possibilities

Franco Gasperino19:08:55

is there a warning list on what you should not do in these configurations? I assume that a connected session could evaluate an existing form (runtime changes?). Are interactions with the reader atomic?

seancorfield19:08:37

Well, there's the usual caveat of "changing code that is running, live in production is potentially dangerous" 🙂 If you eval anything that hangs up the REPL (e.g., trying to (println (range)) or some such), you're going to hang up your production app. If you eval anything that stops the process, it'll stop the app. And (obviously?) any evaluations you do that modify the database or filesystem etc are going to happen on the live system.

seancorfield19:08:53

I haven't verified that def is atomic/"safe" but we only do this on a relatively low-traffic app these days and we're not seen a problem due to just that aspect.

seancorfield19:08:28

(we've messed up the production app a couple of times and needed to jump on the server and restart it... but "great power" and "great responsibility" etc 🙂 )

Franco Gasperino19:08:07

i dont have an immediate case, but only pondering...

(defn evens-only [x] (even? x))
  (defn pos-only [x] (pos-int? x))
  (def pipeline (comp (filter pos-only) (filter evens-only)))
  (def channel (clojure.core.async/chan 1 pipeline))

  (clojure.core.async/>!! channel 2)
  (clojure.core.async/<!! channel)
;; Socket repl session..
=> (in-ns 'blah')
=> (defn- after-5-only [x] #(> 5 x))
=> (def pipeline (comp (filter pos-only) (filter evens-only) (filter after-5-only))

Franco Gasperino19:08:15

that type of ad-hoc mutation

Franco Gasperino19:08:38

runtime limited deployment work

seancorfield20:08:25

Remember that with def, the value will be compiled into code that uses it so, in general, when you def a value (rather than defn a function), the existing code will not see your change.

Franco Gasperino20:08:38

yes, i do understand that

Franco Gasperino20:08:39

either a value or an expression which returns a value, the value would be retained even if the function was changed

Franco Gasperino20:08:19

perhaps a transducer wasn't the best example of my head scratching

Franco Gasperino20:08:02

ive been using your recommendation of rich comments instead of direct repl evals.

Franco Gasperino20:08:46

however, i was curious how to probe at a live running production app. so far, i can create CLI alias to run the app and combine it with a socket repl definition to connect to it

Franco Gasperino20:08:29

looks good. boxing my testing atom swap! reset! operations, it looks sane

Vincent Cantin19:08:25

What is the correct form in CLJS for calling a JS function with a variable number of parameters? I am trying to call https://developer.mozilla.org/en-US/docs/Web/API/Element/before on a list of DOM elements. (apply .before target params) does not compile.

Vincent Cantin19:08:25

I found two solutions: • (-> target .-before (.apply target (into-array params)))(apply js-invoke target "before" params)

Jakub Šťastný19:08:59

Why does (let [fn-ref #'+] (fn-ref 1 2)) work? (Correctly produces 3.) I knew (let [fn-ref +] ...) would work, but didn't expect the #'fn-name to work for invocation (`(fn-name)`) as well. (Turns out one can get fn metadata AND call it at the same time, as in (let [fn-ref #'+] (println (:doc (meta fn-ref))) (fn-ref 1 2)). That saved me a lot of headache, but I still don't know why the hell does it work.

Russell Mull20:08:42

This is the same reason you can use keywords in the function position of an invocation: https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/Keyword.java#L24

Jakub Šťastný20:08:44

That roughly translates to "are callable" I take it?

Russell Mull20:08:50

yes, that's right

Russell Mull20:08:57

You can make your own thing work that way, if you like.

Jakub Šťastný20:08:13

"You can make your own thing work that way, if you like." Any example of that?

Russell Mull20:08:41

goodness... I feel like I've done it before, but quickly deleted the code before anybody saw it 🙂

dpsutton20:08:20

((reify clojure.lang.IFn (invoke [_] "hi i'm callable")))

Jakub Šťastný20:08:48

Great, thank you 🙏:skin-tone-3:

noisesmith01:08:20

sometimes when refactoring it makes sense to replace a function with a record with an IFn implementation (like a closure, but you can create new closures with different "locals")

noisesmith01:08:17

(ins)user=> (defrecord Foo [a b] clojure.lang.IFn (invoke [this] (+ a b)))
user.Foo
(ins)user=> ((->Foo 1 2))
3
(ins)user=> (-> (->Foo 1 2) (update :a + 39) (#(%)))
42

varun20:08:38

im currently going through Clojure for the Brave and True and something isn't clicking for me:

(defn comparator-over-maps
  [comparison-fn ks]
  (fn [maps]
    (zipmap ks
            (map (fn [k] (apply comparison-fn (map k maps)))
                 ks))))
   
(def min (comparator-over-maps clojure.core/min [:lat :lng]))
(def max (comparator-over-maps clojure.core/max [:lat :lng]))
I thought the point of apply is to invoke a function that typically expects a collection over a variadic number of arguments. What does apply comparison-fn (map k maps)) get you instead of (comparison-fn (map k maps)) ?

dpsutton20:08:26

(min [1 2 3]) returns [1 2 3] as it is saying "if there's only one element, it is the min." (min 1 2 3) returns 1 for obvious reasons. If you have a collection [1 2 3], how would you call min on it's elements as if they were arguments? (apply min [1 2 3]) is effectively (min 1 2 3)

varun20:08:05

oh. i see. i didnt realize apply could be used to explode a collection.

varun20:08:16

i think in my head i thought apply was doing the opposite. collecting rest args into a collection so that a function can treat a variadic number of args as if it were a collection

dpsutton20:08:42

that is effectively its purpose. (apply f x y coll) -> (f x y c1 c2 ...) where c1, etc are the members of the collection

👍 3