Fork me on GitHub
#clojure
<
2024-03-21
>
hulunote08:03:52

Hello everyone, I have open sourced a new project Hulunote: https://github.com/hulunote/hulunote, which uses Clojure/Script, datascript, etc. Mainly hoping to combine LLM+Datalog, I hope everyone likes it:smile:

p-himik09:03:51

Hi. Great! However, it's better not to cross-post across multiple channels. Specifically for release announcements we have the #C06MAR553 channel.

hulunote21:03:19

thanks @U2FRKM4TW 🌹

👍 1
stopa15:03:28

Hey team, when doing validations in clojure, is there an idiomatic way to avoid nesting? For ex:

(if-let [code (expensive-operation-to-get-code params]
  (if-let [email (expensive-operation-to-get-email params)]
    {:code code :email email}
    {:error "missing email"})
  {:error "missing code"})
There are two ways I thought of so far: 1. I could use exceptions:
(let [code (expensive-operation-to-get-code-or-raise-error ...)
      email (expensive-operation-to-get-email-or-raise-error ...)]
But this feels like I'm overloading exceptions, and can get into tricky situations, where I raise an error that isn't caught anywhere 2. I could use lets with ifs inside:
(let [code (expensive-operation-to-get-code ...) 
      email (when code 
              (expensive-operation-to-get-email ...)]
--- So far, I am leaning towards 1. raise exceptions, but my spidey senses are telling me it may lead me down a wrong path. What do you think?

lukasz15:03:49

It's really contextual, but I usually write variant 1 in handler-y type functions (because of logging), and variant 2 in more business logic (when I only need the final outcome to tell me what happened)

p-himik15:03:29

This gets asked once every few months, so it should be possible to find previous discussions here. I myself prefer either the initial or the 2nd approach. I try to avoid the 1st one if the validation is a part of the business logic and not a safeguard against dev mistakes. Some people prefer error monads. I think there's one or two libraries for them.

😅 1
1
rolt15:03:57

I've used delay with version 1. in the past (but without exceptions) for that specific problem. And then just cond to have everything "flat". I feel like it keeps the code readable.

vemv15:03:17

https://github.com/fmnoise/flow can be considered a hybrid solution as it uses exceptions, but then usage looks sort of monadic (as in sweet/streamlined) Personally I tend to be attracted to the Result pattern ("error monad") as it fits particularly well in codebases that annotate functions with Spec or Malli. It's fairly simple. You cannot really "spec" exceptions (that is, with a commonly understood type/spec system).

daveliepmann15:03:20

Here I might use a lightweight error-monad-ish pattern, like

(ok-> {}
      (expensively-assoc-code)
      (expensively-assoc-email))
...where ok-> is a some->-like macro which diverts from the happy path on finding an :error key. Exceptions feel like the wrong tool for this particular example but the devil is in the details.

👍 1
Noah Bogart16:03:22

we don't use this pattern everywhere, but at my job we have a with-validation macro in our route handlers, used like (with-validation obj [fn1 fn2 fn3] sym ...body) that expands to (roughly)

`(loop [results# {}
        [next-validator# & remaining#] ~validators]
   (cond
     (contains? results# ::errors) (error-response 400 (::errors results#))
     next-validator# (recur (merge results# (next-validator# ~obj results#)) remaining#)
     :else (let [~sym results#] ~@body)))

Noah Bogart16:03:21

i think this is how a lot of flow works, but i'd have to look at it again

bzmariano17:03:35

could this be a good use case for core.async ? I could dispatch every expensive op "at the same time" and collect each result from a channel, when the first :error is read then, close the channel and return

p-himik17:03:41

That would work only if you're fine with doing extra work and the functions can be run independently of each other. And the approach doesn't require core.async at all - it's just a collection of futures. If any of the tasks is CPU-intensive, core.async is an even worse fit.

escherize19:03:22

what about failjure?

reefersleep21:03:33

@U05092LD5 When I read about railroad-oriented programming, I made a tiny experimental library that generalised the ok-> pattern you write about. I'm not entirely happy about it - the generalisation I came up with causes exceptional-looking threading - but it was fun to try it out. What do you think? https://github.com/Reefersleep/thread-until

daveliepmann16:03:25

@U0AQ3HP9U Cool! I'm still using https://gist.github.com/daveliepmann/fc2d9f93d51e6bdba0e7e28b1a2a6b26 A former colleague of mine had a similar critique, that the errors still need to be "caught" like exceptions. That's valid. Would love to hear more about your experience and feelings about "exceptional-looking". I think this approach really shines in situations where there are a large number of business objects to be processed, there are multiple distinct ways for each one to fail, and you don't want to stop the whole pipeline on any single failure.

reefersleep21:04:24

@U05092LD5 imo the "catching" of `:error isn't an issue :) At least it's easy to standardise on a recognisable pattern when using these macros. Your collection of macros seems fine to me. Sounds like you've made more use of them than I have of mine - I actually didn't find any good use cases in my code base at the time and kind of left the library as is. What I meant by "exceptional-looking" was that I have a mandatory two first items in my threadings, and it doesn't look good, whitespace-wise, when either the predicate fn or the data literal (or both) are too big (and that's a very small limit - as soon as they're more than 1 line, it looks off). I really don't like this, and it's actually what's put me off of looking for good use cases for my macros. (And then, I just forgot after a while)

👍 1
d._.b16:03:56

I have a weird thing happening where I C-c C-k in emacs to evaluate an integration test namespace, and test-helper.clj won't load with the error "Could not locate foo/integration/test_helper__init.class, foo/integration/test_helper.clj or foo/integration/test_helper.cljc on classpath. Please check that namespaces with dashes use underscores in the Clojure file name." Any ideas? The file exists and is absolutely test/foo/integration/test_helper.clj.

d._.b16:03:56

Is this name special somehow? This also happens for test/foo/test_helper.clj.

p-himik16:03:43

Not special in any way. Is test on the classpath?

wcohen18:03:42

is hato still the http library of choice for those-avoiding-apache-deps?

hiredman18:03:52

you can just http://java.net.http.HttpClient without a wrapper

👍 2
wcohen18:03:33

oh i like this -- i had no idea the bb library worked on clj too

wcohen18:03:40

but @U0NCTKEV8 you're right... i still always get a little stressed when i call JVM things directly. i gotta get over it

lukasz18:03:26

if you're used to clj-http, then hato is a good replacement -whether you like working with the interop or not

mkvlr19:03:21

babashka.http started out as a fork of https://github.com/schmee/java-http-clj which is another option

emccue21:03:46

I would still avoid hato because of this issue https://github.com/gnarroway/hato/issues/43

emccue21:03:02

i know if i want it fixed i should PR, but i haven't had time or focus

valerauko01:03:03

Mostly using aleph