beginners

lepistane 2025-05-06T09:17:25.210319Z

how come we don't have Attribute-based access control libraries in clojure? Is it because we can interop with https://casbin.org/ easily?

p-himik 2025-05-06T10:04:35.674919Z

Probably.

2025-05-06T16:00:53.410469Z

What's the use-case for this?

lepistane 2025-05-06T16:18:14.288469Z

same app, same endpoint, different types of users can see different payload. Some can see whole payload some should see everything but X and Y Some see only basic stuff Currently rolling role based system but it has it's limits + filtering of the payload isn't scaleable. Really trying to not roll my own solution so i am looking around for already made ones and casbin is the only that could be plugged in without too much fuss

2025-05-06T17:29:04.012489Z

Right, but it also doesn't do anything to filter your payload or make your APIs return a subset of the data. All it does is give you a isAllowed function that returns true/false if the user is allowed to read or write the attribute.

2025-05-06T17:30:25.237699Z

It's just a convenient way to define and store the rules about what roles exists, what users have what roles, and what attribute each role is allowed to read/wrote too or actions roles can perform. Which is nice that you don't have to roll your own for that. But ya, you can use jcasbin from Clojure and the API surface is so minimal it doesn't seem worth to have a Clojure layer.

lepistane 2025-05-07T06:23:31.547159Z

Do you roll your own middleware for filtering out the payload that user isn't allowed to read?

2025-05-07T15:31:54.068799Z

If your input/output payloads have the same pattern of getting what is the authorized user and structuring the response data that you can make a middleware that works for them all you could. Otherwise, you have to do it somewhere else. You could do it at the data layer, before fetching the data, or after fetching it, or you can do it on each handler and custom filter as appropriate.

Diego 2025-05-06T15:48:02.200469Z

hello everyone, since clojure is lazy, would this request happen?

(time (or 10 @(hk-client/get "")))

2025-05-06T15:49:51.684149Z

laziness has nothing to do with this one. or is short circuiting, so the second branch isn't run because it returns 10 first

seancorfield 2025-05-06T15:50:21.024039Z

user=> (time (or 10 @(future (println "I ran!"))))
"Elapsed time: 0.038514 msecs"
10

ghadi 2025-05-06T15:50:50.222629Z

(to be super pedantic, Clojure is not lazy. It's strict, just like Python, Java, go)

👍 2
Ludger Solbach 2025-05-06T16:05:13.893469Z

but it has lazy sequences

2025-05-06T22:29:31.914769Z

The answer is in the API docs: https://clojure.github.io/clojure/clojure.core-api.html#clojure.core/or And it includes a link to the 2 lines of source code that make it happen.

adi 2025-05-07T03:46:00.833239Z

but it has lazy sequencesDifferent from the language being lazy. A language like Haskell has laziness baked-in (because functions support currying / partial application by design). https://wiki.haskell.org/index.php?title=Partial_application

adi 2025-05-07T03:53:43.485709Z

clojure-docs has an approachable guide to laziness in Clojure: https://clojure-doc.org/articles/language/laziness/

Joseph Ferano 2025-05-06T06:39:15.409299Z

Hi, I'm redoing some Leetcode and DSA stuff to learn Clojure, I would appreciate critiques of my code, how to make things more idiomatic. I'm also doing AoC 2024, but I want to gain a bit more fluency before I focus on it. Here's Shortest Path with a 2D grid. Thanks!

(defn shortest-path
  [grid]
  (let [row-count (count grid)
        col-count (count (nth grid 0))]
    (loop [q (into clojure.lang.PersistentQueue/EMPTY [[0 0 0]])
           visited #{[0 0 0]}]
      (if (empty? q) 
        -1
        (let [[row col steps :as node] (peek q)]
          (if (= [row col] (mapv dec [row-count col-count]))
            steps
            (let [check-neighbors (map #(mapv + node %) [[-1 0 1][1 0 1][0 -1 1][0 1 1]])
                  valid-neighbor (fn [[r c _]] (and (<= 0 r (dec row-count))
                                                    (<= 0 c (dec col-count))
                                                    (not (visited [r c 0]))
                                                    (= 0 (nth (nth grid r) c))))
                  neighbors (filter valid-neighbor check-neighbors)]
              (recur (into (pop q) neighbors)
                     (into visited (mapv #(assoc % 2 0) neighbors))))))))))

a13 2025-05-06T08:40:57.543549Z

At first glance, lgtm! I can mention some tiny stylistic issues, but other than that - nicely done! • instead of empty? (check the source code in core.clj) in conditions, it's more idiomatic to use seq with branches swapped. Although, you may prefer the shortest branch first. • (= 0 ...) is better as zero? • I don't think it's worth adding the "0 steps" part in visited since you ignore it anyway. So I'd use mapv butlast neighbors instead. • check-neighbors sounds like an action (because of the verb being first), so I'd just use neighbors instead (cause that's what they are). • valid-neigbor is definitely a predicate, please add ? to the name. • peek works the same way as first for queues, so maybe it's shorter to use destructuring of q instead • you don't use row-count and col-count directly, only deced, so maybe it's worth to bind them already decreased? And name "max-row" and "max-col" or smth like that.

a13 2025-05-06T08:49:03.263449Z

btw, do you check if the first cell isn't 1?

Joseph Ferano 2025-05-06T09:59:06.462499Z

Wow thanks! That was more help than what I was expecting. I am currently not checking if the first cell is not 1, and I should. I do in my python version

👍 1
Joseph Ferano 2025-05-06T09:59:26.456389Z

Those are all good tips, let me implement them

Joseph Ferano 2025-05-06T10:12:07.840159Z

Ok, here's the second pass (actually 3rd). I think I did have butlast in my first iteration but tried setting steps to 0, just to see how it "felt"

Joseph Ferano 2025-05-06T10:12:44.274409Z

Also, Claude just suggested something nice; instead of (nth (nth grid r) c) I can do (get-in grid [r c]) which is really nice

👍 1
Joseph Ferano 2025-05-06T10:13:00.562269Z

(defn shortest-path
  [grid]
  (if (= 1 (get-in grid [0 0]))
    -1
    (let [max-row (dec (count grid))
          max-col (dec (count (nth grid 0)))]
      (loop [q (into clojure.lang.PersistentQueue/EMPTY [[0 0 0]])
             visited #{[0 0]}]
        (if (not (seq q))
          -1
          (let [[[row col steps :as node] & tail] q]
            (if (= [row col] [max-row max-col])
              steps
              (let [valid-neighbor? (fn [[r c _]] (and (<= 0 r max-row)
                                                       (<= 0 c max-col)
                                                       (not (visited [r c]))
                                                       (zero? (get-in grid [r c]))))
                    neighbors (filter valid-neighbor?
                                      (map #(mapv + % node)
                                           [[-1 0 1][1 0 1][0 -1 1][0 1 1]]))]
                (recur (into (pop q) neighbors)
                       (into visited (mapv butlast neighbors)))))))))))

Joseph Ferano 2025-05-06T10:13:45.437629Z

I don't use slack much, is there a way to maximize this thread like I can in Discord? I don't like that all my code is squirshed in some narrow side panel

a13 2025-05-06T10:14:23.268189Z

try "Threads" in the left panel

Joseph Ferano 2025-05-06T10:14:38.090669Z

Ah, perfect! Thanks!

Joseph Ferano 2025-05-06T10:15:23.643469Z

I see what you mean about peek, I can just destructure the first element. I guess what you meant by first is that when you destructure that's the function that's getting called under the hood?

a13 2025-05-06T10:16:05.222599Z

correct

Joseph Ferano 2025-05-06T10:16:23.894539Z

Cool, I'm really enjoying Clojure, this is so nice

a13 2025-05-06T10:17:17.333069Z

the second (get-in grid [0 0]) looks wrong, shouldn't it be [r c] ?

Joseph Ferano 2025-05-06T10:17:27.847179Z

Woops, you're right

a13 2025-05-06T10:18:50.936359Z

also since you are already destructuring q, no need to call "pop q", just bind it in the same let expression

Joseph Ferano 2025-05-06T10:19:33.022599Z

Oh with & right?

a13 2025-05-06T10:19:41.835199Z

yes

Joseph Ferano 2025-05-06T10:19:58.700659Z

Ok, done

a13 2025-05-06T10:20:04.591599Z

(let [[[row col steps :as node] & q*] q]
or so

Joseph Ferano 2025-05-06T10:20:23.974639Z

Oh? I'm not familiar with that syntax

a13 2025-05-06T10:20:49.072999Z

the "*" is just an idiomatic lisp way to say "it's the same thing but with a new value"

Joseph Ferano 2025-05-06T10:21:15.005099Z

Oh, it's the FP thing

a13 2025-05-06T10:21:17.360369Z

some people tend to use ' etc

Joseph Ferano 2025-05-06T10:21:28.719869Z

Yes, in F# you tend to use ' for "prime"

a13 2025-05-06T10:21:30.021799Z

not FP, but lisp

a13 2025-05-06T10:22:09.007889Z

anyway, it's just a name. can be any other one that makes sense

Joseph Ferano 2025-05-06T10:22:23.816149Z

Sure, if star is idiomatic I'll go with that

a13 2025-05-06T10:23:53.632769Z

also, I think it's possible to avoid destructuring of node, just use butlast instead of "[row col]"

a13 2025-05-06T10:24:41.211049Z

oh, no, steps are still needed

👍 1
Joseph Ferano 2025-05-06T10:25:39.476539Z

I like your avatar

a13 2025-05-06T10:25:49.044939Z

haha, thanks!

Joseph Ferano 2025-05-06T10:26:26.908749Z

Thanks for the help! The clojure related discords don't seem as active, so it's nice to see some activity here

👍 1
p-himik 2025-05-06T10:49:57.499319Z

> (let [[[row col steps :as node] & q*] q] I would definitely not use destructuring for queues. It turns queues into seqs, and they behave differently and have different guarantees. peek and pop are the operations you want to use with queues.

user=> (def q (into clojure.lang.PersistentQueue/EMPTY [1 2 3]))
#'user/q
user=> (-> q (pop) (conj 4) (vec))
[2 3 4]
user=> (let [[x & q] q] (conj q 4))
(4 2 3)

✍️ 2
Joseph Ferano 2025-05-06T11:01:07.160409Z

Thanks!

p-himik 2025-05-06T11:04:34.056279Z

Also, the replacement of (empty? q) with (not (seq q)) makes sense only if you swap the branches, so there's no that not. Just keep (empty? q). :) Sometimes it might even make sense to just use (not (empty? q)) (or (not-empty q), although it's usually for different things). I myself use (seq q) when I want to check for empty collections, but it's definitely a debatable thing and a matter of taste. Don't listen to people talking about "inefficiencies" of empty? - a single not will not make your app suffer any performance losses.

a13 2025-05-06T11:08:14.114339Z

IMO it's not about "inefficiencies" but seq saying exactly that "the collection has elements, so I'm going to process it somehow", so it's a happy path here.

p-himik 2025-05-06T11:12:01.966779Z

> it's not about "inefficiencies" I'm not saying it is. I'm saying that some people claim that it is. > seq saying exactly that "the collection has elements, so I'm going to process it somehow", so it's a happy path here So does (not (empty? ...)) but without any cognitive load of having to keep the fact that (seq ...) returns nil if the collection is empty at the top of your head. As I said - a debatable thing. :)

a13 2025-05-06T11:14:46.844559Z

> but without any cognitive load of having to keep the fact that (seq ...) returns nil if the collection is empty in my head, it says exactly that "it has elements", but sure, I've been working with Clojure for years 🙂

p-himik 2025-05-06T11:15:55.829569Z

So have I, but we're in #beginners. And the instances of this very discussion of seq vs not empty? are aplenty.

👍 1
Joseph Ferano 2025-05-07T01:04:31.829569Z

Thanks for the riveting discussion! Learning a lot. Seems like I came to the right place. As a beginner clojurian, seq feels kinda weird, because it doesn't immediately jump out at you that you're testing to see if a collection is empty whereas empty? is very clear on its intent. But that's fine. That's something I'll get over if seq is more idiomatic.

👍 1
a13 2025-05-07T07:22:35.378779Z

@joseph286 Think of it as follows: "The main problem is working with the collection (not stopping work on it) so we need elements in it."

a13 2025-05-07T07:28:26.028469Z

It's one of the reasons I like Clojure. In most cases, it allows you to write a solution that follows the requirements almost literally. In other mainstream languages, you have to translate the requirements into the language itself. The only caveat is that you have to "forget" your old habits to avoid the XY problem.

rolt 2025-05-07T07:55:38.141249Z

I usually try to really avoid tuples, and use maps instead. So I would change the [x y steps] tuple to either {:position [x y] :steps steps} or {:x x :y y :steps steps}. It may make things a bit more verbose at first but usually with destructuring it ends up being not too bad and way more maintainable in the long run. An other less important thing: the fact that whether a cell is empty or not is denoted by 0 or not makes things a bit harder to read. I would either convert to boolean or :O & :X keywords, or keep 0 but move operation to auxiliary empty-cell? function so that the logic lives somewhere else

a13 2025-05-07T08:18:11.439979Z

Yeah, I thought about empty-cell? too, so it's possible to reuse with the initial cell as well