This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2020-04-24
Channels
- # announcements (8)
- # aws (12)
- # babashka (84)
- # beginners (380)
- # calva (56)
- # clj-kondo (52)
- # cljdoc (4)
- # cljs-dev (327)
- # cljsrn (4)
- # clojure (154)
- # clojure-italy (5)
- # clojure-nl (3)
- # clojure-uk (21)
- # clojurescript (52)
- # conjure (133)
- # cursive (64)
- # datomic (33)
- # emacs (22)
- # fulcro (35)
- # graalvm (24)
- # graphql (1)
- # kaocha (1)
- # leiningen (1)
- # off-topic (24)
- # onyx (2)
- # pathom (10)
- # re-frame (3)
- # reagent (3)
- # reitit (3)
- # shadow-cljs (48)
- # spacemacs (12)
- # tools-deps (98)
- # xtdb (7)
im having trouble trying to articulate in clojure how to code the following function.
given a list of keywords, i want to iterate over that list and check if a map contains that keyword. if it does i want to do an action (in my case assoc it with a new value). can anyone give me a direct to look in? i have tried map
and for
and keep hitting a wall
If you know the key/value pairs you want to merge in ahead of time and always add them with that value regardless of whether they are already in the original map, or not, then merge
can do that. Since you say you want the new values to be used only if the key is already present in the original map, that isn't merge
. reduce
is a pretty general looping construct, something like (reduce original-map (fn [cur-map k] (if (contains? cur-map k) (assoc cur-map k new-val) cur-map) list-of-keys)
is the general kind of pattern that seems like it would work for you.
except I messed up the order of arguments to reduce
there. original-map
should be just before list-of-keys
+1 for the reduce approach
reshred.system.server=>
(def mm {:status 200, :body nil})
#'reshred.system.server/mm
reshred.system.server=>
(assoc mm :body body)
Syntax error compiling at (form-init8744011881109093941.clj:1:1).
Unable to resolve symbol: body in this context
class clojure.lang.Compiler$CompilerException
Show: AllClojureJavaToolingDuplicates
reshred.system.server=>
(println mm)
{:status 200, :body nil}
nil
The Unable to resolve symbol: body in this context
is the part of that error message that stands out to me.
Try (assoc mm :body {:id 1})
The error message I called out above is because the symbol body
had no value defined for it at the time you tried to evaluate (assoc mm :body body)
The reason that (update mm :body {:id 1})
returned the same value as mm
is different -- it is because update
and assoc
take different kinds of arguments -- update
takes a function and optionally arguments for it, and calls that function on the original value associated with the key
A Clojure map data structure can behave like a function, though, which is why there was no error. A Clojure map behaves like a function from its keys, returning their corresponding values.
And sorry if I am inundating you with too much, but one more REPL session for you to think about, demonstrating that assoc
is a pure function, and the map that is the value of mm
is immutable, and not changed by the call to assoc
:
user=> (def mm {:status 200, :body nil})
#'user/mm
user=> (assoc mm :body {:foo 1 :bar 2})
{:status 200, :body {:foo 1, :bar 2}}
user=> mm
{:status 200, :body nil}
assoc
takes one map as input, and returns a new one, which usually shares memory and most of its key/value pairs with the input map.
hello,everyone. I'm trying to make Number of combinations with FP,but I have no idea.Any one can help me?
just like this function (defn C [nums n] ...)
means take n from nums into making Number of combinations
So you don't want a function like (num-combination 5 3) that returns 10. You want a function like (combinations ["a" "b" "c" "d" "e"] 3) that returns a collection of 10 3-element collections, all different?
Do you already have an idea of how you might solve it, in words, perhaps using some kind of recursive approach?
There are definitely multiple ways to solve this, and if this is part of an exercise to learn how to solve such problems, I wouldn't want to just give the whole answer away all at once 🙂
Clojure's recur
can be used for some kinds of recursive approaches, but not all of them. I suspect it would be simpler to ignore the existence of recur
at first, and think of that as an optimization that might be useful later, after you have something working that doesn't use recur
I can give a significant hint for one way to solve it recursively, first just in words rather than writing code for it.
If you think of all of the 3-element subsets of the 5-element set #{"a" "b" "c" "d" "e"} some of them contain the element "a", and some do not.
Of all of the 3-element sets that do contain "a", how many other elements besides "a" must they have in them?
No problem. Take your time.
Do you mean like: given 5 people, how many different sets of 3 people can you select from those 5?
And you already know the formula from math for this, probably?
@andy.fingerhut thank you for the insight!
im listening to a POST request incoming and seem to be back to square one of my issue that i mentioned in https://clojurians.slack.com/archives/C053AK3F9/p1587612929306400. I am now able to transform the incoming body
to a UUID type but i am getting the following error
java.lang.IllegalArgumentException: No implementation of method: :write-body-to-stream of protocol: #'ring.core.protocols/StreamableResponseBody found for class: clojure.lang.PersistentArrayMap
my code looks like this
(def ids-to-replace
[:id :user_id :seller_id])
(defn ^:private id->uuid
[original-map to-replace]
(reduce
(fn [cur-map k]
(if (contains? cur-map k)
(assoc cur-map k (string-util/to-uuid (k cur-map))) cur-map))
original-map to-replace))
(defn wrap-uuid-type
[handler]
(fn
([request]
(println "first on submit")
(let [response (handler request)
body (id->uuid (json/read-str (:body response) :key-fn keyword) ids-to-replace)]
(println (assoc response :body body))
(assoc response :body body)))))
(defn wrap-database-component [handler database]
(fn [req]
(handler (assoc req :database database))))
(defn routes-handler [database]
(println "routers-handler fired")
(-> routes/define-main-routes
(wrap-uuid-type)
(wrap-database-component database)))
i'm just catching up. can you say in words what you want to achieve with wrap-uuid-type
?
sure! so i have an incoming json from a POST
request that is like
{
id: "uuid-here-but-as-a-string"
}
and i want to intercept this and change the type of the id
to UUID
so my database doesnt yell at me for it being the wrong type. i was hoping that i could do this via a middleware so i dont have to wrap each POST
function with a converterah ok. so you want to modify the request before the handler i think. see how you do response (handler request)
? that means the request is going in unchanged. you want to (handler (change-things-to-uuids request)
instead
i ahve it working in other places where i can do response (handler request)
and just return response
and things work ok
my issue is after i do the replacement it says it cannot write it back to a stream which makes me think there is no good way to do this
> No implementation of method: :write-body-to-stream of protocol: #'ring.core.protocols/StreamableResponseBody found for class: clojure.lang.PersistentArrayMap
look at these two handlers:
(defn handler [request]
{:status 200
:headers {"Content-Type" "application/edn"}
:body {:data {:important :stuff}}})
(defn handler [request]
{:status 200
:headers {"Content-Type" "application/edn"}
:body (pr-str {:data {:important :stuff}})})
the first handler throws the same error as you are seeing. the second one does notand yes per the error you are correct. i have to convert the :body
to a keyword made and then do the replacement
(defn ^:private id->uuid
[original-map to-replace]
(reduce
(fn [cur-map k]
(if (contains? cur-map k)
(assoc cur-map k (string-util/to-uuid (k cur-map))) cur-map))
original-map to-replace))
im afraid ill get the same error as i did with str
where it has no idea how to serialize the UUID
type
clojure.core/pr
([] [x] [x & more])
Prints the object(s) to the output stream that is the current value
of *out*. Prints the object(s), separated by spaces if there is
more than one. By default, pr and prn print in a way that objects
can be read by the reader
{:id #uuid "c0c03eff-9870-4c0e-83ac-506bf59d69cf", :first "Yusuke", :last "Urameshi", :email ""}
emphasis here: > By default, pr and prn print in a way that objects > can be read by the reader
so now your only issue is turning clojure datastructures into json strings rather than edn strings
you've seen the helpful pr-str
and now you need to figure out how to turn clojure datastructures into json
am i understanding correctly? you are happy with what you are receiving from your client but you are not happy with the format you are sending back to the client
> Middleware that converts responses with a map or a vector for a body into a JSON response.
Request client -> middlware -> replace ID with UUID type -> pass to controller -> save in DB Response Save in db --> response middlware --> convert to JSON --> client
> Middleware that parses the body of JSON request maps, and replaces the :body key with the parsed data structure.
ok. so i think i follow what you're trying to do but i'm not sure what parts are working and you're happy with and which parts you are still working on
(defn routes-handler [database]
(println "routers-handler fired")
(-> routes/define-main-routes
(wrap-json-body {:keywords? true})
(wrap-uuid-type)
(wrap-database-component database)))
if i just print request
i get
:body #object[org.eclipse.jetty.server.HttpInputOverHTTP 0x784ebb6c [email protected][c=0,q=0,[0]=null,s=STREAM]]
if i print (handler request)
i get
:body {"id":"c0c03eff-9870-4c0e-83ac-506bf59d69cf","first":"Yusuke","last":"Urameshi","email":""}
follow https://github.com/ring-clojure/ring-json#wrap-json-body and make sure you can verify that the stuff coming in is json
can i recommend that we follow those docs and don't move on until we understand that part?
nvm its not important. when i tried to load my app nothing would load but with the :or
message responses happen again
when i try to submit JSON now from my client to the API server the :body
is still in the same type
:body #object[org.eclipse.jetty.server.HttpInputOverHTTP 0x2fa5e7f4 "[email protected][c=0,q=0,[0]=null,s=STREAM]"]
instead of using my angular app i went to postman and it seems like the content-type is not getting set to application/json
so it was being ignored
ring.middleware.json/wrap-json-body
([handler] [handler options])
Middleware that parses the body of JSON request maps, and replaces the :body
key with the parsed data structure. Requests without a JSON content type are
unaffected.
(defn handler [request]
(println "json-body handler")
(println (handler request)))
(defn routes-handler [database]
(println "routers-handler fired")
(-> routes/define-main-routes
(wrap-json-body handler {:keywords? true :or {:message "Broke"}})
(wrap-uuid-type)
(wrap-database-component database)))
{:keywords? true :or {:message "Broke"}}
oh i guess it is but that's super weird to me
(ns server
(:require [ring.adapter.jetty :as jetty]
[ring.middleware.json :refer [wrap-json-body wrap-json-response]]))
(defn handler [request]
(prn (get request :body))
{:status 200
:headers {"Content-Type" "application/edn"}
:body {:data {:important :stuff}}})
(defn run-server []
(jetty/run-jetty (wrap-json-response (wrap-json-body #'handler))
{:port 3000
:join? false}))
and that's the same as
(defn run-server []
(jetty/run-jetty (-> handler
wrap-json-body
wrap-json-response)
{:port 3000
:join? false}))
you're calling it with too many arguments right? see i'm passing handler into mine. but you're calling it with both routes/define-main-routes and handler?
(-> routes/define-main-routes
(wrap-json-body handler {:keywords? true :or {:message "Broke"}})
and we read the docstring that wrap-json-body will only do work if the application/json
content type header is set
(defn routes-handler [database]
(-> routes/define-main-routes
(wrap-json-body {:keywords? true :or {:message "Broke"}})
(wrap-uuid-type)
(wrap-database-component database)))
(defroutes define-main-routes
(context "/api/v1/items" [] (wrap-routes item/define-routes))
(context "/api/v1/users" [] (wrap-routes user/define-routes))
(context "/api" [] (wrap-routes app-routes))
(route/not-found "Incorrect API endpoint."))
(def define-routes
"Return all user routes"
(routes
(GET "/:id" [id :as req] (response (get-user id req)))
(GET "/:id/address" [id :as req] (response (get-address-by-user id req)))
(POST "/" req (response (create req)))
(POST "/:id" [id :as req] (response (update-user id req)))))
replace it with (POST "/:id" [id :as req] (do (prn req) (response (update-user id req))))
the body prints correctly as expected
:body {:id "c0c03eff-9870-4c0e-83ac-506bf59d69cf", :first "Yusuke", :last "Urameshi", :email ""}
BUT i want to intercept this request prior to getting to this point is what im attempting to do
(defn with-uuids
[handler]
(fn [request]
(handler (update-in request [:body :uuid-key] #(java.util.UUID/fromString %)))))
put it in the ns next to where your app handler is defined. the one that hooks up all the middleware
(defn routes-handler [database]
(-> routes/define-main-routes
(wrap-json-body {:keywords? true :or {:message "Broke"}})
(wrap-uuid-type)
(wrap-database-component database)))
(defn wrap-uuid-type
[handler]
(fn
([request]
(let [body (id->uuid (:body request) ids-to-replace)]
(handler (assoc request :body body))))))
(defn ^:private id->uuid
[original-map to-replace]
(reduce
(fn [cur-map k]
(if (contains? cur-map k)
(assoc cur-map k (string-util/to-uuid (k cur-map))) cur-map))
original-map to-replace))
(def ids-to-replace
[:id :user_id :seller_id])
(defn ^:private id->uuid
[original-map to-replace]
(reduce
(fn [cur-map k]
(if (contains? cur-map k)
(assoc cur-map k (string-util/to-uuid (k cur-map))) cur-map))
original-map to-replace))
(defn wrap-uuid-type
[handler]
(fn
([request]
(let [body (id->uuid (:body request) ids-to-replace)]
(handler (assoc request :body body))))))
(defn wrap-uuid-type
[handler]
(fn
([request]
(println "first on submit")
(let [response (handler request)
body (id->uuid (json/read-str (:body response) :key-fn keyword) ids-to-replace)]
(println (assoc response :body body))
(assoc response :body body)))))
yeah im confusing myself and changing things then getting frustrated and forgetting what i did
so couple things wrong. imagine this is run before the body is changed into a clojure map
scratch that. you see (let [response (handler request)
this line? You're calling the handler on the request without updating the body to be either json or uuid'd
now we've separated things. we have just a json middleware that turns application/json requests into clojure maps
java.lang.IllegalArgumentException: contains? not supported on type: org.eclipse.jetty.server.HttpInputOverHTTP
since the body looks like
:body #object[org.eclipse.jetty.server.HttpInputOverHTTP 0x1ab8640f "[email protected][c=0,q=0,[0]=null,s=STREAM]"]
chrome says yes but i just tried in postman with explicitly setting the content type and it works
curl localhost:3000 --request POST --header "Content-Type: application/json" --data '{"thing":"0f248583-d687-4417-b621-1ac1af92962a"}'
yup it does and i see that the content type is empty so its def a client facing issue now
thank you again dan. huge s/o you spent over an hour with me today thanks a ton!! i hope it goes smoothly too 🙂
i am not sure i am doing this tranformation at the right place or do i have to transform that request on the actual POST
route for each POST
request
but I only get [email protected]
spit
just calls str
on the thing you pass in. doall
realizes it all, but it's still a LazySeq
object.
What format do you want the data written out as?
pr-str I think will help
Yeah, I was about to suggest that but wondered what @sroller expected in the file...
I presume a list
thank you again dan. huge s/o you spent over an hour with me today thanks a ton!! i hope it goes smoothly too 🙂
thank you guys. I was eventually successful with (spit "file.edn" (with-out-str (clojure.pprint/pprint xml)))
my underlying problem: I'm trying to import 700mb xml from a my discontinued sporttrack3. It contains all my activities + GPS data since 2004. I'm playing around with Clojure since 2015. I'm finally getting serious with this one. (Did I mentioned I hate XML?)
My approach is to suck the entire file in and then work over the structure created with xml/parse. I found zip/xml-zip but I doesn't yield the results I'm expecting. I'm still at the very beginning with this.
ok - so pr-str obviously worked. I like the output of pprint better though. I can find better my way around. Need to sleep now, it's 1:17am. Cu l8ter.
Is there something like test categories in clojure through meta data or something or do people just use different namespaces?
Leiningen offers the ability to mark deftest forms with keywords, and then select subsets of deftest forms whose tests should be run via a function given that keyword, and returns true or false, specified in the project.clj file.
By namespaces is another way. I would guess there are others used by at least some people, but don't have any kind of exhaustive list to provide.
Thanks Andy, do you know where that first one is documented, using a keyword? That's what I was looking for but I couldn't track it down
And a bit of context: https://github.com/technomancy/leiningen/blob/master/doc/TUTORIAL.md#tests
Quick question about clojure equality check I am using emacs and I have flycheck-clj-kondo enabled and it gave me this message which I didn't understand
(= "X" X-find)
what is an idiomatic way to build a hashmap from two other hashmap and a list of keys such ad that for each key there is an order (not always the same) from which of the two other hashmaps should be tried first, then the next, then nil for value
I can type it out but I get the feeling this is exactly something I should be able to do more easily with clojure
merge
is more restricted than that, in that it always considers all keys in the order that you give them, with the last map having a particular key having its value appear in the result.
In the behavior you wish to have, do you want to be able to say that for key :foo
, the order of precedence is "1st map, then 2nd map, then 3rd map", but for key :bar
you want "2nd map, then 1st map, then 3rd map" ?
Hmmm. Never mind the "3rd map" part of my question, since you asked about only 2 input maps.
I don't have the output encoded, it wouldn't be necessary to do the work if I had, but I can type out the logic
input: [:key1, :key2, :key3]
and assuming there are to hashmaps map1
map2
output would be {:key1 (or (get map1 key1) (get map2 key1)), :key2 (get map2 key2) :key3 (or (get map2 key3) (get map1 key3))
note how the order for keys can change or sometimes just one of the maps are used. But there are only two maps and the keys are the same
because there are dozens of keys, even if it's easy to type it out, it's really hard to read and I would say it's not clean code
basically, I am preparing a hashmap to be sent for a remote API and I need to use user input (map1) or app-state (map2)
So do you expect there to be another input, which for each key somehow describes whether map1 should take precedence, or map2 should?
For example, the input could contain two maps, and two collections of keys ks1 and ks2, where keys in ks1 prefer to get their value from map1, and keys in ks2 prefer to get their value from map2?
I can even suspect that a third map might not be unlikely in some cases, although this is not the case at the moment
so that's why I didn't want to use something like what you suggest @andy.fingerhut because if it turns out that there will be 3 maps to be read from, this breaks down
I can't suggest how to write a function to do this, unless I know what the inputs are. If you want to extend it to a 3rd map, then I'm even more in the dark what form you want to specify the input in.
@andy.fingerhut I don't understand, my question I believe is exactly about the inputs. At the moment I just know what it needs to be there, but apart from 2 (or more) maps from which the keys have to come, and the actual keys, there is no input
so the input you asking me for is exactly the same thing I am asking about: how to encode the logic of which order the maps should be read?
So you want to call the function with parameters like (frobnifier [:key1 :key2 :key3] map1 map2)
, and it somehow magically figures out that :key1
should prefer the value from map1?
I'd like to figure out how to avoid using magic.
but there are dozens of keys and for each key there might be different order, I need to encode this order somehow, I want to make it explicit and easy to read and easy to extend
So here is a different function than the one I hint at above, I'll call it prefer-keys. It takes map1 and a collection ks1 whose values should be taken from map1 if they exist there, otherwise from map2. It also takes map2 and a collection ks2 of keys whose value should preferentially be taken from map2. Assumptions I will make for the moment, but you might prefer different behavior: (a) no key appears in both ks1 and ks2, and (b) the output map contains only keys in ks1 or ks2. If a key is in an input map that is in neither ks1 nor ks2, it will not be in the returned map.
(defn prefer-keys [m1 ks1 m2 ks2] (merge (select-keys m2 ks1) (select-keys m1 ks1) (select-keys m1 ks2) (select-keys m2 ks2))) user=> (prefer-keys {:a 1 :b 2} [:a :e] {:a -1 :c -3 :d -4} [:b :c]) {:a 1, 😛 2, :c -3}
I am looking for some ways I can debug when the program breaks at one point. So I am using emacs cider for clojure and then I have a program that will give me an error. It doesn't give me where it breaks or anything so I wanted to use a debugger to identify where my program breaks. So on emacs, I have to run the function (cider-eval-defun-at-point), but when I run that function, it gives me the error of the program and just doesn't work. Any tips or fixes?
let me clarify, you don't get a stack trace?
wait..so I just got my eval defun at point to work, but I don't see how this is a debugger
like how would I actually use it? I thought I could go through each expressions and see where it breaks???
For doom-emacs what I have to do is: • f9 on the function definition • f9 on the function call
where f9 is cider-debug-defun-at-point
it's working now. I messed up the functions I was doing eval not the debug this whole time thxs a lot 😄
is it possible to test a macro that throws an error if incorrectly written? if it was a normal function, I could write (is (thrown? IllegalArgumentException. TEST))
, but when it's a macro, the error is thrown at expansion/compile
you can use eval
inside is
or macroexpand
(cmd)user=> (deftest foo-test (is (thrown? clojure.lang.ArityException (macroexpand '(when)))))
#'user/foo-test
(ins)user=> (foo-test)
nil
This is interesting. I've been playing with this, trying to get it to work, and the only way to "pass the test" is to say (thrown? clojure.lang.Compiler$CompilerException (macroexpand '(...)))
you could also try/catch on the Compiler$CompilerException and then assert about the cause
okay, i'll give that a whirl
hmm - but to really ensure the test runs and can fail, you should return the exception from the try/catch, then assert on that outside the catch
got that working. further testing has shown that using macroexpand
in the repl works, but not in a test file run with lein test
. Using eval
works with lein test
as long as I fully-qualify the name: cond-plus.core/cond+
thanks so much for the help
macros are happy to expand to functions / macros that are not (yet?) in scope, so ensure that all the code that is in the expansion is required in the context you are using it (clojure won't enforce this in the way it would for functions)
of course this turns into an error at runtime, it's just that it won't be reported in the way we are used to
and the failing version
(ins)user=> (deftest foo-test (is (thrown? clojure.lang.ArityException (macroexpand '(when true)))))
#'user/foo-test
(cmd)user=> (foo-test)
FAIL in (foo-test) (NO_SOURCE_FILE:1)
expected: (thrown? clojure.lang.ArityException (macroexpand (quote (when true))))
actual: nil
nil
I'm having a bit of trouble understanding an error comming from this validation code:
(defn validate-message
""
[params]
(first (st/validate params message-schema)))
class clojure.lang.Keyword cannot be cast to class clojure.lang.Associative (clojure.lang.Keyword and clojure.lang.Associative are in unnamed module of loader 'app')
any ideas??
usually that means someone called (assoc x k v)
and x was a keyword
I'd check the args to validate, perhaps one of those args needs to be a map - the full stack trace (check *e
) will help here
#error {
:cause "Unable to resolve symbol: message-schema in this context"
:via
[{:type clojure.lang.Compiler$CompilerException
:message "Syntax error compiling at (/home/jferreira/Develop/projects/clojure/gestbook/src/clj/gestbook/routes/home.clj:24:10)."
ok this is nice...didn't know about this stack trace
(def message-schema
[[:name
st/required
st/string]
[:message
st/required
st/string]
{:message "message must contain at least 10 chars"
:validate (fn [msg] (>= (count msg) 10))}])
So the problem is with this def...
why wouldn't the evaluator be able to check this symb?
what happens when you load that on its own? it could be the definition just isn't in the right order (before usage) in the file?
When i eval it from the REPL there is no problem
is the definition before the usage in the file?
yes, right above it
(def message-schema
[[:name
st/required
st/string]
[:message
st/required
st/string]
{:message "message must contain at least 10 chars"
:validate (fn [msg] (>= count msg 10))}])
(defn validate-message
""
[params]
(first (st/validate params message-schema)))
(defn home-page [{:keys [flash] :as request }]
(layout/render request "home.html" (merge {:messages (db/get-messages)}
(select-keys flash [:name :message :errors]))))
calling it directly from the REPL says theres a syntax error....
oh, i think i see it, i'm closing the message vector before the map
It's working now
thanks
thanks @noisesmith! very helpful
How can I prepend all keys in a given map with "foo"?
(def raw {:id 71 :name "whatever"})
;; expected => {:foo/id 71, :foo/name "whatever"}
I would like it to be generic so I can't use #rename-keys
with a predefined list of from=>to rules.
The bigger task is the transformation of raw jdbc result to the proper Pathom format.
your example makes the key have a namespace "bar" but your sentence says you want to prepend "foo"
(reduce-kv (fn [all key value]
(assoc all (keyword "bar" (name key)) value)
{}
raw)
(->> raw
(juxt (map (comp (partial keyword "bar") name key)) val)
(into {}))
something like either of these ^ should workheh ya found that out when i decided to actually test it in cider
(map (juxt (comp (partial keyword "bar") name key) val)
could also do
(zipmap (map (comp (partial keyword "bar") name) (keys raw)) (vals raw))
thank you! @ddouglass
sometimes my calva -> nREPL connection grinds to a halt (auto formatting fails, the repl itself seems slow). Any thoughts on what’s going on/what I’m doing wrong?
@ryan072 I don't think it is you. It is latest Calva. Downgrade to .93 for now. And please PM me if you have done time to help me debug it. I can't reproduce.
Hello, all you fine Clojurians... I’m new to the Clojure(Script) ecosystem and I’m trying to get my bearings. One thing I’ve noticed as I look around for current state-of-the-art is that it seems many otherwise useful and well-documented libs have their last commits many months or even years ago. Is this an indication of abandonment (as it would’ve be in other ecosystems) or a consequence of the stability of Clojure implementations...as in “it’s working and it will continue working, so no need to touch it”? Any guidance here is much appreciated!
stability
new clojure versions very rarely break libraries, so unless something is mainly for interop with a more volatile ecosystem, changes are pretty rare once something works
It's also part of the Clojure mindset to a) try to keep the focus of a library small (so you don't get feature creep over time, which also means it can be "done" and need no further updates) and b) avoid breaking changes (fixing bugs is OK, adding features is OK, breaking anything is bad -- so we often use new names for things that are different). @nathan.probst And Welcome!