clojure

New to Clojure? Try the #beginners channel. Official docs: https://clojure.org/ Searchable message archives: https://clojurians-log.clojureverse.org/
coby 2021-02-12T00:42:44.007300Z

Is there a widely-used, well-documented library for implementing role-based access control in Clojure? I imagine it's often pretty domain-specific and mostly just simple checks on maps/sets etc., but wanted to see if there was something nice out there.

lukasz 2021-02-12T14:46:35.033300Z

@ctamayo I'm one the look out for one too - sorry if not helpful, but let's compare notes if either of us finds something 😉

1
coby 2021-02-12T16:58:14.037Z

tbh I wasn't gonna look that hard, seems pretty easy to roll your own. But I saved your message to come back to just in case. 🙂

2021-02-22T23:24:46.055100Z

+1 this

Casey 2021-02-25T12:14:52.188700Z

Did ya'll find anything suitable here? I've found • permissions https://github.com/tuhlmann/permissions • and zakon https://github.com/fmnoise/zakon

lukasz 2021-02-25T15:21:46.195300Z

Permissions looks really cool, interestingly a part of our system already does something like that so (assuming it works as expected) migration would be almost seamless. Thanks for sharing @ramblurr!

Casey 2021-02-25T15:29:24.196400Z

permissions is cljc, so would work in cljs, unlike zakon. though I don't see why zakon couldn't work in cljs.

Casey 2021-02-25T15:31:40.196600Z

permissions can be initialized with just data, whereas zakon has a macro. though with that macro, zakon lets you supply arbitrary functions as authorizers. both rely on global state which is less than ideal if you have dynamic roles/permissions stored in a db

lukasz 2021-02-25T15:58:45.200500Z

Oh, that wasn't obvious - in what way Permissions depends on global state?

Casey 2021-02-25T16:14:10.201800Z

that init-roles bit mutates a global atom

Casey 2021-02-25T16:14:33.202Z

then notice how none of the has-permission? calls pass the available roles, as they are read from the atom

lukasz 2021-02-25T16:33:47.203300Z

Oh, that's... not ideal

lukasz 2021-02-25T16:34:54.203500Z

Should be easy to fix though and refactor the code to allow for passing a role map rather than sourcing it from a global atom

lukasz 2021-02-25T16:36:11.203800Z

hm, or you can supply custom resolvers here: https://github.com/tuhlmann/permissions/blob/master/src/agynamix/roles.cljc#L100-L104

raspasov 2021-02-12T04:10:17.007400Z

What are you trying to achieve exactly? Find the biggest item in a collection by some definition?

dpsutton 2021-02-12T04:22:43.009400Z

Keeping only the biggest n items in a reducible collection. Imagine it’s a million rows from a db each with a score. Want to keep the largest scored values without requiring the entire result set and sorting to get the largest items

2021-02-12T05:04:31.010800Z

I'm trying to attach some metadata to a defed fn inside of a macro, but nothing quite seems to stick:

(let [doc "docs"
        meta {:arglists '([test])}]
    `(def ^~meta foo ~doc (fn [x] 1)))
;; => error: Metadata must be Symbol,Keyword,String or Map

2021-02-12T05:05:22.011400Z

But when I check, it says it's a map:

(let [meta {:arglists '([test])}]
    `[~(type meta)])
;;=>  [clojure.lang.PersistentArrayMap]

2021-02-12T05:06:18.012700Z

Am I just barking up the wrong tree here?

phronmophobic 2021-02-12T05:06:55.013Z

try: ~(with-meta 'foo meta)

phronmophobic 2021-02-12T05:07:18.013500Z

I think it's trying to apply the metadata '~ to the symbol meta in your example

phronmophobic 2021-02-12T05:09:23.014200Z

> (defmacro test-fn-meta []
  `(def ~(with-meta 'foo {:a 1}) (fn [])))
> (test-fn-meta)
#'my.ns/foo
> (meta #'foo)
{:a 1,
 :line 929,
 :column 18,
 :file "*cider-repl examples/browser:localhost:49996(clj)*",
 :name foo,
}

2021-02-12T05:11:35.016100Z

ah, that makes perfect sense. I'm adding the meta to the var, I had tried wrapping the fn before but that obviously doesn't do what I'm aiming for

phronmophobic 2021-02-12T05:12:20.016700Z

as an improvement, defn allows an optional map between the name and params that will be added to the metadata:

(defmacro test-fn-meta []
  `(defn ~'foo ~{:a 1} [x] 1))

2021-02-12T05:14:09.017500Z

I had tried that, but in my (brief) test I couldn't override the :arglists, it just used the ones from the actual arg vector

phronmophobic 2021-02-12T05:14:22.017600Z

actually. it's probably trying to add the metadata ~meta to the symbol foo

2021-02-12T05:14:32.018Z

let me try with this method, though

2021-02-12T05:15:13.018300Z

ah, that works!

2
2021-02-12T05:15:15.018500Z

nice

raspasov 2021-02-12T05:20:36.018800Z

So are you OK witn O(N) performance? If that’s the case, perhaps a simple (filter …) transducer ?

dpsutton 2021-02-12T05:48:54.019Z

i don't know how i could filter. given that i don't know a priori what some threshold will be

dpsutton 2021-02-12T05:49:17.019200Z

just that i want the largest N items. i don't have any fore knowledge of how to determine those N items until i pass a function over them

dpsutton 2021-02-12T05:50:27.019500Z

i certainly can't beat O(N) in the general case. i think i'll hit O(n log(n)) in the worst case scenario where its essentially just heap sort but i'm discarding values so i have much smaller memory footprint

dpsutton 2021-02-12T05:53:18.019700Z

but that's the point of using a heap here. i don't want to track order or sorting, just a collection of items with constant time access to the min so i can throw that one away if necessary

raspasov 2021-02-12T06:12:09.019900Z

ah I see, largest N as opposed to a pre-determined threshold, hmm

dpsutton 2021-02-12T06:12:56.020100Z

correct. that's why it needs to be in the reducing function to manipulate the concrete collection rather than in a transducer

raspasov 2021-02-12T06:13:54.020300Z

Maybe just (assoc …) all the items into a sorted map and (take N …)

raspasov 2021-02-12T06:14:19.020500Z

(sorted-map-by …)

dpsutton 2021-02-12T06:14:24.020700Z

that realizes the whole collection. if there are a million my server falls over

dpsutton 2021-02-12T06:15:27.020900Z

or continually resorts a collection. you're essentially using it as a heap whereas i'm using the proper heap datastructure here. constant access to smallest item, no sorting of the rest of the collection

raspasov 2021-02-12T06:16:51.021100Z

Is there a way to get the data sorted out of the database? Or that’s not a good option?

raspasov 2021-02-12T06:18:06.021300Z

(assuming it’s coming from a database)

dpsutton 2021-02-12T06:18:59.021500Z

no. scoring things in code based on the rows from the db. far too complicated to implement that in sql

raspasov 2021-02-12T06:19:19.021700Z

Have you seen this? https://github.com/clojure/data.priority-map

raspasov 2021-02-12T06:19:40.022100Z

(just looking for a more idiomatic solution as opposed to using the Java option)

dpsutton 2021-02-12T06:19:59.022300Z

yeah i looked at that. i forget what it lacked

dpsutton 2021-02-12T06:20:30.022500Z

ah, it was ordering. it maintains the priorities to the items but doesn't expose a way to quickly get the lowest priority item

dpsutton 2021-02-12T06:20:55.022700Z

also, no good way to evict the lowest priority. just add new ones in.

dpsutton 2021-02-12T06:21:09.022900Z

i mean, just abstractly a heap is all that i want with nothing that i don't want

2021-02-12T06:41:14.025Z

ok, I got everything working the way I wanted in clj, but when I tried using it in cljs I found a weird behavior with arglists:

(defn bar {:b 2 :arglists '([damned lies])}
  [x] 1)
(meta #'bar)
;;=> {:ns user, :name bar, :file "user/main.cljs", :end-column 10, :source "bar", :column 1, :line 1, :b 2, :end-line 1, :arglists ([damned lies]), :doc nil, :test nil}
;; this is just like clj^

(defn foo {:a 1 :arglists '([lies])}
  ([x] 1)
  ([x y] 2))
(meta #'foo)
;;=> {:ns user, :name foo, :file "user/main.cljs", :end-column 10, :top-fn {:variadic? false, :fixed-arity 2, :max-fixed-arity 2, :method-params [[x] [x y]], :arglists ([x] [x y]), :arglists-meta (nil nil)}, :source "foo", :column 1, :line 1, :end-line 1, :arglists ([x] [x y]), :doc "docs", :test nil, :a 1}

2021-02-12T06:41:57.025600Z

It looks like multi-arity functions will override the :arglists supplied in the metadata with the ones it figures out on its own

2021-02-12T06:42:35.026300Z

I'm up way too late, so I'll check in later, but this seems weird to me

2021-02-12T06:55:07.026700Z

b-+

phronmophobic 2021-02-12T06:58:04.026800Z

according the docs, cljs has no reified vars, https://www.clojurescript.org/about/differences#_vars_and_the_global_environment the truth is a little bit trickier as there's been a few improvements that address some of the use cases of reified vars. overall, I've found it's worth avoiding vars in cljs if possible. That may change as they keep improving cljs

raspasov 2021-02-12T07:14:23.027100Z

(require '[net.cgrand.xforms :as x])

(let [data (repeatedly 10000 (fn [] {:score (rand-int 1000)}))]
  (sequence
    (comp
      (x/sort-by :score >)
      (take 3))
    data))
One attempt using the xforms library… I am not 100% sure if x/sort-by realizes the whole collection…. It might.

dpsutton 2021-02-12T07:28:27.027400Z

yeah it does. it just keeps all of your items in an array list and on the completing single arity of a transducer it calls (java.util.Collections/sort cmp)

dpsutton 2021-02-12T07:28:42.027600Z

(defn sort
  ([] (sort compare))
  ([cmp]
    (fn [rf]
      (let [buf #?(:clj (java.util.ArrayList.) :cljs #js [])]
        (fn
          ([] (rf))
          ([acc] (rf (core/reduce rf acc (doto buf #?(:clj (java.util.Collections/sort cmp) :cljs (.sort cmp))))))
          ([acc x] (#?(:clj .add :cljs .push) buf x) acc))))))

raspasov 2021-02-12T07:30:41.027800Z

Yes, I give up 🙂

raspasov 2021-02-12T07:32:08.028Z

Depending on the database you’re using, I would still try to get all the items in a sorted order first… That would actually prevent you from having to go through the 1 million items in the first place (even if you’re not keeping them all in memory).

dpsutton 2021-02-12T07:32:29.028200Z

can't. its for searching and there are a few different strategies to rank them

dpsutton 2021-02-12T07:32:46.028400Z

need rows and then run functions over the different columns using different ways to rank them

raspasov 2021-02-12T07:33:01.028600Z

I see… you need all the items for other purposes.

dpsutton 2021-02-12T07:33:06.028800Z

i agree if we could ask the db engine to rank them and then just take that

dpsutton 2021-02-12T07:33:29.029Z

well doing things like longest common subsequence between search term and the columns doesn't sound fun in sql

dpsutton 2021-02-12T07:33:50.029200Z

but i appreciate you going through this with me. for sure i'd prefer a native clojure immutable heap or something similar

👍 1
dpsutton 2021-02-12T07:33:56.029400Z

but those java util collections are rock soid

dpsutton 2021-02-12T07:34:00.029600Z

solid

raspasov 2021-02-12T07:34:03.029800Z

I am just suggesting asking the DB to do the sorting… I 100% agree about not doing most things in SQL, it would be a mess.

dpsutton 2021-02-12T07:34:04.030Z

but mutable 😞

dpsutton 2021-02-12T07:34:24.030200Z

the values they sort by are computed dynamically according to search term. no way to ask the db to sort it

raspasov 2021-02-12T07:34:43.030400Z

I see.

p-himik 2021-02-12T15:26:33.034200Z

There's no function to quickly check if a collection has duplicates, is there?

lukasz 2021-02-12T15:27:51.034600Z

frequencies?

rutledgepaulv 2021-02-12T15:34:04.035800Z

(apply distinct? [1 2 3 4 2])
=> false
(apply distinct? [1 2 3 4])
=> true

rutledgepaulv 2021-02-12T15:34:53.036300Z

would prefer this over frequencies since it will short circuit on first repeat

p-himik 2021-02-12T15:44:40.036700Z

That's the one, thanks! I used borkdude's re-find but I didn't guess to try vararg as opposed to a single collection.

2021-02-12T19:18:32.037900Z

How can I split a string by (char 1)

;;; works, is there a better way?
(vec (.split (str "asdf" (char 1) "asdf") (str (char 1))))

;;; these don't work
(str/split (str "asdf" (char 1) "asdf")
  #"\u00001"
  ;;#"[\\1]"
  ;;#"\\x01"
  )

dpsutton 2021-02-12T19:21:50.038100Z

(let [s "asdf"] (str/split s (re-pattern (str (char 1)))))

dpsutton 2021-02-12T19:22:06.038300Z

if i understand what you're after

2021-02-12T19:23:15.038900Z

I get the same result for these two subexpressions. You do not?

user=> (= (vec (.split s1 (str (char 1)))) (str/split s1 #"\u0001"))
true

2021-02-12T19:28:40.040200Z

And in fact, for your first and third options that you say didn't work for you:

user=> (require '[clojure.string :as str])
nil
user=> (def s1 (str "asdf" (char 1) "asdf"))
user=> (def a1 (vec (.split s1 (str (char 1)))))
#'user/a1
user=> (def a2 (str/split s1 #"\u0001"))
#'user/a2
user=> (def a3 (str/split s1 #"\x01"))
#'user/a3
user=> (= a1 a2 a3)
true

2021-02-12T19:29:51.041500Z

ohh I had an extra 0 in the \u0001 str, that one works for me

2021-02-12T19:30:14.042100Z

(Note that inside a Clojure string surrounded by "", you need to type \\ to get a single backslash. Inside of #"" you only need to type one backslash to get one backslash in the regex string -- the Clojure reader parses regex strings slightly differently than regular strings, as a convenience to avoid having to type so many double backslashes)

2021-02-12T19:31:17.043200Z

Oh yeah \x01 does work with the single backslash

2021-02-12T19:31:20.043300Z

but that can lead to more confusion if you weren't aware of it (and I just double-checked -- this fact is documented in official docs here if you search for regex: https://clojure.org/reference/reader)

dpsutton 2021-02-12T20:56:34.044800Z

if you have a function that will end up reifying a java.util.Comparator, do you ask the caller to supply a function satisfying the <0,0,>0 contract that a comparator expects and reify it inside the function or do you have the caller just pass you a java.util.Comparator and not worry about it since its clear at the call site that you have that contract?

dpsutton 2021-02-12T20:57:01.045400Z

i guess, do you make everyone go through the ceremony of reify'ing or take a function and do the reify in one spot for conveinece to the caller

2021-02-12T21:00:02.045700Z

functions are Comparators

😮 1
2021-02-12T21:00:26.046200Z

user=&gt; (instance? java.util.Comparator (fn []))
true
user=&gt;

dpsutton 2021-02-12T21:02:06.046700Z

oh get out of town. awesome

alexmiller 2021-02-12T21:07:10.046900Z

https://clojure.org/guides/comparators for 10x more detail

💯 1
danielglauser 2021-02-12T22:48:08.048300Z

What Java version do folks recommend or are using for prod? We're looking to upgrade from Java 8 but aren't sure how high is safe to go. Obviously we'll test things out well before we ship, just looking for a recommendation on a target.

R.A. Porter 2021-02-12T22:51:44.048400Z

My feeling on Java versions is the same for Clojure projects as it is for Java projects: if you want to play with the non-LTS releases, that’s great; if it’s for production code, stick with the LTS Javas: 11, 17, 23… Not everyone feels the same way, of course. If you’re in the Java space, you might want to play with some of the features but in Clojure dev, I think it’s even less compelling as it’s not like there are significant VM changes occurring.

1
seancorfield 2021-02-12T22:55:17.048600Z

I agree. Even though we are using Adopt OpenJDK versions, rather than Oracle's versions, we feel more comfortable with 11 in production and plan to move to 17 at some point. In dev we've been testing against 11, 14, and I'm currently using 15 -- but I can easily run tests against multiple versions just by using an env var (I have four or five different JDKs installed locally).

2
2021-02-12T23:00:40.048800Z

Out of curiosity did you run into any issues with 14 or 15?

2021-02-12T23:00:59.049Z

as in did they not "just work"

seancorfield 2021-02-12T23:07:21.049200Z

I don't recall hitting any problems when I moved up to JDK 14, except for some minor changes in locale-specific number formatting (which I think were bug fixes -- but they broke a couple of our tests).

seancorfield 2021-02-12T23:07:51.049400Z

But locale-specific number formatting has changed a little across several recent JDK versions so that wasn't entirely surprising.

seancorfield 2021-02-12T23:08:21.049600Z

The big jump was from 8 to 11 (because of all the changes in 9). Since then it's been pretty smooth sailing.

2021-02-12T23:09:49.049800Z

Yeah same here basically (on much smaller codebases)

alexmiller 2021-02-12T23:25:17.050100Z

Java 10 or 11 introduced some important changes if you are running in a container (making JVM pay attention to container settings)

alexmiller 2021-02-12T23:25:34.050300Z

so if you're doing that, it's almost definitely worth upgrading from 8 to 11

alexmiller 2021-02-12T23:26:02.050500Z

as of this year, the clojure community has shifted from majority 8 to 11

seancorfield 2021-02-12T23:31:13.050700Z

We still have to maintain JDK 8 compatibility in most of our code because we have a legacy app that we're still slowly rewriting to Clojure and it won't run on JDK 9+ (it's a mix of Clojure and... other stuff... 🙂 ).

alexmiller 2021-02-12T23:33:51.050900Z

officially, we test and recommend Clojure only on LTS Java releases. unofficially, I test it with each release and am not aware of any issues on recent versions

1
2021-02-12T23:45:43.052100Z

What's the simplest way to get edn/read-string to throw or some other way to validate if the EDN file contains a valid data-structure but atfer it has junk?

2021-02-12T23:46:31.053100Z

config.edn:

{:a 1
 :b 2}
 :c 3}
So that: (edn/read-string (slurp "config.edn")) ;; ERROR