Fork me on GitHub
#clojure
<
2020-09-09
>
Chris McCormick08:09:03

is it an anti-pattern to do something like the following?

(let [x (atom [])]
  (for [y some-collection]
    (some-async-modification-with-callback y #(swap! x conj %)))
as opposed to building a purely functional structure and using chans and e.g. async/map?

cgrand08:09:23

for being lazy the body may not be evaluated (beware of the repl forcing realization). Use doseq. How do you plan to wait for termination?

👍 3
Chris McCormick08:09:37

You mean if one of the async modification functions never returns or errors out? What I am really wondering is if using an atom like this as a temporary mutatey variable, which is basically like using a let in Javascript, is frowned upon.

didibus08:09:58

I think he means like, how do you know the thing that will later read the value of x will do the read after all async operations have added their result to it?

didibus09:09:53

Using mutation is always "preferred to be avoided", but atoms are also there for a reason, so it's kind of up to you to decide if this is some ugly hack in the context of what you are doing, or a necessary way that ends up making things simpler

👍 3
Chris McCormick09:09:29

Ah yes I see what you mean about receiving all values and knowing when it's done. In that case creating chans and pulling from them in an async/map would be better.

didibus09:09:26

What is making your function async though?

didibus09:09:49

I'm just thinking, agent kind of do what you're expecting and allow you to wait on them being done processing. But they also implement asynchronous behavior, so if the function is already async by other means it won't work

didibus09:09:22

Otherwise in your case, you could use promise, like pass a callback that delivers the promise.

Chris McCormick09:09:09

i think the use case where i ran into this wasn't actually async. it was a javascript function which returned intermediate values via a callback.

didibus09:09:11

(for [y some-collection]
  (let [p (promise)]
    (some-async-fn-with-clbk y #(deliver p %)))

didibus09:09:34

Oh you're talking ClojureScript?

Chris McCormick09:09:56

but i guess the question applies in Java too

Chris McCormick09:09:55

finding the original use case

Chris McCormick09:09:42

var map = new ROT.Map.Arena(3, 3);
var userCallback = function(x, y, value) {
    // do something with x, y, value e.g. store them
}
map.create(userCallback);
you could have a simliar situation in java/clojure

didibus09:09:48

In ClojureScript I'd say core.async is the way to go

didibus09:09:15

But what you do is fine as well, if you don't care to know that everything is done conjing before you read

Chris McCormick09:09:43

i guess the reason i got confused is because map.create() is not actually asynchronous. it blocks until all the values have been called with userCallback.

Chris McCormick09:09:59

so it seems strange to set up chans and use core.async

didibus09:09:07

Oh, hum, if it blocks I think what you did is fine

Chris McCormick09:09:52

yeah that temporary atom is what my friend suggested and i didn't like it but i think you and he are right that it's actually a good use of atom here.

didibus09:09:53

Cause basically that API wants you to use it in an imperative way. Since it doesn't return results, but instead provides them to your callback

👍 3
didibus09:09:56

That's my 2 cent

fullNameHere10:09:21

Given a nested vector like [[0 1 "X"] [3 4 5 ] ["O" 7 8] ], how could I write a function so that everything gets printed with a breakline for every new vector?. Should return 0 1 "X" 3 4 5 "O" 7 8

p-himik10:09:33

I'd use a nested doseq with a (println) after the inner one.

p-himik10:09:14

Actually, you can just (apply println v) in doseq.

p-himik10:09:28

prn, if you want to preserve double quotes.

fullNameHere11:09:27

Thank you. I wasn't sure how. Ill give that a shot.

dharrigan11:09:38

This doesn't seem to respond anymore

dharrigan11:09:16

A heroku error is returned

dharrigan11:09:39

Should be used instead?

Sam Ritchie15:09:33

hey all, curious if anyone had some advice on how to structure namespaces in a numerical library. I'm implementing a number of minimization routines, all of which have sort-of-similar interfaces

Sam Ritchie15:09:16

and what I'm rebelling against a bit is, say, Scipy's organization style, where function after function are stacked into the 1000s of lines of code

Sam Ritchie15:09:29

one idea is to use a single namespace for every implementation of "minimize"; say, sicmutils.numerical.minimize.brent, sicmutils.numerical.minimize.golden-section etc, and then to alias all the implementations (or provide a dispatching fn) in sicmutils.numerical.minimize

Sam Ritchie16:09:17

the con is that we end up with many namespaces with nested names. but the pro is that the various methods are MUCH easier to learn and describe if they're split up. Curious to hear what folks think

p-himik16:09:09

A relevant SO question: https://stackoverflow.com/questions/15580807/sharing-functions-between-namespaces-in-clojure Check out the email thread linked in the accepted answer.

p-himik16:09:38

There's also quite a few relevant discussions if you search for "potemkin": https://groups.google.com/g/clojure/search?q=potemkin

Sam Ritchie16:09:38

nice, this is great

Sam Ritchie16:09:46

good grist for the mind

Joe Lane16:09:21

This is actually in response to your "are multi-methods fast" question.

Sam Ritchie16:09:06

interesting, I have not

Sam Ritchie16:09:32

@U0CJ19XAM I don't quite see here how resolution is handled for different types...

Joe Lane16:09:48

https://github.com/uncomplicate/neanderthal/blob/81e943f86875ec595bdde9c3053de264c6606235/src/clojure/uncomplicate/neanderthal/core.clj#L155 That ^^^ is used across different data types as well as engines. It may be more involved than what you're looking for but it's stupid fast. I'm sorry I can't speak more to the neanderthal approach right now, but I highly recommend studying what was done in this library and how it interacts with cpus / gpus X cuda / opencl / blas. it's an incredible testament to clojure.

Sam Ritchie16:09:30

yes, this is a fantastic recommendation

Sam Ritchie16:09:50

to the extent that I can I want to simply extend the ability to use Neanderthan's data types into the sicmutils system

emccue16:09:54

Is there something in clojure built in that does this?

emccue16:09:57

(insert-after [1 2 3] 3 "a")
=> [1 2 3 "a"]
(insert-after [1 2 3] 2 "a")
=> [1 2 "a" 3]
(insert-after [1 2 3] 1 "a")
=> [1 "a" 2 3]
(insert-after [1 2 3] 444 "a")
=> [1 2 3]

emccue16:09:12

(defn insert-after [items after item]
  (let [idx (.indexOf items after)]
    (if (< idx 0)
      items
      (vec (concat (subvec items 0 (inc idx))
                   [item]
                   (subvec items (inc idx)))))))

delaguardo16:09:37

(defn insert-after [coll idx item]
  (let [[begin after] (split-at idx coll)]
    (concat begin (cons item after))))

emccue16:09:24

I wrote this, but I know concat isn't ideal

Sam Ritchie16:09:59

here's a lazy version, for all sequences

emccue16:09:44

I'm using it for inserting into an interceptor chain with pedestal

emccue16:09:49

so i think it needs to be a vec

potetm17:09:36

why not concat?

emccue04:09:20

I think it nukes itself on large sequences

cgrand17:09:20

(defn insert-after [items after item]
  (into [] (mapcat #(cond-> [%] (= % after) (conj item)) items)))

potetm17:09:33

Purposely not using xform here or just a misplacement of parens? I ask because I’m curious if there’s something I’m not aware of re: xforms.

seancorfield17:09:13

(mapcat f) returns a transducer.

seancorfield17:09:09

You were asking why @U3E46Q1DG didn't use the xform version!

seancorfield17:09:21

(I misread the parens)

seancorfield17:09:07

user=> (defn insert-after [items after item]
  (into [] (mapcat #(cond-> [%] (= % after) (conj item))) items))
#'user/insert-after
user=> (insert-after (range 10) 3 :x)
[0 1 2 3 :x 4 5 6 7 8 9]
user=> 

potetm17:09:12

Yes, exactly. I’m thinking the xform master might have something to teach!

seancorfield17:09:16

I think a lot of us "old 'uns" are so used to the non-transducer versions of core functions that we instinctively dash off quick snippets of code that way...

potetm17:09:02

Yeah prolly.

cgrand18:09:54

Actually it was meant to use a transducer but I rushed it right before dinner. 😀

✔️ 6
cgrand20:09:54

And to be honest I don’t like creating a vector just to concatenate it immediatly after.

cgrand20:09:06

So maybe

(defn insert-after [items after item]
  (into [] (fn [rf] (fn ([acc] (rf acc)) ([acc x] (cond-> (rf acc x) (= x after) (rf item))))) items))
(not checking for reduced on purpose: there’s only into below us)

cgrand20:09:11

But then why bother, collapsing everything in a reduce is no longer:

(defn insert-after [items after item]
  (persistent! (reduce (fn [v x] (cond-> (conj! v x) (= x after) (conj! item))) (transient []) items)))

emccue04:09:56

yeah anything xform or transducer goes over my head still, so i'm gonna pass on those versions