beginners

Christoph 2025-06-10T16:11:15.094589Z

What is your preference regarding error-as-data return types ? I'd like to get a rough idea of what's idiomatic, please vote with a cat 🐈 if you like it, and a dog 🐶 if you prefer to avoid it. For context, say there is a impure function at the boundary of the application, like • (update-database! db ...) • (make-http-request! :post url ..) • (save-to-file! name ..) etc.. To put it in other words, do you prefer 🐶 to return whatever the underlying thing gives you (eg the http response, or database response) 🐈 to wrap/cast those return types into something uniform, like for example { ok: value } or { error: exception-info } The reason I'm asking is, in some cases I see a return type nil meaning success, and sometimes it means a failure, and with collection functions that use predicates like every? some? not-any? etc, it would be useful to have some key, like :error or :value mean the same thing, but I think I have a typed-functional-programming hat on, and might be missing the Clojure way(s) of approaching this use case ?

🐈 1
🐶 1
🐟 1
p-himik 2025-06-10T16:15:54.378199Z

A third option - I prefer to avoid any one such approach and instead do what makes sense at a particular location. Shoehorning a particular dogmatic approach can easily result in some gnarly code.

➕ 3
p-himik 2025-06-10T16:17:20.629669Z

But using a specific approach does help when you're doing it within the scope of a particular API. It could even be an internal one, even a particular namespace.

daveliepmann 2025-06-10T16:20:14.180099Z

+1 from me for "extremely depends on context"

➕ 1
Christoph 2025-06-10T16:27:10.792899Z

Thanks yes. I'm asking specifically about application boundaries because I also don't want to add typing everywhere and lose flexibility because of it

daveliepmann 2025-06-10T16:30:51.968359Z

I guess my question is, why are you wrapping a particular response at all?

🤔 1
p-himik 2025-06-10T16:34:18.567159Z

> application boundaries There are two boundaries in applications - between an application an its users (whatever "user" might mean), and in the reverse direction, between an application and what it uses. Your code sample suggests that it's the latter. And that's exactly where I'd judge based on the context. If it's the former, then it's a bit surprising that something else straight up calls functions from your app. But there a uniform error interface makes sense. Personally, I'd return a map with a status field or something like that. See also: https://github.com/cognitect-labs/anomalies.

1
1
Christoph 2025-06-10T16:51:26.272409Z

I guess my question is, why are you wrapping a particular response at all?so that I can move implementation details of when things are right/wrong down a level, for example instead of

(cond
  (f1 a) (report-error a)
  (f2 b) (report-error b)
  (f3 c) (report-error c)
  :else 
  (println "all good!"))
to have the different f1 f2 and f3 code closer to where the api/db calls are defined, as they are only interesting to the specific api calls, and do the (call-bunch-of-api's ..) code look more like this instead:
(let [results (do-api-calls ..)]
  (when (some has-error? results)
    (report-errors results))
  results)

p-himik 2025-06-10T16:59:00.568779Z

In a lot of places like this, an exception would suffice. Then all that code becomes just (do-api-calls ...). In some cases, you can't just have the upstream deal with any possible exception - then (try ... (catch ...)) with wrapping the original exception could work. Or yes, returning some value that signifies an error. I haven't actually measured anything like it, but my intuition tells me that around 90% of all failures are or should be handled by logging the underlying cause and aborting or retrying the upstream workflow, whatever it is. So suppose you have a web server with a public HTTP API. You don't want to report all SQL errors and NPEs there, you just want to return HTTP 500 in case something is wrong because of the server and it can't be alleviated by the user. So naturally you have a middleware that catches everything that hasn't been yet caught, you log the exception, and you return an opaque HTTP 500 - no custom error handling at all, unless at specific locations where you can handle some specific error. Like e.g. turning FileNotFoundException into HTTP 404 or something like that.

1
Christoph 2025-06-10T17:07:11.446679Z

> my intuition tells me that around 90% of all failures are or should be handled by logging the underlying cause and aborting or retrying the upstream workflow Thanks that makes sense, it reminds me of https://fsharpforfunandprofit.com/posts/against-railway-oriented-programming/#when-should-you-use-result

daveliepmann 2025-06-10T17:29:08.893659Z

@c.els sorry I was unclear — I know the general approach (I call them https://www.daveliepmann.com/articles/idiomatic-clojure-errors.html), and was trying to drill into the dimension of "what about a particular situation calls for error maps instead of throwing an exception?"

Christoph 2025-06-11T05:52:36.506579Z

very interesting write-up @daveliepmann thank you for for all the resources too! 📚 🧑‍🎓

1
vc 2025-06-10T18:20:40.889459Z

I just jumped into #datomic pro, loving it. Have setup storage, transactor, peer. Done sample transactions. All going fine. I intend to use it for prod in couple of weeks. 🤞 Putting here because I am feeling nervous. 🥶

🤘 1
Nim Sadeh 2025-06-10T19:21:25.430849Z

I have the following destructuring inside a let statement:

{:user/keys [time_zone]} (users/fetch-user-by-id db user_id)
This causes an error immediately upon running lein run
Execution error (NullPointerException) at java.util.Objects/requireNonNull (Objects.java:246).
zoneId
even though the code in question does not run on startup. The error doesn't even reference a line number, and compiles just fine. I am not importing anything as user. The code
{:keys [time_zone]} (users/fetch-user-by-id db user_id)
Runs fine (but is wrong). Any other efforts to access this field failed similarly. Has anyone run into something like this?

p-himik 2025-06-10T19:28:07.418909Z

*destructuring The error should still have a stacktrace somewhere. But it's probably about time_zone being nil. Why do you destructure like :user/keys? What does users/fetch-user-by-id return exactly? How are you making sure that the code does not run on startup? Regarding the last code block - is this the only change you have made to make the error go away?

Nim Sadeh 2025-06-10T19:32:33.059819Z

Thanks, I was looking for the right word I was investigating an error about time_zone being nil. I discovered that in transaction contexts in my code because of the way I setup the database connection, data comes in fully qualified (:user/id, :user/time_zone, etc). Note that trying to do (:user/time_zone) failed the same way. I have logs that log out that area of the code with a debug to see if the time zone is null and they did not log out. The code is part of an endpoint, which isn't getting hit. This code doesn't run when I just deconstruct using :keys Yes, that is the only change I made to make the server run (but it is wrong as there's no data at that key)

p-himik 2025-06-10T19:36:39.706389Z

I would try getting the full stack trace of that exception.

2025-06-10T19:42:04.825599Z

What type is returned by the function call?

Nim Sadeh 2025-06-10T19:44:41.032209Z

(s/def :user/row (s/keys :req-un [:user/id
                                  :user/email
                                  :user/full_name
                                  :user/phone
                                  :user/calendar_source
                                  :user/surge_contact_id
                                  :user/time_zone]
                         :opt-un [:user/created_at]))

2025-06-10T19:44:46.372599Z

If it is a weird java.util.Map impl, but my guess would be something downstream that is depending on the value of time_zone, and the full stacktrace would point you to where

2025-06-10T19:47:30.326279Z

I would also dig into "runs fine, but is wrong"

Nim Sadeh 2025-06-10T19:47:32.276159Z

I'm curious about why I get this error just running lein run

Nim Sadeh 2025-06-10T19:47:47.539939Z

Given that this functino is part of an HTTP endpoint that wasn't cURL'd

2025-06-10T19:48:03.436609Z

Look at the whole stacktrace

2025-06-10T19:48:56.664979Z

It will tell you exactly what caused it to happen

2025-06-10T19:51:14.442129Z

Without looking at the stacktrace you are just kind of guessing because you can change a line of code and that changes the behavior, that code must be responsible for the behavior, but that is correlation, a stacktrace is causation

Nim Sadeh 2025-06-10T20:00:18.549709Z

Ohhh I figured it out. there's an old piece of code that runs that on startup, and the namespace is unqualified in that run.

lassemaatta 2025-06-10T06:25:59.321969Z

the docstrings of filter and keep specifically require that pred must be free of side-effects. Why "must" vs "should"?

oλv 2025-06-10T09:08:08.684999Z

This is arbitrary - no police will show up on your door if you print from filter :^)

lassemaatta 2025-06-10T09:57:06.724389Z

it's all fun and games until the referential transparency police show up at your door 🚓

😂 4
oλv 2025-06-10T09:58:38.424129Z

Right!

Christoph 2025-06-10T10:06:43.884209Z

maybe it's just different words because of the different authors, you could take a look at the source to see if it's relevant 🤷 https://github.com/clojure/clojure/commit/4ddc34ee11563345ad2142763c03495bf85110ff#diff-313df99869bda118262a387b322021d2cd9bea959fad3890cb78c6c37b448299R2808

lassemaatta 2025-06-10T11:34:26.232249Z

yeah, I checked the original commit (https://github.com/clojure/clojure/commit/9b78d483959e912a2ef77ff649b893b035144549) as I thought maybe this has something to do with the transducer arities.

🤷 1
lassemaatta 2025-06-10T06:26:14.150359Z

map on the other hand makes no such statements

lassemaatta 2025-06-10T06:27:32.157149Z

swap!, which can call f arbitrary number of times, only specifies "f ... should be free of side effects"

lassemaatta 2025-06-10T06:30:02.399119Z

clojuredocs contains a comment > Although the documentation states that the predicate must be free of side effects, it would be more accurate to say that you should not rely on filter to induce side effects that may be caused by the predicate, nor on the timing of when the predicate will be evaluated, because of the lazy and chunked nature of filter. which makes sense, but is there something else behind the choice of "must" vs "should"?