Fork me on GitHub
#clojure
<
2022-03-29
>
andrewzhurov09:03:15

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!

mpenet09:03:03

I think there were discussions about immutable namespaces at some point

andrewzhurov09:03:20

Hmm.. I wonder more about having namespaces as data

andrewzhurov09:03:55

I bet there had been a LOT of discussions such that! šŸ˜„ Would love to go through them

andrewzhurov09:03:04

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&amp;cid=C053AK3F9

lread14:03:17

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.

andrewzhurov17:03:51

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!

roklenarcic10:03:39

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ā€.

Alex Miller (Clojure team)11:03:50

There is no support for provided per se - you can create an alias for the provided deps and include that when you need it

SK10:03:50

Why is keys not returning a set? So that it works with arguments other than map?

lassemaatta10:03:23

at least one practical reason: keys and vals return the keys and values in the same order for the same map

šŸŽÆ 3
SK10:03:48

there are sets with predictable ordering, so I don't see how this can be an argument

SK10:03:36

one could just return an ordered set for keys and a seq for vals that is in the same order as the keys

genmeblog10:03:33

you can always call:

(.keySet {:a 1 :b 2 :c 2})
;; => #{:a :b :c}

šŸ‘ 1
genmeblog10:03:28

It returns only read-only set though.

lassemaatta10:03:00

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.

andy.fingerhut21:03:23

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?

pinkfrog14:03:49

How would you typically break out from a nested loop?

quoll14:03:03

Do you mean a nested loop/recur?

pinkfrog14:03:35

Yes. Break out the two loops.

pinkfrog14:03:15

(loop []
   foobar
   (loop []
      break-out-both-loops)
   foobar)

quoll14:03:33

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).

šŸ‘ 2
pinkfrog15:03:38

@U051N6TTC Are you putting the inner loop inside a let bracket to get the result of loop?

Joshua Suskalo15:03:27

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)))

quoll15:03:19

Heh. That's what I was saying that I avoid šŸ™‚

Joshua Suskalo15:03:07

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.

quoll15:03:59

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.

Joshua Suskalo16:03:58

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.

quoll16:03:59

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

šŸ‘ 2
quoll16:03:25

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.

šŸ‘ 1
Joshua Suskalo16:03:29

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.

Martynas Maciulevičius14:03:20

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

eval-on-point14:03:05

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)

p-himik14:03:28

Also there's #beginners

Martynas Maciulevičius14:03:07

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.

āž• 1
p-himik14:03:23

(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.

jimmy14:03:12

You can just use name.

(->> (keyword "hello/there")
     name
     keyword
     name
     keyword
     name
     keyword
     name
     keyword)

;; => 

:there

Martynas Maciulevičius14:03:58

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.

R.A. Porter14:03:21

(defn stringify [k]
  (->> k
       ((juxt namespace name))
       (remove nil?)
       (clojure.string/join "/")))
would also work

R.A. Porter14:03:18

But I'd probably go with @U2FRKM4TWā€™s solution.

Martynas Maciulevičius14:03:58

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.

jimmy14:03:11

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 code

šŸ™Œ 1
āœ… 1
R.A. Porter14:03:14

str 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.

Martynas Maciulevičius14:03:50

But the problem is that (keyword :hi/there) includes the : into the namespace. So it's inconsistent.

āž• 1
Ferdinand Beyer15:03:20

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.

āœ… 1
āž• 1
andrewzhurov18:03:57

> 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.

noisesmith19:03:10

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=>

noisesmith19:03:27

even keyword literals can be super weird, for historical reasons

(ins)user=> :
:

noisesmith19:03:23

should have done these above

(ins)user=> (keyword "))))))")
:))))))
(ins)user=> (keyword (pr-str (java.util.UUID/randomUUID)))
:#uuid "5c0cdfbd-fdc1-4174-9519-b00b1ccfcf8b"

simple_smile 1
Asko Nōmm14:03:15

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.

Martynas Maciulevičius14:03:36

try (symbol hi) šŸ™‚ (= 'hi (symbol "hi"))

Ben Sless15:03:08

Is there a recommended way to work with iteration in an asynchronous context?

ghadi15:03:45

we're considering a core.async variant of iteration , where the step fn returns a chan, rather than a direct value

Ben Sless15:03:40

Any chance for anything callback based?

ghadi15:03:20

talk more about the use case

ghadi15:03:30

callbacks can enqueue onto channels

Ben Sless16:03:53

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

Joshua Suskalo16:03:38

to me that just sounds domain specific enough to warrant its own implementation, but maybe I'm wrong here.

lilactown16:03:55

with call backs, it becomes much harder to return a value representing the process like iteration does

lilactown16:03:28

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

lilactown16:03:33

also, with call backs, it becomes much grosser to collect results into an accumulator

Ben Sless16:03:19

Eventually you end up reinventing reactive streams

šŸ˜‚ 1
Ben Sless16:03:47

I'll have to use either core async, missionary or manifold

lilactown16:03:59

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

ghadi16:03:55

what's wrong with using a channel?

lilactown16:03:43

i have nuanced opinions on core.async

Ben Sless16:03:49

Other people's code

lilactown16:03:57

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

lilactown16:03:28

ofc I'm not dissuading adding an iteration-chan to core.async, it just doesn't solve the problem for me

Joshua Suskalo16:03:12

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?

Joshua Suskalo16:03:49

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.

Ben Sless16:03:11

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.

lilactown17:03:07

it's mainly the latter

Joshua Suskalo17:03:58

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.

borkdude17:03:40

@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.

lilactown17:03:32

@U5NCUG8NR unfortunately project loom doesn't effect the majority of my professional coding (clojurescript), but it is exciting nonetheless

Joshua Suskalo17:03:18

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.

mauricio.szabo17:03:04

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...

mauricio.szabo17:03:25

(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)

ghadi15:03:02

it will allow pipelining of the step fn

ghadi15:03:24

rather than fetch1 consume1 fetch2 consume2 fetch3 consume3....

ghadi15:03:34

the fetch and consume could be concurrent:

ghadi15:03:01

fetch1 fetch2 fetch3 consume1 consume2 consume3

ghadi15:03:10

-----------time--------------------->

jumar19:03:15

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.

Joshua Suskalo19:03:52

well it is marked as deprecated.

Joshua Suskalo19:03:27

just looking at the api of it, seems like its usage overlaps and is replaced by reducers

jumar19:03:08

Yeah, I thought it's just an old curiousity šŸ™‚ Thanks!

Alex Miller (Clojure team)20:03:29

Yes, it's deprecated, but as always, no reason to break stuff so it's out there

šŸ‘ 2