Fork me on GitHub
#babashka
<
2023-12-15
>
leifericf13:12:27

I have a bunch of functions like these:

(defn load-token [env token-name]
  (let [token (token-exists? env token-name)]
    (when-not (and token (token-valid? (:token token)))
      (println (format "Token %s/%s is not cached or has expired."
                       (name env) token-name))
      (refresh-token env (filter-scope env token-name)))
    (println (format "Loading token %s/%s from cache."
                     (name env) token-name))
    (token-exists? env token-name)))
They print stuff to the console to give the user feedback on what's happening. But these prints also clutter up the functions themselves. Printing also adds potential side effects to otherwise pure functions. Is there a way to move the prints out of the functions themselves? :thinking_face:

borkdude13:12:10

This doesn't seem bb specific, might as well post in #C03S1KBA2 or #C053PTJE6 or so

👍 2
borkdude13:12:27

Welcome to discuss here too of course

leifericf14:12:11

True, good point!

mmer14:12:15

I would love to know the answer to this. I moved to using tap> but this still clutters the functions.

borkdude14:12:25

I usually do it as written above, but usually don't write to stdout, as this may mess with tools that read output.

(binding [*out* *err*] ...)
is what I do to print to stderr

borkdude14:12:53

tap> is for debugging I would say, not for "production" error messages

mmer14:12:06

But with tap you can separate out the result of the tap without changing the running program by defining the tap you want to use. SO I think it might make sense in production

borkdude14:12:41

if you control who calls add-tap, but this isn't true for library code

mmer14:12:38

I agree. As a subject the idea of logging being a side effect is an issue for so called pure functions.

borkdude14:12:46

to make the function pure you could return {:errors [..] :result ..} but then you would have to handle those errors somewhere

💡 1
mmer14:12:57

That is a useful thought. it is easy to forget that we can return structure results in that way. Some languages allow multiple return values to replicate this.

leifericf14:12:40

I’m in the habit of printing info-level stuff to the end user of my script, but perhaps that's too much. I suppose I could limit my printing to errors when something goes wrong. That would reduce the number of prints.

mmer14:12:45

Its almost like an option to switch on tracing somewhat like the +X for bash scripts would be good. Or the equivalent of the AOT in bb at script runtime.

Crispin15:12:37

you could also pass in a function to invoke for status/logging. Like tap>, but the caller specifies the function. If the passed in arg is nil don't call it.

mmer15:12:14

@UKH2HDSQH Does Clojure cope calling nil as a function with arguments? Otherwise you would have to include lots of if's or when's

borkdude15:12:29

no it doesn't. but you can default to identity or some other function that does nothing

💡 1
Crispin15:12:39

you would need to boilerplate that away somehow. You could be lazy and make identity the default for the arg, but you might want another function or macro that puts in the boilerplate

mmer15:12:53

Does SCI do any runtime optimizations?

Crispin15:12:22

(`fnil` is another function that can help with handling nils)

borkdude15:12:32

@U4C3ZU6KX what exactly do you mean?

mmer15:12:11

The issue with having lots of conditionals is that they will cost time, so if you included a macro that expanded to cond and if the first conditional was say the nil function so that you could see that when nil is passed in the SCI would see that it results in no code and so it could remove the cond altogether.

borkdude15:12:07

it actually does something like this for if yes

borkdude15:12:25

it's not a runtime optimization but a "compile" time optimization

mmer15:12:30

Great, I thought you would have thought of that !

borkdude15:12:15

so when you write (if nil 1 2) it will actually only ever run "2"

borkdude15:12:52

I guess another optimization would have been possible for the reverse: (if :else 1 2) could expand directly into 1

mmer15:12:46

Would this work with the executable bundle? I am assuming it would as you bundle bb into the the package so the script is still compiled on the fly.

borkdude15:12:09

yes, it works exactly the same

Brandon Olivier18:12:29

I’m trying to download a jpeg from a url with babashka, but when I write it to a file, I get cannot read errors from Preview (macos) and the contents look different from when I download the image via a browser. Any idea what’s going wrong?

lukasz18:12:18

Are you following redirects? That would be my first guess. Second, some services will block unknown user agents and deny traffic. Lastly, some images might be accessible only when you're authenticated.

Brandon Olivier18:12:30

I don’t think it’s that, because the files are very similar. I suspect it’s an encoding issue, because the real file has weird block characters in Emacs, and the babashka downloaded one has control sequences, like \357

Brandon Olivier18:12:16

Yeah, spit isn’t preserving something, if I slurp/spit the real file, I get the contents of the downloaded one.

borkdude18:12:38

you aren't using slurp are you? this is for text, not binary data

borkdude18:12:05

use babashka.http-client + :as :bytes to download binary data

Brandon Olivier18:12:12

I’m using http/get

borkdude18:12:38

also don't use spit for binary data

Brandon Olivier18:12:49

I’m using fs/write-bytes

Brandon Olivier18:12:16

Amazing, that worked!