This page is not created by, affiliated with, or supported by Slack Technologies, Inc.
2022-03-29
Channels
- # announcements (7)
- # asami (13)
- # babashka (22)
- # beginners (52)
- # calva (95)
- # clj-kondo (14)
- # cljs-dev (7)
- # clojars (5)
- # clojure (94)
- # clojure-austin (5)
- # clojure-dev (15)
- # clojure-europe (25)
- # clojure-nl (18)
- # clojure-uk (15)
- # clojuredesign-podcast (28)
- # clojurescript (63)
- # copenhagen-clojurians (1)
- # cursive (3)
- # datalevin (7)
- # datascript (13)
- # datomic (13)
- # duct (14)
- # emacs (24)
- # events (1)
- # fulcro (13)
- # graphql (7)
- # kaocha (4)
- # lambdaisland (6)
- # lsp (22)
- # music (5)
- # off-topic (24)
- # rdf (1)
- # re-frame (3)
- # reitit (9)
- # shadow-cljs (23)
- # sql (15)
- # testing (4)
- # tools-build (6)
- # vim (7)
- # vscode (7)
- # xtdb (21)
Hey, folks! https://clojurians.slack.com/archives/C053AK3F9/p1648500209863569 sparked in #beginners (yeah, I chose the perfect place to discuss such a lightweight topic đ). So I'm cross-posting it here, perhaps there are more folks that would like to add their reasoning, which I would absolutely love to hear!
Hmm.. I wonder more about having namespaces as data
I bet there had been a LOT of discussions such that! đ Would love to go through them
In particular, I wonder, could a Clojure program (including all Clojure dependencies) be read as data? https://clojurians.slack.com/archives/C053AK3F9/p1648545017427979?thread_ts=1648500209.863569&cid=C053AK3F9
Yes. I recommend https://github.com/clj-commons/rewrite-clj for this.
Drop by #rewrite-clj if you want to chat or have questions. Clj-kondo's data analysis can also be handy depending on what you want to do.
Uh ho, you smart folks seem to have all possible stuff already invented! đ I should have expected no less. đ I'll take a look and may drop by to torture peeps with questions later on. đ Thank ya!
whatâs the best way in deps.edn to have a build where some dependencies are optional (e.g. like cheshire in clj-http)? In maven you can declare dependencies to âprovidedâ.
There is no support for provided per se - you can create an alias for the provided deps and include that when you need it
at least one practical reason: keys
and vals
return the keys and values in the same order for the same map
one could just return an ordered set for keys and a seq for vals that is in the same order as the keys
I may be wrong, but I think keys
(and vals
) return a lazyish seq (`APersistentMap$KeySeq`), which might be nice if your map is huge.
On what kind of data structure other than a map would you want to call keys
on? What would you expect it to return where a set is in some way more useful than a sequence for the return value of keys
?
Well, I will usually use a return of a keyword instead of whatever typically gets returned, and the outer loop looks for that and returns its own value at that point. A thrown exception is possible, but I'm loathe to do this.
But honestly, when this happens, then I try to step back and look for what I may be missing. For instance, doseq
may be more appropriate, and it has the :while
option. Or should I be using a reduce
at one of those layers? (that can call reduced
if it needs to break).
@U051N6TTC Are you putting the inner loop inside a let bracket to get the result of loop?
If you absolutely must do something of this form you could consider doing what https://github.com/IGJoshua/farolero does internally with the block
macro and use try/catch for control flow. It turns out the JVM is really good at optimizing this.
(try
(loop []
foobar
(loop []
(throw (ex-info "Break" {:break true}))))
(catch ExceptionInfo e
(when-not (:break (ex-data e))
(throw e))
foobar)))
Hence the "if"
I would generally avoid this if you can phrase it in a way that's more functional or that fits better into a list comprehension, but sometimes you really need imperative control flow.
I was talking in generalities, so it really depends. let
is a general thing that's useful for lots of things. if-let
too, if nil
is your escape value. It's up to you.
loop
is not something I like using. It often means that I missed an abstraction where I could use a function from clojure.core
. But if I do need to use loop
, then needing ANOTHER loop seems like a bad idea. I mean, you can use a pair of vars to control your loop, and update each accordingly.
yeah, that's fair and I would agree, but it would also depend on context. For example trying to write an implementation of an established algorithm that you can then use and later refactor to be more idiomatic it may be desirable to have the initial control flow more closely match the established implementations in order to verify correctness.
For instance, both of these print the same output:
(doseq [x (range 4) y (range (inc x))] (println x y))
(loop [x 0 y 0]
(when (> 4 x)
(println x y)
(if (= y x)
(recur (inc x) 0)
(recur x (inc y)))))
Note that the second one has only a single loop
Oh, I used an https://github.com/quoll/cljs-math/blob/f0cf8c59bc8a7c203cac1843c6e0493b69cec0bb/src/cljs/math.cljc#L316 not too long ago. It was a case of an exceptional condition in the data, and the flow control would have been too messy if I didn't. But I looked for alternatives first. Using throw
really was the best I could see for that case.
Yeah, I would agree this is better, I just think that the other options shouldn't be dismissed out of hand for reasons like the above.
Why does this work this way? I understand the EDN serialization, but it's... string đ
(->> (keyword "hello/there")
str
keyword
str
keyword
str
keyword
str
keyword)
:::::hello/there
Using str
on a keyword will give you a string representation of it, including the :
character. To get a string of the keyword's name/namespace, you can use name
or namespace
. [name - clojure.core | ClojureDocs - Community-Powered Clojure Documentation and Examples](https://clojuredocs.org/clojure.core/name) [namespace - clojure.core | ClojureDocs - Community-Powered Clojure Documentation and Examples](https://clojuredocs.org/clojure.core/namespace)
I'm not a beginner and I know what str
does.
I ask because it's the second time I need to use a to-string function that strips that :
sign. Could there be a better way...?
And yes, the source code for this would be here: (defn to-str [kw] (subs (str kw) 1)))
.
But It's not the first project that I have to do it in. And it's the second time in this project that I have to use it.
(defn kw->str [k]
(if-let [n (namespace k)]
(str n "/" (name k))
(name k)))
In general, you don't use str
with keywords at all. Serialization is usually not just stringifying, and deserialization is not just applying the original function to some string.You can just use name
.
(->> (keyword "hello/there")
name
keyword
name
keyword
name
keyword
name
keyword)
;; =>
:there
But then it means that (keyword ":hello/world")
should not parse :hello
into its namespace and instead use it as the name
only.
Because currently they parse string into this:
(namespace (keyword ":hello/world")) => :hello
So it's inconsistent with what you said about deserialization from string. Then it shouldn't interperet the string as a keyword with potential namespace.
(defn stringify [k]
(->> k
((juxt namespace name))
(remove nil?)
(clojure.string/join "/")))
would also workBut I'd probably go with @U2FRKM4TWâs solution.
Thanks everyone. I tried to have a discussion about why do we even have this. Because I thought that if I chain str
and keyword
a bunch of times I shouldn't end up with accumulating :
in the front.
p-himik answered it but I didn't like that there is no library function to serialize and deserialize a keyword without introducing :
and without writing new code.
I will just say, if you are concerned about round-tripping the answer is almost always prn-str
and clojure.edn/read-string
(edn/read-string (prn-str :hello/there))
;; => :hello/there
You can definitely do it without writing new codestr
just calls .toString
on the underlying object; the string representation of a keyword includes its colon. I would be surprised/irritated if I called .toString
on a keyword and didn't get the prefix colon.
But the problem is that (keyword :hi/there)
includes the :
into the namespace. So it's inconsistent.
But yes, thanks guys.
Yeah, arguably (keyword ":hi/there")
should return a keyword with no namespace and :hi/there
as a name. Maybe there is a use case for the rudimentary parsing, or it is for compatibility.
In general, consider keyword
a constructor for sanitised input, to create a keyword from name and optional namespace strings. It is not meant for parsing, as others have pointed out.
> Yeah, arguably (keyword ":hi/there")
should return a keyword with no namespace and :hi/there
as a name.
+1, (keyword ":hi/there")
resolves to the arity of ([name] ...)
, but then tries to parse passed in name
onto namespace+name.
the keyword
function will do profoundly silly things, if you ask it to
(ins)user=> (pprint (map (comp vector (partial apply keyword))
[[" " " "]
["/////" ":::::"]
["it doesn't really matter " " put anything you like in here"]
[(str (Object.)) (str (java.util.UUID/randomUUID))]]))
([: / ]
[://////:::::]
[:it doesn't really matter / put anything you like in here]
[:java.lang.Object@b273a59/94f3d2db-5461-4617-aff5-ad902760c835])
nil
user=>
even keyword literals can be super weird, for historical reasons
(ins)user=> :
:
should have done these above
(ins)user=> (keyword "))))))")
:))))))
(ins)user=> (keyword (pr-str (java.util.UUID/randomUUID)))
:#uuid "5c0cdfbd-fdc1-4174-9519-b00b1ccfcf8b"
How would one go about dynamically creating a quoted symbol, say 'this-is-a-string
? I tried doing (quote (str "this-is-a" some-variable))
but that just returns the whole (str ..)
rather than the result of it.
try (symbol hi)
đ
(= 'hi (symbol "hi"))
It worked! Thank you @U028ART884X
we're considering a core.async variant of iteration
, where the step fn returns a chan, rather than a direct value
Most async api functions receive callbacks and I may not want to bring core.async in as a dependency. I just want to get back a sequence, or at worst case a callback which will receive the sequence, not necessarily a channel. I also need to control the level of concurrent, back off, etc
to me that just sounds domain specific enough to warrant its own implementation, but maybe I'm wrong here.
with call backs, it becomes much harder to return a value representing the process like iteration
does
when diving into this rabbit hole last time i ended up creating an abominable "async-seq," which is pretty similar to async generators https://github.com/lilactown/async-seq/blob/main/src/town/lilac/async/seq.cljs
also, with call backs, it becomes much grosser to collect results into an accumulator
the "async-seq" / async generators is nice if your process can be split up into discrete async tasks (a la iteration) but wouldn't work well for true streams
none of my professional projects use core.async heavily. we use manifold in our services/APIs and some combination of promises and re-frame on our clients
ofc I'm not dissuading adding an iteration-chan
to core.async, it just doesn't solve the problem for me
I would be interested to hear your take on core.async, and I'm curious if it's an issue with the channel/csp style of programming, or if it's specifically an issue around the gotchyas with the CPS transformation?
Because if it's the former, then I'm curious what manifold gets you over csp, and if it's the latter, I'm curious if Project Loom would entirely solve that problem for you.
The benefit (only one) of callbacks is they're a universal API. Everyone can participate in the new construct immediately by connecting it to their async framework.
Alright, good to know. I've been very excited for Project Loom for a while, I'm hoping that others get as excited about it as me as we get closer to its launch.
@U3Y18N0UC also wrote an interesting blog about core.async and CLJS:
https://mauricio.szabo.link/blog/2020/06/11/clojurescript-vs-clojure-core-async/
In (CL)JS using the host's promises is usually sufficient, with a sprinkle of macros perhaps (`promesa`). Having an iteration
that's not tied to core.async seems like a good one to have but in addition core.async could also have its own version that would re-use the clojure.core async iteration perhaps.
@U5NCUG8NR unfortunately project loom doesn't effect the majority of my professional coding (clojurescript), but it is exciting nonetheless
ah, well that's certainly a good point, and it is unfortunate, and in those cases I definitely agree that core.async can feel quite limiting.
I tend to assume that every project that used core.async
to replace callbacks on ClojureScript is broken in some way.
Things do not work as expected - you either drop results or you have a bug. On Clojure, it could work (although I do think it's too low-level for my tastes) but not in ClojureScript...
(Unless, of course, you're trying to replace Javascript's Promise
with async-chan
. That works, but I don't see any reason to do it)
I didn't know about clojure.parallel
ns at all - just found it by coincidence.
Is that something that anybody is using? Can it still be useful these days?
It seems to be referencing old ForkJoin implementation before it was included in JDK 7.
well it is marked as deprecated.
just looking at the api of it, seems like its usage overlaps and is replaced by reducers
Yes, it's deprecated, but as always, no reason to break stuff so it's out there