how come we don't have Attribute-based access control libraries in clojure? Is it because we can interop with https://casbin.org/ easily?
Probably.
What's the use-case for this?
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
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.
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.
Do you roll your own middleware for filtering out the payload that user isn't allowed to read?
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.
hello everyone, since clojure is lazy, would this request happen?
(time (or 10 @(hk-client/get ""))) laziness has nothing to do with this one. or is short circuiting, so the second branch isn't run because it returns 10 first
user=> (time (or 10 @(future (println "I ran!"))))
"Elapsed time: 0.038514 msecs"
10(to be super pedantic, Clojure is not lazy. It's strict, just like Python, Java, go)
but it has lazy sequences
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.
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
clojure-docs has an approachable guide to laziness in Clojure: https://clojure-doc.org/articles/language/laziness/
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))))))))))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.
btw, do you check if the first cell isn't 1?
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
Those are all good tips, let me implement them
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"
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
(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)))))))))))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
try "Threads" in the left panel
Ah, perfect! Thanks!
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?
correct
Cool, I'm really enjoying Clojure, this is so nice
the second (get-in grid [0 0]) looks wrong, shouldn't it be [r c] ?
Woops, you're right
also since you are already destructuring q, no need to call "pop q", just bind it in the same let expression
Oh with & right?
yes
Ok, done
(let [[[row col steps :as node] & q*] q]
or soOh? I'm not familiar with that syntax
the "*" is just an idiomatic lisp way to say "it's the same thing but with a new value"
Oh, it's the FP thing
some people tend to use ' etc
Yes, in F# you tend to use ' for "prime"
not FP, but lisp
anyway, it's just a name. can be any other one that makes sense
Sure, if star is idiomatic I'll go with that
also, I think it's possible to avoid destructuring of node, just use butlast instead of "[row col]"
oh, no, steps are still needed
I like your avatar
haha, thanks!
Thanks for the help! The clojure related discords don't seem as active, so it's nice to see some activity here
> (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)Thanks!
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.
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.
> 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. :)
> 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 🙂
So have I, but we're in #beginners. And the instances of this very discussion of seq vs not empty? are aplenty.
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.
@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."
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.
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
Yeah, I thought about empty-cell? too, so it's possible to reuse with the initial cell as well