Fork me on GitHub
#beginners
<
2021-01-14
>
Joe00:01:40

Just watched Sean’s REPL talk using VS Code and Clover. If I’m pretty happy with my Calva set up is there any argument in favour of switching? Or is more a personal preference / devil you know sort of thing?

seancorfield00:01:17

@allaboutthatmace1789 Mostly personal preference -- as I said in the talk, I want a setup that works with just a socket REPL and no additional dependencies in my project, so I have the exact same experience with any Clojure process running (since I can start a socket REPL via a JVM option -- not code).

seancorfield00:01:45

Everything I showed can be done with (almost) any other editor/integration setup.

Joe00:01:24

That makes sense, thanks!

zackteo05:01:38

Hi everyone, am wondering what is the best way to approach this problem I have. I have a list of data like so '({ :lrow 0, :col 0, :lcol 2, :row 0 } { :lrow 2, :col 0, :lcol 1, :row 1 } { :lrow 2, :col 2, :lcol 2, :row 1 })) my function will receive data like ...

{:coord {:row 0, :col 0, :sheet "Sheet1"},
           :value "ABC",
           :formula nil,
:style {:bold true}}
and I want to transform the data to be the following (when :row and :col correspond to an entry in the list) Aka add in :lrow and :lcol
{:coord {:row 0, :col 0,
         :lrow 0, :lcol 2,
         :sheet "Sheet1"},
           :value "ABC",
           :formula nil,
:style {:bold true}}
What might be the best way to approach this?

zackteo05:01:12

I thought of using sets but doesn't fit exactly(?)

phronmophobic06:01:58

if you construct a map so that you can look up the lrow,lcol coordinates by row, col

zackteo06:01:43

what do you mean? 😮

phronmophobic06:01:55

it should be easy to check the map with the row, col of the :coord map to add lrow and lcol if it exists

phronmophobic06:01:08

you want to construct a map like:

{[0 0] {:lrow 0, :col 0, :lcol 2, :row 0},
 [1 0] {:lrow 2, :col 0, :lcol 1, :row 1},
 [1 2] {:lrow 2, :col 2, :lcol 2, :row 1}}

phronmophobic06:01:30

so you can do something like:

(def coord-index {[0 0] {:lrow 0, :col 0, :lcol 2, :row 0},
                  [1 0] {:lrow 2, :col 0, :lcol 1, :row 1},
                  [1 2] {:lrow 2, :col 2, :lcol 2, :row 1}})

(def test-val {:coord {:row 0, :col 0,
                       :lrow 0, :lcol 2,
                       :sheet "Sheet1"},
               :value "ABC",
               :formula nil,
               :style {:bold true}})

(let [{{:keys [row col]} :coord} test-val]
  (if-let [{:keys [lrow lcol]} (get coord-index [row col])]
    (update-in test-val [:coord]
               assoc :lrow lrow :lcol lcol)
    test-val))

zackteo06:01:59

Cool! That destructuring really helps I forgot that I could do that! And didn't think/know that I can use the row and col as an index in such a way And wasn't sure how to do the update

zackteo06:01:04

Thanks a bunch!

zackteo06:01:12

Gotta slow digest it

phronmophobic06:01:47

the example code I wrote is pretty dense. let me know if it would help if I unpacked it more.

👍 3
zackteo06:01:38

Managed to unpack it once I figured out how the destructuring worked :)

😄 3
Sergej Koščejev07:01:50

I have a file looking like this:

(ns stuff)

(ns stuff-test (:require stuff) (:use clojure.test))

(with-test
  (defn sum [s]
    (+ s 2))
  (is (= 2 (sum 1))))
and I'm asking CIDER to run all tests in project. But it's telling me that there are no tests. What am I doing wrong?

zackteo08:01:27

Your test file test/project/core_test.clj should look like

(ns project.core-test
  (:require [clojure.test :refer :all]
            [project.core :refer :all]))

(deftest a-test
  (testing "FIXME, I fail."
    (is (= 0 1))))
And your main file doesn't need any test stuff

zackteo08:01:49

Not too sure where you got with-test from

delaguardo08:01:10

cider by default is looking for deftest or defspec in outer forms of namespace. then it extract a name associated with that form and run (test (var symbol)) that means with-test is not tracked by cider at all

delaguardo08:01:56

consider to use deftest

delaguardo08:01:17

also multiple ns is not really a proper way to create namespaces

(ns stuff)

(defn x [] "!")

(ns sandbox.foo)

(defn x [] "@")

(x)

(stuff/x)
this is working but I believe will break namespace loading (eg. requiring stuff from another namespace)

Sergej Koščejev09:01:04

I changed the file to this:

(ns stuff)

(ns stuff-test (:require stuff) (:use clojure.test))

(defn sum [s]
  (+ s 2))

(deftest addition
  (is (= 2 (sum 1))))
But the tests are still not being run, with the same message ("No assertions (or no tests) were run.Did you forget to use 'is' in your tests?")

Sergej Koščejev09:01:50

re multiple ns: deleting the (ns stuff) above doesn't help either. (I wanted to have multiple namespaces in one file initially and split them later.)

Sergej Koščejev09:01:56

ok it actually helped but only after I restarted CIDER

Sergej Koščejev09:01:09

so

(ns stuff-test (:use clojure.test))

(with-test
  (defn sum [s]
    (+ s 2))
  (is (= 2 (sum 1))))
works

roelof09:01:59

I have this as response from a api

({:levels
  [{:name "z1",
    :width 2810,
    :height 2177,
    :tiles

roelof09:01:31

how can I now takes the levels parts out of it and then filter for the vector where the name is z6 ?

Idan Melamed09:01:22

Might be a more idiomatic way, but here's my solution: (filter #(= "z6" (:name %)) (:levels (first a)))

Idan Melamed09:01:23

You can also do it with the thread last macro: (def a '({:levels [{:name "z1",:width 2810,:height 2177, :tiles 12}]})) (->> a first :levels (filter #(= "z6" (:name %))))

Franklin13:01:07

is there a more idiomatic way to write (first (filter filter-function sequence))

Idan Melamed13:01:39

Maybe the thread last macro? ->> sequence (filter filter-function) first

👍 3
Franklin13:01:15

this seems all right... I was just wondering if there's a built-in function that I don't know about that. seems there isn't

Max Deineko13:01:39

Ad built-in function: such a proposal was rejected at some point -- see https://clojure.atlassian.net/browse/CLJ-2056?focusedCommentId=12986

nice 6
👍 6
delaguardo13:01:18

(some #(when (filter-function %) %) sequence)
but this will fail if it suppose to return false

👍 3
andy.fingerhut14:01:51

The small library medley contains find-first, and several other useful small functions: https://github.com/weavejester/medley

👍 3
Marcus14:01:22

What is the difference between >! and put!?

Alex Miller (Clojure team)14:01:02

the first is parking (used in go blocks only) - ie park the go block until the operation can succeed. latter is async, can be used from either in or out of go block

Alex Miller (Clojure team)14:01:41

in most cases, if you are in a go block, you should prefer the parking operation (the async ops are lower level)

Marcus15:01:26

@alexmiller ah of course. For some reason I thought of parking and async as the same thing. Thanks!

eval-on-point15:01:51

This example fails to generate:

(require '[clojure.spec.alpha :as s])

(s/def :person/id string?)
(s/def :robot/id number?)

(s/def ::person
  (s/keys :req-un [:person/id]))

(s/def ::robot
  (s/merge ::person
           (s/keys :req-un [:robot/id])))

(gen/sample (s/gen ::robot))
Is this behavior expected? I thought that one of the benefits of merging map specs would be that you could overwrite defaults specs for certain keys

Alex Miller (Clojure team)16:01:29

no, that's not expected to be supported

Alex Miller (Clojure team)16:01:41

you've written a spec that has incompatible options for the same key

eval-on-point16:01:18

Okay, thanks! So if I understand correctly, the spec merge is semantically different than core merge. Core merge overwrites keys left to right, while spec merge tries to take the union of the map specs being merged. Sometimes, a union of specs can create an impossible to satisfy predicate

eval-on-point16:01:03

Is the union of two specs achieved using s/and?

Alex Miller (Clojure team)16:01:41

If you want something that satisfies both, yes

Alex Miller (Clojure team)16:01:55

But note that s/and flows conformed values

Alex Miller (Clojure team)16:01:30

So using it on maps may not be what you want (it depends)

Alex Miller (Clojure team)16:01:39

Spec 2 will have an s/and variant that does not flow conformed values (currently called s/and- but maybe to be called s/union)

Alex Miller (Clojure team)16:01:26

Just FYI, there is #clojure-spec for more detailed spec questions

eval-on-point16:01:54

Thanks Alex! I am going to make a note to document this on clojureDocs when I have a moment. Super helpful

andy.fingerhut17:01:11

Oh, http://ClojureDocs.org does have pages for each function in Clojure spec now (and probably has for a while now). I just didn't notice them.

Eamonn Sullivan17:01:09

I have a really basic question about spec. I have a library where I define some specs (s/def). They are used in the library for various things and they work fine. However, I would also like to use some of the specs from outside the library (as options or config validation, for example), but I can't figure out how to use the spec outside of the original library. Contrived example (excuse the syntax errors):

(ns my.lib (:require [clojure.spec.alpha :as s]))
(s/def ::is-thingy string?)
(defn publicfn [thing] {:pre [(s/valid? ::is-thingy thing)]} ,,,) ;; this works fine
In client:
(ns my.client (:require [my.lib :as lib] [clojure.spec.alpha :as s])

(defn my-code [options] (when (s/valid? ::lib/is-thingy (:thing options)) ,,,)) ;; this doesn't. "Unable to resolve spec: :my.lib/is-thingy.
How do I refer to the spec definitions created in the library, outside of that library?

Alex Miller (Clojure team)17:01:24

:my.lib/is-thingy is the fully-qualified name of your spec

Alex Miller (Clojure team)17:01:42

::lib/is-thingy should work as a way of creating an alias - your code works for me

3
Eamonn Sullivan09:01:49

facepalm I didn't run clojure -X:jar before installing the library...

Eamonn Sullivan18:01:20

Thanks. Must be some other issue I'm having then. Maybe it's not resolving the correct library.

GGfpc19:01:10

Is multiple maps in sequence in a threaded macro a smell?

(defn get-game-data
  [user-id]
  (let [url (str BASE_URL user-id)
        request-headers {:headers      {:accept "application/x-ndjson"}
                         :query-params {:max 20}}]
    (as-> (client/get url request-headers) games
          (:body games)
          (str/split games #"\n")
          (map json/read-str games)
          (map walk/keywordize-keys games)
          (map #(update-in % [:moves] str/split #" ") games))))
Stuff like the above. I could make it a single map, but then the code wouldn't be as clean

hiredman19:01:46

seems fine to me. I guess I would usually reach for -> or ->> before as->. some people really don't like as-> used outside of another arrow form (I don't agree with this)

seancorfield19:01:47

I'm one of those "some people". I think using as-> there makes it much harder to read...

Alex Miller (Clojure team)19:01:22

if you're talking about 10s or maybe even 100s of things, then I wouldn't worry about it, clarity wins. if trying improve perf, better to move to transducers and then multiple maps is also not a big deal.

seancorfield19:01:28

(-> (client/get url request-headers)
    :body 
    (str/split #"\n")
    (->> (map json/read-str)
         (map walk/keywordize-keys)
         (map #(update-in % [:moves] str/split #" ")))
That is much clearer to my eyes.

seancorfield19:01:28

What this highlights is that you're changing the data type in the middle of the pipeline (from a "thing" to a sequence).

benny19:01:11

@seancorfield Thank you for your Clojure London talk on REPL-Driven Programming. I learned quite a bit! (ref for others: https://www.youtube.com/watch?v=gIoadGfm5T8)

seancorfield19:01:31

Glad it was useful!

Stuart19:01:26

Yes, it was good 🙂 @vlaaad needs to take down his xmas tree though...👀🌲

vlaaad19:01:52

It's so snowy here in Stockholm, finally some Christmas weather!

roelof19:01:19

I have this code :

(->> (read-all-data)
     (filter-artObjects)
     (filter-all-objectNumbers)
     (ask-for-image-data)
     (map filter-image-url))
which produces this :
{:name "z5"
  :width 146
  :height 179
  :tiles
  [{:x 0
    :y 0
    :url
    ""}]})
clj꞉paintings.core꞉> 
is there a way I can make it output like this : {width: xx , height: xx url: xxx} ` ?

Michael Stokley20:01:33

how many tiles do you expect? always 1?

Michael Stokley20:01:02

(defn lift-first-tile [image]
  (let [tile (-> image :tiles first)]
    (-> image
        (dissoc :tiles)
        (merge tile))))

(lift-first-tile
 {:name "z5"
  :width 146
  :height 179
  :tiles
  [{:x 0
    :y 0
    :url
    ""}]})
;; => {:name "z5",
;;     :width 146,
;;     :height 179,
;;     :x 0,
;;     :y 0,
;;     :url
;;     ""}

roelof20:01:19

??? now I get everything. I only need the 3 i have said

Michael Stokley20:01:53

try select-keys, in that case 🙂

Michael Stokley20:01:52

another approach:

(defn lift-first-tile-url [image]
  (let [url (-> image :tiles first :url)]
    (assoc image :first-tile/url url)))

(lift-first-tile-url {:name "z5"
                      :width 146
                      :height 179
                      :tiles
                      [{:x 0
                        :y 0
                        :url "http:"}]})
;; => {:name "z5",
;;     :width 146,
;;     :height 179,
;;     :tiles [{:x 0, :y 0, :url "http:"}],
;;     :first-tile/url "http:"}

roelof20:01:45

(defn filter-image-url [imagedata]
  (->> imagedata
       :levels
       (sort-by :name)
       (last))
        (select-keys [:width :height]))
; Error printing return value (ArityException) at clojure.lang.AFn/throwArity (AFn.java:429).
; Wrong number of args (1) passed to: clojure.core/select-keys

Michael Stokley20:01:30

a few things jump out at me. first, i think there's a misplaced close parens in the example above - last)) closes the thread last macro before including the call to select-keys. so that may not be what you want

Michael Stokley20:01:32

second, select-keys expects the map in the first position and the key sequence in the second, but the thread last macro ->> will feed imagedata into the last argument of select-keys

Michael Stokley20:01:59

you can play with macroexpand-1 to see that:

(macroexpand-1 '(->> imagedata
                     :levels
                     (sort-by :name)
                     (last)
                     (select-keys [:width :height])))
;; => (select-keys [:width :height] (last (sort-by :name (:levels imagedata))))

roelof20:01:41

to see where the problem is yes, to see how to solve it not at the moment

Michael Stokley20:01:46

part of the difficulty can be managing the thread first vs thread last. thread last is good at handling collections but thread first is good at handling associative data structures. if you have an existing threading macro and you want to add an expression that requires the "opposite" argument placement, try writing a function

Michael Stokley20:01:55

i'll show an example in a second

Michael Stokley20:01:19

(defn add-key
  [m]
  (assoc m :c 3))

(->> [{:a 1} {:b 2}] ; <- we're starting with a collection
     (filter #(contains? % :a)) ; the vector is placed last in `filter`
     first
     add-key ; <- `assoc` wouldn't work directly because it expects the map as the first argument
     )
;; => {:a 1, :c 3}

Michael Stokley20:01:13

so, applying that strategy to your filter-image-url function:

(defn filter-image-url [imagedata]
  (let [just-width-and-height (fn [image] (select-keys image [:width :height]))]
    (->> imagedata
         :levels
         (sort-by :name)
         (last)
         just-width-and-height)))

roelof21:01:43

Can I use the same "trick" to get also the url ?

Michael Stokley21:01:55

i guess it depends on the context and the shape of the data. you originally had a thread-last form that returned a single map with the :tiles value. you could add a function lift-first-tile to the thread last pipeline. that could be one approach

Michael Stokley21:01:35

this is unrelated, but consider whether you really need to exclude the other keys. often in clojure folks find that it's useful, or at least harmless, to pass maps around that include additional data, even if it isn't strictly required.

roelof09:01:14

could be, but then I have to take them out when displaying with hiccup. Maybe I have to reconsider my code because later I would like to add the objectnumber I get from a much earlier code to the map. Right now it seems to be impossible

roelof19:01:49

here in the netherlands no snow the whole winter 🙂

vlaaad20:01:02

It was like that here last year

roelof20:01:42

but maybe net Sunday we have a few hours snow

Santiago20:01:15

I need to write something that 1) subscribes to a rabbitmq queue 2) gets a message and writes it to a TCP socket 3) TCP socket must be always open otherwise client (a Spark cluster) will close. AFAIK I need two loops concurrently: 1 to subscribe to rabbitmq and the other to serve the TCP socket. I have never done such a thing and would like to try doing in clojure. What literature do you recommend for such a problem?

hiredman20:01:27

Depending on a tcp socket to always be open is usually a bad idea

Santiago20:01:04

I think it won’t work otherwise, but in any case could you expand on why? I haven’t had experience with these topics at such a low level

hiredman20:01:25

Network hiccups are surprisingly common

Santiago20:01:34

maybe “always open” is not the correct nomenclature

Santiago20:01:04

https://spark.apache.org/docs/latest/structured-streaming-programming-guide.html#quick-example they use netcat in the tutorial. When you launch nc ... is it “always on”?

hiredman20:01:39

My guess is this is written with spark as the client, and your code as the server to deal with exactly this kind of thing

hiredman20:01:32

Or not, I dunno

Santiago20:01:38

right, so I need to a server running plus the rabbitmq subscriber which also runs in a loop 🤷

hiredman20:01:29

My comment about networking hiccups is mostly on the scale of weeks to months, if your whole whatever has a shorter runtime than that you might not care

Michael Stokley22:01:09

is there an easy way to remove all existing taps

Michael Stokley22:01:51

i find that i've polluted my set of taps with various functions over time and can't seem to remove-tap all of them

Michael Stokley22:01:10

i should just restart the repl.

noisesmith22:01:18

yeah, a big gotcha is adding a function itself as a tap, rather than the var holding the function

Michael Stokley22:01:04

@U051SS2EU do you mean i should (add-tap (var f)) instead of (add-tap f)?

noisesmith23:01:50

if you want to be able to reload f, yeah (or the equivalent of (var f), #'f)

noisesmith23:01:15

f itself is no longer equal after you run defn again, the var remains equal to the var

Michael Stokley23:01:19

thanks, this has proven really useful just now in refining what i want f to be

noisesmith22:01:32

which means when you redef the function, you no longer have the thing to call remove-tap on

dpsutton22:01:37

(reset! @#'clojure.core/tapset #{}) should work

dpsutton22:01:41

its just a private atom with a set. you can see from the source of remove-tap that there's no cleanup dance to do. just toss it

Stuart23:01:08

I'm trying to use next/jdcb for a mssqlserver. I can't see how to specify "TrustedConnection=True;" rather than providing a username and password?

seancorfield23:01:29

@qmstuart You can pass extra properties via the db-spec hash map (that you build a datasource from): {:dbtype "mssql" :dbname "your-db" :TrustedConnection true :host "..."} something like that I expect.

seancorfield23:01:47

Then call jdbc/get-datasource on that (and then use that return ds everywhere). If you're using a connection pool, you'll need to consult the docs for the pooling library in use and figure out how it passes properties to the driver manager.

seancorfield23:01:15

#sql is a good place for next.jdbc questions as well as general DB-related stuff.

Stuart23:01:04

thanks! I dunno how I didn't see :TrustedConnection. I looked at the docs, I swear 😄