Fork me on GitHub
#beginners
<
2022-06-17
>
zakkor07:06:20

how can I tell clj to load a specific namespace by default? I want to do something like clj -?:api , which should pop me into a REPL where the src/api.clj file has been loaded

practicalli-johnny18:06:35

Loading is possible, changing into that namespace is not. To load namespace, Create a file that defines a user namespace, e.g. dev/user.clj and add the API namespace as a require. As long as the REPL process is started with dev directory on the class path, API namespace will be loaded when REPL starts. This will not put the REPL into the api namespace (only Leiningen does that) More details here https://practical.li/clojure/clojure-cli/projects/configure-repl-startup.html

zakkor08:06:05

thank you! the user.clj file is what I wanted

👍 1
Bart Kleijngeld07:06:24

I have a situation where the presence of :a in a map m needs to be handled one way, but if there's a :b then that needs to be handled another way. I came up with:

(condp #(%1 %2) m
  :a :>> handle-a
  :b :>> handle-b)
I like this, except for the #(%1 %2) which isn't very readable. I was hoping to just use get, but the arguments are in the wrong order for that to work. Of course I can use (fn [k m] (k m)), but that is overly verbose. Any ideas?

plexus07:06:07

I would keep it simple

(cond
  (contains? m :a)
  (handle-a m)
  (contains? m :b)
  (handle-b m))

Bart Kleijngeld07:06:44

Using cond it would actually look like this:

(cond
  (contains? m :a)
  (handle-a (m :a))
  (contains? m :b)
  (handle-b (m :b)))
I don't like the repeated map accessing there.

plexus07:06:02

Because it's more typing or because you're concerned about performance?

plexus07:06:28

you can do something like this, but it all seems very premature optimization

(if-let [a (:a m)]
  (handle-a a)
  (if-let [b (:b m)]
    (handle-b b)))

Bart Kleijngeld07:06:03

Oh no performance is not the issue. Neither is the typing, unless the amount of clauses would increase a lot. I don't dislike the cond solution necessarily, it's just that I feel the condp was almost perfect (if get could be used) and hoped someone had some way of flipping the arguments or something 🙂 Thanks though!

jumar09:06:58

Btw contains? is better because that allows you to detect the case when :a is in the map but is nil whereas you won't notice it when using get (unless you use the default value arg)

👍 1
Bart Kleijngeld09:06:20

Yes, that's a good point

plexus09:06:01

Alternatively you can use find+`val`

(if-let [ma (find m :a)]
  (handle-a (val ma))
  (if-let [mb (find m :b)]
    (handle-b (val mb))))

1
Benjamin09:06:02

(with-open
  [in (io/input-stream (str "/home/benj/repos/clojure/currency-data-to-s3/" "test.csv"))
   boas (java.io.ByteArrayOutputStream.)
   gos (java.util.zip.GZIPOutputStream. boas)]
  (io/copy in gos)
  (spit "lul.gz" boas)

  )
jo what am I doing wrong? My goal is to read a text file, gzip in memory and upload that

plexus09:06:25

Comparing the version with and without boas it seems only the first dozen bytes get written

(with-open [in (io/input-stream "/tmp/xx.csv")
            out (io/output-stream "/tmp/lul.gz")
            gos (java.util.zip.GZIPOutputStream. out)]
  (io/copy in gos))
(with-open [in (io/input-stream  "/tmp/xx.csv")
            boas (java.io.ByteArrayOutputStream.)
            gos (java.util.zip.GZIPOutputStream. boas)]
  (io/copy in gos)
  (spit "/tmp/lul.gz" boas))

Benjamin09:06:56

yea 1) works and 2) has a bug

plexus09:06:03

Seems there's some flushing that needs to happen, if you close the gzipoutputstream you get more data, but somehow it's not valid

plexus09:06:40

(let [boas (java.io.ByteArrayOutputStream.)]
  (with-open [in (io/input-stream  "/tmp/xx.csv")
              gos (java.util.zip.GZIPOutputStream. boas)]
    (io/copy in gos))
  (spit "/tmp/lul.gz" (.toString boas)))

Ben Sless09:06:17

Does it work if you try translating this example directly to Clojure? https://www.geeksforgeeks.org/gzipoutputstream-class-in-java/

Benjamin09:06:28

(let [file "/tmp/xx.csv"
        fis (io/input-stream file)
        gzip-file "/tmp/lul.gz"
        fos (io/output-stream gzip-file)
        gzip-out (java.util.zip.GZIPOutputStream. fos)]

    (io/copy fis gzip-out)

    (.close fis)
    (.close fos)
    (.close gzip-out)

    )
hm, still something is wrong. This version writes 10bytes

Benjamin09:06:02

(with-open [in (io/input-stream "/tmp/xx.csv")
              out (io/output-stream "/tmp/lul.gz")
              gos (java.util.zip.GZIPOutputStream. out)]
    (io/copy in gos))
this is the only version I know that works

Ben Sless11:06:14

Looks right to me, anything wrong with it?

Pedro Boschi12:06:27

GZipOutputStream needs "to do some work" to properly wrap the resulting gzip file, which is done only when the stream is closed (`.close` is called). So the "gos" needs to be closed before you can write the generated content to a file somewhere. This worked for me

(with-open [baos (java.io.ByteArrayOutputStream.)]
 (with-open
   [in  (io/input-stream (str "/home/benj/repos/clojure/currency-data-to-s3/" "test.csv"))
    gos (java.util.zip.GZIPOutputStream. baos)]
   (io/copy in gos)
   )
 (spit "lul.gz" (.toByteArray baos))
)

Benjamin13:06:08

@U03KDUXD9PB do you still have the version that worked?

(with-open [baos (java.io.ByteArrayOutputStream.)]
 (with-open
   [in  (io/input-stream (.getBytes "fo"))
    gos (java.util.zip.GZIPOutputStream. baos)]
   (io/copy in gos))
 (spit "/tmp/lul.gz" (.toByteArray baos)))
tried this and it doesn't work on my end

Benjamin13:06:24

@UK0810AQ2 yea that works for when writing a file, but I initially wanted to upload the gzip bytes directly

Pedro Boschi13:06:20

@U02CV2P4J6S The problem is with spit. Depending on the bytes generated, it believes the data is over when there are still bytes remaining on the stream. This worked with several files of mine:

(with-open [baos (java.io.ByteArrayOutputStream.)
            fos (io/output-stream "/home/boschi/temp/lul.gz")]
  (with-open
    [in  (io/input-stream "/home/boschi/temp/template.html")
     gos (java.util.zip.GZIPOutputStream. baos)]
    (io/copy in gos))
  (io/copy (java.io.ByteArrayInputStream. (.toByteArray baos))
           fos))

Benjamin13:06:24

it explains why copyin to a file output stream worked and all spitting did not

Pedro Boschi16:06:21

just fixing a mistake in my code: there is no need to create an inputstream with the byte array.... just write the byte-array to the outputstream.... picard-facepalm

(with-open [baos (java.io.ByteArrayOutputStream.)
            fos (io/output-stream "/home/boschi/temp/lul.gz")]
  (with-open
    [in  (io/input-stream "/home/boschi/temp/template.html")
     gos (java.util.zip.GZIPOutputStream. baos)]
    (io/copy in gos))
  (.write fos (.toByteArray baos)))

nottmey12:06:20

Oh well, passing or as a param for a function seems not to work 🙈 (which is I think a very obvious thing one would do from time to time)

Can't take value of a macro: #'clojure.core/or

nottmey12:06:12

anyone knows a way around this?

delaguardo12:06:13

why do you want to pass it as an argument?

nottmey12:06:36

(update-in m [...] or default-value)
to make sure a value is set in a map without additional if

delaguardo12:06:04

(update-in m [...] #(or % default-value))

👍 1
delaguardo12:06:44

general workaround is to wrap or with function 🙂

👌 1
nottmey12:06:48

frustrating ^^

nottmey12:06:14

thank you ☺️

Benjamin13:06:32

the important difference is that or does not evaluate all it's arguments (conditional evaluation). This is one of the reasons to have a macro.

👍 1
Benjamin13:06:13

fnil fits this update-in case

nottmey13:06:35

How would you do (update m :k #(or "value I want to have set, if :k has no value")) with fnil ?

Benjamin13:06:53

(update {} :k (fnil (fn [curr] curr) "default"))
  {:k "default"}

Benjamin13:06:55

(update {:k 10} :k (fnil (constantly "there was a k") "default"))
  {:k "there was a k"}

nottmey13:06:02

ah, hmm, I see

vlad_poh17:06:21

when should you do try/catch in clojure?

Alex Miller (Clojure team)17:06:58

if you can catch (and handle) an exceptional event. for instance: retrying, logging, passing an event along in a more useful way such as catching and returning an http response with the proper error code

Alex Miller (Clojure team)17:06:01

in general, it's best to minimize explicit try/catch to high level code and not try to do this at 10 places in the stack

👍 2
1
Alex Miller (Clojure team)18:06:31

may I suggest the new parse-double function added to clojure.core in 1.11?

😮 1
Alex Miller (Clojure team)18:06:11

it will return null in the case of an invalid double string, so probably you'll want (or (parse-double n) -1)

Alex Miller (Clojure team)18:06:37

or maybe you want -1.0 - seems a little weird to parse to either a double or long -1

vlad_poh18:06:37

Excel saves dates as integer or a string in a format “01/01/2020” hence the weird function but parse-double is exactly what i need.