Fork me on GitHub
#beginners
<
2021-02-23
>
solf03:02:30

When generating a pom.xml using tools deps (`clojure -X:deps mvn-pom`), does it retrieve the version from somewhere? There doesn't seem to be something like a :version option in deps.edn.

seancorfield04:02:34

@dromar56 it generates a minimal pom.xml, it's possible that the mvn-pom exec function takes various keyword arguments... I don't know that they're documented...

seancorfield04:02:42

Let me check the source code...

seancorfield04:02:51

Hmm, I believe this is the function https://github.com/clojure/tools.deps.alpha/blob/f3a2fbb426ae65d5a5c79a756f3b95b463f334a4/src/main/clojure/clojure/tools/cli/api.clj#L88 -- so the only thing it current takes is :argmaps to help specify how to build the dependencies that appear in the pom.xml file.

seancorfield04:02:52

If you are using depstar to build JAR files, it can update the pom.xml file for you -- it supports a :version exec argument, and will update pom.xml if you say :sync-pom true.

solf04:02:49

Ah thanks, I do use depstar, I'll switch to that

alexmiller04:02:32

the version (like most things) should be set in the pom.xml and then deps will sync to it

solf05:02:12

Got it, thanks. I wasn't sure where was the idiomatic place to put the version, this answers it.

alexmiller04:02:03

the underlying pom gen/sync code does actually have the ability to set this, and I expect this to be part of other forthcoming work

toot 3
seancorfield05:02:41

@alexmiller I was actually a bit surprised when I looked at mvn-pom and it didn't seem to expose the group, artifact, or version that would be generated into the initial pom.xml...

seancorfield05:02:29

(so I'm glad to hear that is a future possibility)

Pragyan Tripathi06:02:52

I have a function defined in a namespace e.g., user/test-func I wish to send a keyword :user/test-func over network and resolve it into the defined function to execute. Is there a way to do it?

phronmophobic06:02:18

maybe something like:

user> (-> :clojure.string/join
          symbol
          requiring-resolve) 
#'clojure.string/join

Pragyan Tripathi09:02:18

I think this only works in clj... I am trying to do it in cljs..

Yang Xu06:02:06

Hi, Who can explain this code to me? (:keyword things) What does this code do? I saw this code when invokes

; invoke
(driver.u/database->driver (:database preprocessed)
; defined
(def ^{:arglists '([database-or-id])} database->driver
  (memoize/ttl database->driver* :ttl/threshold 1000))
in Metabase source code. Does (:database preprcoessed) is a function invocation or just function parameters?

andy.fingerhut06:02:06

Keywords in Clojure behave like functions when used as the first element of an expression, and cause themselves to be looked up in a map. They aren't 100% the same in all ways, but (:kw foo) is similar in behavior to (get foo :kw)

Yang Xu06:02:57

Thank you, I got it.

bhaim12308:02:28

Hi I need to make println thread-safe. I sew an example using *out* but I was wondering if there is another option? What I need to do is to make sure my print is thread-safe, while other threads my print in their own way (which I cannot control)

Dave Suico10:02:21

Hello World, Does anyone know an article/video/tutorial on how to work with google sheets? I am task to read/write sheets using clojure. Thank you!

robertfw10:02:49

a quick bit of googling turned this up, though it is 5 years old. There is a comment from 2019 saying things were still working https://tech.metail.com/reading-google-sheets-from-clojure/

sb10:02:36

Yes, robertfw link is great, plus if you would like to understand how could you generate a token and work with that.. good point this gist (I modified for my goals and worked fully .. in the last month) https://gist.github.com/arohner/8d94ee5704b1c0c1b206186525d9f7a7

Adrian Imanuel11:02:47

i use this, but it's still need login each time we run the code, i got stuck on the authentication token & refresh token...

(ns asdf.core
  (:require [clojure.edn :as edn]
            [clojure.test :refer :all]
            [google-apps-clj.google-drive :refer :all :as gdrive]
            [google-apps-clj.google-sheets-v4 :refer :all :as gsheets]
            [google-apps-clj.credentials :as gauth]))


(defn login
  []
  (gsheets/build-service ""
                       "_la38KD6Hc73wPtwEAJZYOfJ"))

;this is just a test to connect
(login)

;here's the function to get the spreadsheet info/cell values
(gsheets/get-spreadsheet-info
 (login)
 "16qb3Zg4kdD62y7cf0oQ4TrRmq890V19fpnu7hmRpTeg")

(gsheets/get-cell-values
 (login)
 "16qb3Zg4kdD62y7cf0oQ4TrRmq890V19fpnu7hmRpTeg"
 ["Sheet1!B9:B11"])
the dependencies are these
[google-apps-clj "0.6.1"]]

Adrian Imanuel11:02:27

@sb how to run that in a project?

sb11:02:49

I reopen the project and I check it. One moment.

Adrian Imanuel11:02:00

http://cool.no hurries, i'm thankful for that...

Adrian Imanuel11:02:50

I'm using this https://github.com/SparkFund/google-apps-clj and i couldn't get the token & refresh token yet, been learning & working for the gsheet for about 1 week lol... i'm so frustrated here's my failure https://youtu.be/7GAcYNR6LFM

sb11:02:11

@UCJCPTW8J I check now the code. I used via lein-env the credentials : api-key and :creds {patht o service json …d49a5.json}. I downloaded from Google Dev console.

sb11:02:45

and with that I can generate token like “ya27.c…. xYA4Haq”

sb11:02:38

so no browser interaction

sb11:02:10

(defn load-creds
  "Takes a path to a service account .json credentials file"
  [secrets-json-path]
  (-> secrets-json-path slurp (json/parse-string keyword)))


(defn create-claim [creds & [{:keys [sub] :as opts}]]
  (let [claim (merge {:iss   (:client_email creds)
                      :scope (str/join " " scopes)
                      :aud   ""
                      :exp   (-> 1 time/hours time/from-now)
                      :iat   (time/now)}
                     (when sub
                       ;; when using the Admin API, delegating access, :sub may be needed
                       {:sub sub}))]
    (-> claim jwt/jwt (jwt/sign :RS256
                                (-> creds
                                    :private_key
                                    (#(StringReader. %)) (#(key/pem->private-key % nil))))
        (jwt/to-str))))


(defn request-token [creds & [{:keys [sub] :as opts}]]
  (let [claim (create-claim creds opts)
        resp (http/post ""
                        {:form-params {:grant_type "urn:ietf:params:oauth:grant-type:jwt-bearer"
                                       :assertion  claim}
                         :as          :json})]
    (when (= 200 (-> resp :status))
      (-> resp :body :access_token))))

sb11:02:37

(request-token (load-creds creds)) you get the token and you can use.. the v4 api and v3 api different.. eg

(http/post ""
             {:accept           :json
              :throw-exceptions false
              :headers          {"Authorization" (str "Bearer " token)}
              :form-params      form-params
              :content-type     :json}))

sb11:02:11

I used for analytics exports, but I think, that is similar to other things

Adrian Imanuel11:02:57

let me digest this... sorry I'm still newbie ahahah

sb11:02:05

(http/get (str "" api-key)
           {:accept           :json
            :headers          {"Authorization" (str "Bearer " token)}
            :content-type     :json})

sb11:02:18

That is a simple GET request, you can test how to create post request via this documentation.. do you need add valid credentials too etc (allow this request type etc)

Adrian Imanuel12:02:20

I'll try to run the code, and if there's any stuck question arises i'll try to ask... thanks a lot for sharing @sb

sb12:02:48

Ok, the key the credentials and the scopes. I hope it will work for you!

🙏 3
Dave Suico16:02:43

Thanks, everyone I appreciate all your response and now I ended up using an API key rather than looking for an official client library.:thumbsup:

Adrian Imanuel16:02:59

hi, can someone using windows help, how to escape space character? sadly I couldn't avoid this

(create-claim
 (load-creds "/Users/Adrian Imanuel/Google Drive/3. Clojure/Projects/newgsheet/client_secret_1080463043693.json"))
i got an error due to using space
Execution error (NullPointerException) at java.io.StringReader/<init> (StringReader.java:50).
Cannot invoke "String.length()" because "s" is null

ghadi16:02:06

type *e to see the full exception trace, and put that in a snippet here (Ctrl-Shift-Space)

ghadi16:02:45

(I'm not sure I agree with the problem analysis around space characters)

Adrian Imanuel16:02:04

(ns newgsheet.core
  (:require [cemerick.url :as url]
            [cheshire.core :as json]
            [clj-jwt.core :as jwt]
            [clj-jwt.key :as key]
            [clj-time.core :as time]
            [clj-http.client :as http]
            [clojure.string :as str])
  (:import java.io.StringReader))

(defn request-token [creds & [{:keys [sub] :as opts}]]
  (let [claim (create-claim creds opts)
        resp (http/post ""
                        {:form-params {:grant_type "urn:ietf:params:oauth:grant-type:jwt-bearer"
                                       :assertion  claim}
                         :as          :json})]
    (when (= 200 (-> resp :status))
      (-> resp :body :access_token))))

(request-token
 (load-creds "c:\\Users\\Adrian Imanuel\\Google Drive\\3. Clojure\\Projects\\newgsheet\\client_secret_1080463043693.json"))

sb16:02:12

before request-token missing three parts: load-creds, scopes, create-claim fns

;;; Example code for calling Google apis using a service account.

(defn load-creds
  "Takes a path to a service account .json credentials file"
  [secrets-json-path]
  (-> secrets-json-path slurp (json/parse-string keyword)))

;; list of API scopes requested, e.g. 
(def scopes [""
             ""])

(defn create-claim [creds & [{:keys [sub] :as opts}]]
  (let [claim (merge {:iss (:client_email creds)
                      :scope (str/join " " scopes)
                      :aud ""
                      :exp (-> 1 time/hours time/from-now)
                      :iat (time/now)}
                     (when sub
                       ;; when using the Admin API, delegating access, :sub may be needed
                       {:sub sub}))]
    (-> claim jwt/jwt (jwt/sign :RS256 (-> creds :private_key (#(StringReader. %)) (#(key/pem->private-key % nil)))) (jwt/to-str))))

Adrian Imanuel16:02:35

@sb yes, sorry i'm cutting the code, it's my mistake, pls ignore that

ghadi16:02:04

need the stack trace @adrianimanuel

Adrian Imanuel16:02:01

here the stack trace

;; => #error {
 :cause "\\Users\\AdrianImanuel\\Google Drive\\3. Clojure\\Projects\\newgsheet\\client_secret_1080463043693.json (The system cannot find the path specified)"
 :via
 [{:type java.io.FileNotFoundException
   :message "\\Users\\AdrianImanuel\\Google Drive\\3. Clojure\\Projects\\newgsheet\\client_secret_1080463043693.json (The system cannot find the path specified)"
   :at [java.io.FileInputStream open0 "FileInputStream.java" -2]}]
 :trace
 [[java.io.FileInputStream open0 "FileInputStream.java" -2]
  [java.io.FileInputStream open "FileInputStream.java" 211]
  [java.io.FileInputStream <init> "FileInputStream.java" 153]
  [$fn__11496 invokeStatic "io.clj" 229]
  [$fn__11496 invoke "io.clj" 229]
  [$fn__11409$G__11402__11416 invoke "io.clj" 69]
  [$fn__11508 invokeStatic "io.clj" 258]
  [$fn__11508 invoke "io.clj" 254]
  [$fn__11409$G__11402__11416 invoke "io.clj" 69]
  [$fn__11470 invokeStatic "io.clj" 165]
  [$fn__11470 invoke "io.clj" 165]
  [$fn__11422$G__11398__11429 invoke "io.clj" 69]
  [$reader invokeStatic "io.clj" 102]
  [$reader doInvoke "io.clj" 86]
  [clojure.lang.RestFn invoke "RestFn.java" 410]
  [clojure.lang.AFn applyToHelper "AFn.java" 154]
  [clojure.lang.RestFn applyTo "RestFn.java" 132]
  [clojure.core$apply invokeStatic "core.clj" 667]
  [clojure.core$slurp invokeStatic "core.clj" 6942]
  [clojure.core$slurp doInvoke "core.clj" 6942]
  [clojure.lang.RestFn invoke "RestFn.java" 410]
  [newgsheet.core$load_creds invokeStatic "form-init17689491352809040359.clj" 51]
  [newgsheet.core$load_creds invoke "form-init17689491352809040359.clj" 48]
  [newgsheet.core$eval7297 invokeStatic "form-init17689491352809040359.clj" 97]
  [newgsheet.core$eval7297 invoke "form-init17689491352809040359.clj" 96]
  [clojure.lang.Compiler eval "Compiler.java" 7177]
  [clojure.lang.Compiler eval "Compiler.java" 7132]
  [clojure.core$eval invokeStatic "core.clj" 3214]
  [clojure.core$eval invoke "core.clj" 3210]
  [nrepl.middleware.interruptible_eval$evaluate$fn__6180$fn__6181 invoke "interruptible_eval.clj" 87]
  [clojure.lang.AFn applyToHelper "AFn.java" 152]
  [clojure.lang.AFn applyTo "AFn.java" 144]
  [clojure.core$apply invokeStatic "core.clj" 665]
  [clojure.core$with_bindings_STAR_ invokeStatic "core.clj" 1973]
  [clojure.core$with_bindings_STAR_ doInvoke "core.clj" 1973]
  [clojure.lang.RestFn invoke "RestFn.java" 425]
  [nrepl.middleware.interruptible_eval$evaluate$fn__6180 invoke "interruptible_eval.clj" 87]
  [clojure.main$repl$read_eval_print__9086$fn__9089 invoke "main.clj" 437]
  [clojure.main$repl$read_eval_print__9086 invoke "main.clj" 437]
  [clojure.main$repl$fn__9095 invoke "main.clj" 458]
  [clojure.main$repl invokeStatic "main.clj" 458]
  [clojure.main$repl doInvoke "main.clj" 368]
  [clojure.lang.RestFn invoke "RestFn.java" 1523]
  [nrepl.middleware.interruptible_eval$evaluate invokeStatic "interruptible_eval.clj" 84]
  [nrepl.middleware.interruptible_eval$evaluate invoke "interruptible_eval.clj" 56]
  [nrepl.middleware.interruptible_eval$interruptible_eval$fn__6211$fn__6215 invoke "interruptible_eval.clj" 152]
  [clojure.lang.AFn run "AFn.java" 22]
  [nrepl.middleware.session$session_exec$main_loop__6278$fn__6282 invoke "session.clj" 202]
  [nrepl.middleware.session$session_exec$main_loop__6278 invoke "session.clj" 201]
  [clojure.lang.AFn run "AFn.java" 22]
  [java.lang.Thread run "Thread.java" 832]]}

ghadi16:02:30

ok cool: now that you have the stack trace (in the thread) you can read it like so:

ghadi16:02:51

--------top of stack trace ----- opening a file that doesn't exist reading a file http://clojure.java.io/reader related frames clojure.core/slurp newgsheet.core/load-creds (this is the first part of your code that can be recognized) newgsheet.core$eval7297 (something you typed or evaluated in the REPL or editor) clojure/eval clojure.main/repl stuff nREPL crap for editor integration ---------outermost call -------

ghadi16:02:32

you absolutely do not need any code to debug this @sb 🙂

👍 3
ghadi16:02:01

call load-creds again, but make it (prn the-filename) before it slurp s the file

ghadi16:02:13

You should be able to then call (slurp the-filename) and see the exact same error without your other code involved

Adrian Imanuel16:02:18

i change this :

(defn load-creds
  [secrets-json-path]
  (-> secrets-json-path slurp (json/parse-string keyword)))
to
(defn load-creds
  [secrets-json-path]
  (-> secrets-json-path prn (json/parse-string keyword)))
am i correct?

ghadi16:02:49

no because prn returns nil

ghadi16:02:15

what I want you to do is isolate the problem -- FileNotFound and reproduce it

Adrian Imanuel16:02:48

yes, got the points but didn't get the coding

ghadi16:02:20

if you already know what is being passed in, you can do (slurp secrets-json-path) otherwise

(defn load-creds
  [secrets-json-path]
  (prn secrets-json-path) ;; add this
  (-> secrets-json-path slurp (json/parse-string keyword)))

Adrian Imanuel16:02:59

(load-creds "\\Users\\Adrian Imanuel\\Google Drive\\3. Clojure\\Projects\\newgsheet\\client_secret_1080463043693.json")
"\\Users\\Adrian Imanuel\\Google Drive\\3. Clojure\\Projects\\newgsheet\\client_secret_1080463043693.json"

;; => {:installed {:client_id "", :project_id "clojuresheet-trial", :auth_uri "", :token_uri "", :auth_provider_x509_cert_url "", :client_secret "_la38KD6ZYOfJ", :redirect_uris ["urn:ietf:wg:oauth:2.0:oob" ""]}}
this is the result of above codes

ghadi16:02:21

ok now that the string printed, does it look correct? does that file exist? the folder in between "Google Drive" and "Projects" looks suspicious

Adrian Imanuel16:02:23

in my understanding, it's successfully read the path,

Adrian Imanuel16:02:53

the folder called "3. Clojure"

ghadi16:02:58

yeah, it looks like it read the file

dpsutton16:02:20

this comes up a few times but please be aware of the consequences of sharing your client_id and client secret for cloud services

3
Adrian Imanuel16:02:42

no i already aware of that, i've edited/cut some characters

👍 3
ghadi16:02:19

ok if you call request-token on the result of load-creds, do you get the same error?

ghadi16:02:03

Spoiler alert: all I'm going to suggest is to break the problem down systematically

Adrian Imanuel16:02:13

;; => #error {
 :cause "Cannot invoke \"String.length()\" because \"s\" is null"
 :via
 [{:type java.lang.NullPointerException
   :message "Cannot invoke \"String.length()\" because \"s\" is null"
   :at [java.io.StringReader <init> "StringReader.java" 50]}]
 :trace
 [[java.io.StringReader <init> "StringReader.java" 50]
  [newgsheet.core$create_claim$fn__7268 invoke "form-init17689491352809040359.clj" 66]
  [newgsheet.core$create_claim invokeStatic "form-init17689491352809040359.clj" 66]
  [newgsheet.core$create_claim doInvoke "form-init17689491352809040359.clj" 54]
  [clojure.lang.RestFn invoke "RestFn.java" 410]
  [newgsheet.core$eval7327 invokeStatic "form-init17689491352809040359.clj" 211]
  [newgsheet.core$eval7327 invoke "form-init17689491352809040359.clj" 211]
  [clojure.lang.Compiler eval "Compiler.java" 7177]
  [clojure.lang.Compiler eval "Compiler.java" 7132]
  [clojure.core$eval invokeStatic "core.clj" 3214]
  [clojure.core$eval invoke "core.clj" 3210]
  [nrepl.middleware.interruptible_eval$evaluate$fn__6180$fn__6181 invoke "interruptible_eval.clj" 87]
  [clojure.lang.AFn applyToHelper "AFn.java" 152]
  [clojure.lang.AFn applyTo "AFn.java" 144]
  [clojure.core$apply invokeStatic "core.clj" 665]
  [clojure.core$with_bindings_STAR_ invokeStatic "core.clj" 1973]
  [clojure.core$with_bindings_STAR_ doInvoke "core.clj" 1973]
  [clojure.lang.RestFn invoke "RestFn.java" 425]
  [nrepl.middleware.interruptible_eval$evaluate$fn__6180 invoke "interruptible_eval.clj" 87]
  [clojure.main$repl$read_eval_print__9086$fn__9089 invoke "main.clj" 437]
  [clojure.main$repl$read_eval_print__9086 invoke "main.clj" 437]
  [clojure.main$repl$fn__9095 invoke "main.clj" 458]
  [clojure.main$repl invokeStatic "main.clj" 458]
  [clojure.main$repl doInvoke "main.clj" 368]
  [clojure.lang.RestFn invoke "RestFn.java" 1523]
  [nrepl.middleware.interruptible_eval$evaluate invokeStatic "interruptible_eval.clj" 84]
  [nrepl.middleware.interruptible_eval$evaluate invoke "interruptible_eval.clj" 56]
  [nrepl.middleware.interruptible_eval$interruptible_eval$fn__6211$fn__6215 invoke "interruptible_eval.clj" 152]
  [clojure.lang.AFn run "AFn.java" 22]
  [nrepl.middleware.session$session_exec$main_loop__6278$fn__6282 invoke "session.clj" 202]
  [nrepl.middleware.session$session_exec$main_loop__6278 invoke "session.clj" 201]
  [clojure.lang.AFn run "AFn.java" 22]
  [java.lang.Thread run "Thread.java" 832]]}
oh no

ghadi16:02:50

ok 🙂 so create-claim is barfing

ghadi16:02:25

now we need more code @sb 🙂

Adrian Imanuel16:02:25

I'm sorry not being complete, thought i can pick some function because it has the same error, load-creds then create-claim then request-token

(ns newgsheet.core
  (:require [cemerick.url :as url]
            [cheshire.core :as json]
            [clj-jwt.core :as jwt]
            [clj-jwt.key :as key]
            [clj-time.core :as time]
            [clj-http.client :as http]
            [clojure.string :as str])
  (:import java.io.StringReader))

(defn load-creds
  "Takes a path to a service account .json credentials file"
  [secrets-json-path]
  (prn secrets-json-path)
  (-> secrets-json-path slurp (json/parse-string keyword)))


(defn create-claim [creds & [{:keys [sub] :as opts}]]
  (let [claim (merge {:iss   (:client_email creds)
                      :scope (str/join "")
                      :aud   ""
                      :exp   (-> 1 time/hours time/from-now)
                      :iat   (time/now)}
                     (when sub
                       ;; when using the Admin API, delegating access, :sub may be needed
                       {:sub sub}))]
    (-> claim jwt/jwt (jwt/sign :RS256
                                (-> creds
                                    :private_key
                                    (#(StringReader. %)) (#(key/pem->private-key % nil))))
        (jwt/to-str))))



(defn request-token [creds & [{:keys [sub] :as opts}]]
  (let [claim (create-claim creds opts)
        resp (http/post ""
                        {:form-params {:grant_type "urn:ietf:params:oauth:grant-type:jwt-bearer"
                                       :assertion  claim}
                         :as          :json})]
    (when (= 200 (-> resp :status))
      (-> resp :body :access_token))))

ghadi16:02:38

I would try to lift out (-> creds :private_key (#(StringReader. %)) (#(key/pem->private-key % nil)))) (jwt/to-str)) into the let binding

ghadi16:02:47

it is very hard to understand

ghadi16:02:42

from the trace above, StringReader is saying, "you gave me nil / null to read"

ghadi16:02:59

so, is (-> creds :private_key) nil?

ghadi16:02:24

(As a style note, don't mix -> and #(....) )

ghadi16:02:40

the creds being passed in to create-claim (coming from load-creds ) doesn't appear to have a :private_key

ghadi16:02:23

I gotta run but: break your problems down and debug like a scientist, even if it's slow

Adrian Imanuel16:02:58

alright, i got the point now

Adrian Imanuel16:02:07

will go slow, line by line

Adrian Imanuel17:02:51

thanks a lot for the lesson @U050ECB92 confusedparrot going to digest this

Adrian Imanuel14:02:33

@U050ECB92 reporting that i figured out the missing private_key, it wasn't the code that has problem, but my generated json was the problem. but how did you figure out the missing :private_key? which part of the error stack tells? tbh I still couldn't read all of that

NoahTheDuke19:02:18

given that I have two maps created from frequencies (so a map of string to the number of that string in the original collection), is there an easy way to subtract the second map from the first, creating a new map that has the subtracted total of the combined keys?

NoahTheDuke19:02:30

i have a reduce call that does the trick, but it's nice to use built-ins if they exist

hiredman19:02:05

merge-with -

andy.fingerhut19:02:20

That will work well if a key appears in both maps. If it only occurs in the second, I think it will give you the value from the second map. If you want negative of the value from the second map, you'll need a different way.

💯 3
andy.fingerhut19:02:48

(it also works well if a key appears in only the first map)

Thomas Tay19:02:00

You probably want merge-with @nbtheduke. But I have a qn: how are you sure the keys will be the same? If there are different keys, how do you plan to handle it? If not, then the two maps must have been generated from the same input, is there a way you can generate it only once?

bhaim12319:02:36

Hi I need to make `println` thread-safe. I sew an example using `*out*` but I was wondering if there is another option? What I need to do is to make sure my print is thread-safe, while other threads my print in their own way (which I cannot control)

alexmiller19:02:42

println is thread safe

alexmiller19:02:57

so what behavior are you seeking?

alexmiller19:02:13

do you mean "not interleaved with other threads printing"?

grazfather19:02:29

with-out-str?

bhaim12319:02:30

I would like to send a long text to println and make sure the whole text is printed without any other massing the string.

alexmiller19:02:11

the easiest way is to make the string, then println the full string (don't let println make it)

alexmiller19:02:52

(println (str/join " " [my args to println])) instead of (println my args to println)

bhaim12319:02:13

Got it, thanks a lot!

alexmiller19:02:39

println will then just print the string to stdout and flush, should not be any interleaving

👍 6
seancorfield19:02:52

That's not quite sufficient, since println prints the string and the newline separately so you can still get some interleaving. I've found that to completely avoid potential interleaving, you need to do (print (str (str/join " " [my args to println]) "\n"))

👀 3
blak3mill3r21:02:26

So that's why newlines get interleaved when printlning from many threads. Thanks for elucidating

blak3mill3r21:02:48

That's a bit of a nasty behavior

seancorfield22:02:59

Yeah, it surprised me when I first encountered it... 👀

seancorfield19:02:40

(it's an edge case so you won't encounter it unless you're printing at a high rate from multiple threads)

bhaim12319:02:43

Thanks, that is the case I have. This is the service startup where many threads write at the same time in high rate for a few good seconds

NoahTheDuke20:02:41

@hiredman and @thomastayac_slack meant to respond sooner but got called away. those are both good answers, but i determined i need to exclude negative numbers, so the reduce call ended up being the best method

👍 3
👀 3
Eric Ihli21:02:46

How do you change the in-repl representation of a custom data type? I was hoping there's an interface that provides something like Pythons __repr__ or Clojures Object (toString [_] ,,,) but that REPLS use when displaying a value?

alexmiller21:02:21

you can extend print-method for that (but it's preferred to print as Clojure data)

🙏 3