Fork me on GitHub
#beginners
<
2020-11-21
>
theSherwood00:11:07

Hi. I’m having lein-cljsbuild fail to compile anything and my ide is showing linting errors in the cljsbuild code. Has anybody here experienced something like that before? I’m also not getting much of a stack trace:

Nov 20, 2020 5:34:26 PM com.google.javascript.jscomp.LoggerErrorManager println
SEVERE: /Users/path/target/cljsbuild-compiler-0/cljs/core.js:3359: ERROR - Parse error. primary expression expected
case ##Inf:
      ^

Nov 20, 2020 5:34:26 PM com.google.javascript.jscomp.LoggerErrorManager printSummary
WARNING: 1 error(s), 0 warning(s)
ERROR: JSC_PARSE_ERROR. Parse error. primary expression expected at /Users/path/sine/target/cljsbuild-compiler-0/cljs/core.js line 3359 : 6

theSherwood00:11:32

I’m seeing the modules successfully compiled to js in the target directory. But they aren’t getting bundled. I’m totally thrown by this. Any help would be very appreciated.

st3fan00:11:03

I went from this

(def default-settings
  {:enabled false
   :comment nil})

(def bot
  {:name "lock-pull-request"
   :private-key (slurp (io/resource "devbots/bots/lock-pull-request.key"))
   :id 21060
   :default-settings default-settings})
To this:
(defbot lock-pull-request 21060 :default-settings {:comment nil})

st3fan00:11:12

Baby steps with macros

hiredman01:11:20

Custom def forms are almost always a bad idea

hiredman01:11:27

It moves you away from computing with values

hiredman01:11:19

5 is a value, it may have many names, it may be used directly without binding it to a name, and you'll never know

hiredman01:11:57

Make anonymous things, use the langues binding constructs to bind them to names

Audy01:11:01

How come I am still getting a lazy sequence from this. How can I make it return a just a hash map? Shouldn’t doall force the sequence to be realized?

(->> [{:a 1 :b 2} nil nil]
     (remove nil?)
     (map #(merge % {:c 3}))
     (doall)

;; ({:a 1, :b 2, :c 3})

hiredman01:11:32

A realized lazy sequence is still a lazy sequence

hiredman01:11:55

If you want the first element of a sequence use first

🙌 3
Thomas Tay02:11:26

I have been doing research on records, and I still don't understand when they are appropriate. Isn't a big thing in Clojure about avoiding concretions? I understand that records have a key use case, namely for polymorphism via protocols. But I think that can also be achieved via having some "type" key of in the map, then checking the key to call different functions, right? One downside of this is that the function becomes closed for extension (see: expression problem), but it avoids the need to create a type. I also understand that it is more performant. But if performance is not an issue, we should probably just use map, is my thoughts.

Thomas Tay02:11:16

I think I just need to see some real life examples where records are used, to get a feel for how they can be used

seancorfield04:11:02

Right, the preference is to use hash maps -- unless you need dispatch on just a single "thing" and you need performance, in which case a record can help.

seancorfield04:11:07

Records can also implement multiple protocols etc.

seancorfield04:11:44

@thomastayac_slack This is a helpful flowchart regarding Clojure data type selection https://github.com/cemerick/clojure-type-selection-flowchart

Ayden05:11:27

Hey guys 👋, I'm new here, first time asking a question. I'd greatly appreciate if someone could help me out with the most idiomatic way to do this data transformation - everything I've come up with feels extremely verbose and hard to understand. What I want to do is: apply a map fn to the first element in a collection which satisfies a given predicate. An example:

;; given the following collection:
(def coll [{:type "foo" :message ""}
           {:type "bar" :message ""}
           {:type "bar" :message ""}])

;; and assuming a function signature:
(defn map-first [fn pred coll]
  ;; ???
  )

;; I'm looking for the following sexp:
(map-first
  #(assoc % :message "hello!")
  #(= (:type %) "bar")
  coll)

;; ... to eval to:
[{:type "foo" :message ""}
 {:type "bar" :message "hello!"}
 {:type "bar" :message ""}]
What I've tried so far: • manually writing map-first in terms of loop and recur (this felt like a bad idea) • considered trying to write map-first in terms of reduce, but didn't know where to start

didibus05:11:09

(defn map-first
  [f pred coll]
  (first (reduce
    (fn [[acc done?] e]
      (if (and (not done?) (pred e))
        [(conj acc (f e)) true]
        [(conj acc e) false]))
    [[]]
    coll)))

seancorfield05:11:08

That's at least missing a )

didibus05:11:02

Typed this on my phone, so might be missing a few more ) lol, but should do the trick

seancorfield05:11:04

It also reverses the collection.

seancorfield05:11:19

The init expr should be [[]] not []

didibus05:11:48

Ah yes sorry forgot

seancorfield05:11:16

And I think it's a bit wasteful to walk the entire data structure. A loop / recur could short-circuit on the first match.

didibus05:11:50

My understanding is he wants the full collection back, but the first entry to equal pred modified

didibus05:11:07

So only faster way is mutable coll, or maybe with splitting vector

didibus05:11:29

Don't think this reverses the coll ? You sure

seancorfield05:11:49

Yes, I tried it in a REPL 😛

seancorfield05:11:26

loop / recur could at least just return (into acc coll) when it's done applying the conditional function.

Ayden05:11:45

thanks for the solution @didibus, I think I'll need to study that one a bit at the REPL :thumbsup:

seancorfield05:11:50

well, (into (conj acc (f (first coll))) (rest coll)) I guess

didibus06:11:18

Ya I thought about doing that, you can do it with reduce as well, just wrap it in a reduced to short circuit. But I wasn't confident on the phone for it, I also not sure it'll be that much faster. Since we're still O(n), it's just maybe a slightly more optimized variant.

didibus06:11:27

I'm super confused why it would reverse it. Conj on vector should add the the end... Weird

seancorfield07:11:59

You had [] initially, not [[]], so acc would be nil and (conj nil :x) produces a list not a vector. I see you edited it to have [[]]

didibus06:11:44

Ah yes, that makes sense.

seancorfield05:11:09

How about:

(->> coll
  (filter #(= "bar" (:type %)))
  (map #(assoc % :message "hello!")))
Or if you just want the first element on its own after that, just thread it into (first)

seancorfield05:11:52

That's idiomatic and not very verbose -- it says exactly what it does: find me elements that have type bar, then add the message "hello!" to those elements. And you could just add (first) if you only want the first matching element @abaayd01

seancorfield05:11:15

Now, behind the scenes, because filter and map are lazy and work with chunked sequences, they may evaluate more elements of the sequence than strictly needed to get just the first one, but if you're starting with a known, specific vector of hash maps, that's not really a big deal (just wastes some CPU cycles).

Ayden05:11:52

Thanks for the reply @seancorfield! This approach:

(->> coll
  (filter #(= "bar" (:type %)))
  (map #(assoc % :message "hello!")))
Looks like it'll filter out the first element {:type "foo"}, from the collection though, no?

seancorfield05:11:17

filter returns all the elements of a sequence that satisfy the predicate.

seancorfield05:11:02

Oh, I see what you're after now... OK, that wasn't clear from what I read...

seancorfield05:11:29

Hmm, that's kind of a weird requirement. What's the background behind that problem?

Ayden05:11:41

I thought someone might ask that question - my reduction of the original problem did feel little off, let me explain further, give me a moment.

seancorfield05:11:28

I mean, yeah, it's totally doable in several ways, but it's a bit off the beaten track...

seancorfield05:11:18

Here's a solution using loop/`recur` that doesn't walk the entire collection:

user=> (defn map-first [f pred coll]
         (loop [acc [] coll coll]
           (if (seq coll)
             (if (pred (first coll))
               (into acc (cons (f (first coll)) (rest coll)))
               (recur (conj acc (first coll)) (rest coll)))
             acc)))
#'user/map-first
user=> (map-first #(assoc % :message "hello!") #(= "bar" (:type %)) coll)
[{:type "foo", :message ""} {:type "bar", :message "hello!"} {:type "bar", :message ""}]
user=> 

👀 3
seancorfield05:11:14

That could probably be cleaned up a bit with if-let and destructuring...

seancorfield05:11:46

Here's a cleaner version:

user=> (defn map-first [f pred coll]
         (loop [acc [] coll coll]
           (if-let [[head & tail] (seq coll)]
             (if (pred head)
               (into acc (cons (f head) tail))
               (recur (conj acc head) tail))
             acc)))
#'user/map-first
user=> (map-first #(assoc % :message "hello!") #(= "bar" (:type %)) coll)
[{:type "foo", :message ""} {:type "bar", :message "hello!"} {:type "bar", :message ""}]
user=> 

Ayden05:11:03

ahhhhk, I think I see what you've done there @seancorfield: • you put the elements you've already applied the predicate to in acc • the remainder goes in coll • once you hit an element for which the pred is true you smush everything back together using into , applying f to the current element along the way

Ayden05:11:20

thanks for that, I think I'm going to rethink the original problem however, and see if i can come into it from a different angle

seancorfield05:11:49

Yup. That at least stops at the first matching element, although it still "pours" the rest of the collection into what's been built so far (but at least it doesn't apply the pred to anything else).

🙌 3
seancorfield05:11:02

This sort of problem is always a bit ugly in Clojure because the transformation is essentially stateful: it only applies to the first matching element. Making it apply to all matching elements is easy. Returning just the first matching element, transformed, is also easy.

seancorfield06:11:34

Here's an alternative, using split-with, but I haven't tested it on any edge cases:

user=> (defn map-first [f pred coll]
         (let [[prelude [matched & others]] (split-with (complement pred) coll)]
           (-> []
               (into prelude)
               (conj (f matched))
               (into others))))
#'user/map-first
user=> (map-first #(assoc % :message "hello!") #(= "bar" (:type %)) coll)
[{:type "foo", :message ""} {:type "bar", :message "hello!"} {:type "bar", :message ""}]
user=> 
@abaayd01

seancorfield06:11:53

(it doesn't account for matched & others being an empty sequence so it will produce a strange result if no elements match)

seancorfield06:11:26

(it will work correctly if the first or last element are the match tho')

seancorfield06:11:55

Here's a version of the above that works when no elements match @abaayd01:

user=> (defn map-first [f pred coll]
         (let [[prelude [matched & others :as postlude]] (split-with (complement pred) coll)]
           (-> []
               (into prelude)
               (cond-> (seq postlude) (conj (f matched)))
               (into others))))
#'user/map-first
user=> (map-first #(assoc % :message "hello!") #(= "bar" (:type %)) coll)
[{:type "foo", :message ""} {:type "bar", :message "hello!"} {:type "bar", :message ""}]
user=> (map-first #(assoc % :message "hello!") #(= "quux" (:type %)) coll)
[{:type "foo", :message ""} {:type "bar", :message ""} {:type "bar", :message ""}]
user=> (map-first #(assoc % :message "hello!") #(= "quux" (:type %)) (conj coll{:type "quux"}))
[{:type "foo", :message ""} {:type "bar", :message ""} {:type "bar", :message ""} {:type "quux", :message "hello!"}]
user=> (map-first #(assoc % :message "hello!") #(= "foo" (:type %)) (conj coll{:type "quux"}))
[{:type "foo", :message "hello!"} {:type "bar", :message ""} {:type "bar", :message ""} {:type "quux"}]
user=> 

Ayden06:11:33

Hmm, I think I prefer this alternative @seancorfield I feel like the mechanism of splitting the coll and re-stitching it back together with f applied to the matched element is much clearer without the if 's and recur floating around.

Ayden06:11:48

I've not used cond-> before though, so I'll need to study this version a bit I think

didibus06:11:22

@abaayd01 Using into will still walk the entire collection though. I'm thinking, if you know you have a vector, the fastest way might be:

(defn map-first
 [f pred coll]
 (let [idx
       (reduce
        (fn [i e]
         (if (pred e)
          (reduced i)
          (inc i)))
        0
        coll)]
  (assoc coll idx (f (get coll idx)))))
(map-first
 #(assoc % :name "yay")
 #(= :foo (:type %))
 [{:type :bar :name ""}
  {:type :bar :name ""}
  {:type :foo :name ""}
  {:type :bar :name ""}
  {:type :foo :name ""}])
(map-first
 #(assoc % :name "yay")
 #(= :foo (:type %))
 [{:type :bar :name ""}
  {:type :bar :name ""}
  {:type :foo :name ""}
  {:type :bar :name ""}
  {:type :foo :name ""}])

Ayden06:11:42

ahk I see, @didibus , so here, you're first walking up till you hit the matched element to pull out the index - then you use assoc passing in the idx to update the element in place.

Ayden06:11:56

Also, I didn't realise you could use assoc like that! I assume it has constant time complexity in that instance?

didibus06:11:49

Ya, so this should be O(idx)

didibus06:11:10

There I fixed it

seancorfield06:11:50

That's quite slick. What does it do if the collection doesn't contain a match tho'?

didibus07:11:51

😝, you're asking too much for what I can do on my phone haha

seancorfield07:11:15

Answer: same as my initial split-with version -- it sticks a new entry on the end:

user=> (map-first #(assoc % :message "hello!") #(= "quux" (:type %)) coll)
[{:type "foo", :message ""} {:type "bar", :message ""} {:type "bar", :message ""} {:message "hello!"}]

didibus07:11:29

I think if you made the whole thing an if-let and returned the original coll on else then it just return the coll unmodified

seancorfield07:11:10

You'd have to check idx against (count coll) but, yeah, you'd need a conditional for the no-match case.

didibus07:11:58

Ideally though, if you care about performance and you're going to do this on large data. You probably want some kind of indexed structure, like a dataframe.

didibus07:11:42

It'll treat your structure more like a database table. And you can have indexes on the columns. And then you can run queries on it and all which leverages the index.

seancorfield07:11:36

(that strays from "beginners" tho'... which sort of goes back to me saying earlier that this is a problem that is a bit off the beaten track)

didibus07:11:54

Agree, it's a use case I haven't seen often.

Lyderic Dutillieux15:11:33

Hey guys, I'm trying to use prefix when requiring my libs within a file with (ns my.ns (:require ...)). Let's say I want to require [vendor1.library1 :as lib1] and [vendor1.library2 :as lib2] What is the correct syntax to prefix vendor1 ?

(:require
   [vendor1
     [library1 :as lib1]
     [library2 :as lib2]])
This one just above doesn't seem to work. The doc seems to only give the syntax for the (require ...) function but I couldn't find any hint for the (ns _ (:require ...)) macro. Any idea ?

Audy15:11:36

I think you already had it

(:require
  [vendor1.library1 :as lib1]
  [vendor1.library2 :as lib2]

Lyderic Dutillieux15:11:06

Actually, I require more than 2 libraries, so I want to use prefixing, just like here https://clojure.org/reference/libs#_prefix_lists

delaguardo16:11:40

The syntax you are using is correct. Why you think it is not working?

Lyderic Dutillieux17:11:20

I'm using it in cljs with shadow-cljs. I guess this is an issue with the code analyser of shadow-cljs

Lyderic Dutillieux17:11:40

Thanks for the feedback

dpsutton17:11:09

Cljs does not support the prefix style for requiring

👍 3
Lyderic Dutillieux17:11:01

I didn't know, thanks 😉

Audy15:11:10

If I wanted to dissoc :a in here and keep the type of the lazy sequence, how should I return the result?

(->> [{:a 1 :b 2} nil nil]
     (remove nil?)
     (map #(merge % {:c 3}))

;;=> ({:a 1 :b 2 :c 3})

andy.fingerhut15:11:11

The example you show has a result that is a sequence containing one map. Do you want to handle a situation where the sequence can contain multiple maps, and you want to dissoc key :a in all of those maps?

andy.fingerhut15:11:34

If yes, then you can add (map #(dissoc % :a)) as another step in your ->> expression, at the end.

andy.fingerhut15:11:45

You could also change your last step from the current (map #(merge % {:c 3})) to (map #(dissoc (merge % {:c 3}) :a))

Audy20:11:21

Yes it should return one map and I am going to run checks on it.`(dissoc (merge % {:c 3}) :a))` should work. Thanks for the replies!~

hequ19:11:51

Am I missing something about repl use here? If I have dev-server.clj which runs pedestal rest api with jetty and then that file refers to main.clj and eventually that refers to routes.clj . If I do changes to routes.clj and then evaluate dev-server.clj , shouldn’t that also evaluate all those transient dependencies as well? I’m using vscode with calva running nrepl (clj). Now I have to evaluate all these files to see those changes actually running in repl. Is it working now as intended or do I have some configuration issue somewhere?

hequ20:11:19

apparently there is a library for this: tools.namespace

Carlo19:11:10

If I do lein new figwheel hello_world , and then cd hello_world && lein figwheel, the repl won't connect to my browser, and I get the error message Loading failed for the <script> with source ""

Carlo19:11:22

in fact, no hello_world.js is generated in that folder. Maybe I need to run something the first time?

st3fan21:11:17

Is it possible to discover the version from project.clj at runtime?

seancorfield21:11:57

@st3fan In general, no, because project.clj isn't going to be available to an application running in an uberjar.

seancorfield21:11:42

What people tend to do is have the version in a file that is part of the code (or a resource that is read in) and have escaped code in project.clj to read it into the defproject form (because Leiningen will execute ~-escaped forms in project.clj)

st3fan21:11:37

cool that is a good hint

st3fan21:11:59

i usually have a /version endpoint in my APIs so that it is easy to find out the project versin, git tag, etc.

seancorfield21:11:54

We have a version.txt file in /resources in our main app at work, and we read it in at startup.

seancorfield21:11:41

(we also have a /version endpoint on it, to return the current API version)

seancorfield21:11:01

We also have an endpoint on every app that lists the git tag, Clojure version, and other "interesting" stuff that we might want from a monitoring p.o.v.

seancorfield21:11:34

(we're running Clojure 1.10.2-alpha4 in production according to the probe URL 🙂 )