Fork me on GitHub
#core-async
<
2019-08-29
>
rickmoynihan10:08:59

I’m curious what the reasoning for this (in the a/chan docstring) is: > If a transducer is supplied a buffer must be specified. What property of transducers requires there to be a buffer? Why can’t I have a channel that coordinates in lock-step producers and consumers with no buffering and transforms a value?

Alex Miller (Clojure team)13:08:18

transducers can create multiple outputs per step (`mapcat` is the canonical example) and those expanding transducers must have someplace to put the value (ie the buffer)

👍 4
rickmoynihan10:08:05

Second question: What is the best way to produce a filtered channel of results? I was assuming I’d use a transducer: Perhaps 1:

(defn filter-chan [f in out]
    (let [out (a/chan)]
      (a/pipeline 1 out (filter f) in)
      out))
Or 2:
(defn filter-chan [f in out]
    (let [filtered-in (a/chan 1 (filter f))]
      (a/pipe in filtered-in)
      (a/pipe filtered-in out)
      out))
I guess I’m surprised core.async doesn’t appear to have more conveniences for composing channels with xforms… Or am I missing something?

Alex Miller (Clojure team)13:08:08

you can compose xforms with comp

Alex Miller (Clojure team)13:08:27

so you can have multiple xforms per channel, and you can use pipe or pipeline or tap, etc to decide where to separate channels. it is an architectural choice how you break things up. I go into more depth on this in Clojure Applied

rickmoynihan13:08:43

yes, I’m well aware of comp… I think what I’m getting at by channel composition, is the separating channels you mention. i.e. in my case I need to fan out, then apply a filter to each fan. So by channel composition I really mean taking one of those branches as my input and “composing it” into a new input channel that has the filter applied.

Alex Miller (Clojure team)13:08:46

things like mult exist for fanning out

Alex Miller (Clojure team)13:08:11

and then you can connect a filter transducer to one out chan if needed

rickmoynihan13:08:04

yeah that’s exactly what I’ve done.

rickmoynihan13:08:55

but thanks a lot… I think what you’ve said about deciding where to split channels up and the architectural choices with that, is really what I’m getting at.

Alex Miller (Clojure team)13:08:01

there is no one answer there. the tools are available.

schmee10:08:31

I suppose that the buffer/transducer thing is a consequence of not having arity-overloading in Clojure, if you want no buffering then you can pass (buffer 1) as a buffer

rickmoynihan10:08:55

(buffer 1) isn’t no buffering… but good point, perhaps (buffer 0) works… will try. Seems too. In which case I don’t know why n must be positive… (a/chan 0 (map str)) ;; => Assert failed: fixed buffers must have size > 0

rickmoynihan10:08:15

Regarding composing channels and xforms… Something like this chan-xf seems like it might useful:

rickmoynihan10:08:16

(defn chan-xf [in xform]
    (let [out (a/chan 1 xform)]
      (a/pipe in out)))

  (defn filter-chan [f chan]
    (chan-xf chan (filter f)))

  (def fc (filter-chan odd? in-ch))

cgrand10:08:26

@rickmoynihan a transducer may produce many output items for a single input, that’s why there’s the need for a buffer. When the buffer is a fixed size buffer it will grow beyond its purported max size.

cgrand10:08:33

=> (def buf (a/buffer 1))
#'user/buf
=> (-> buf .-buf .size)
0
=> (def c (a/chan buf cat))
#'user/c
=> (a/go (a/>! c (range 10)))
#object[clojure.core.async.impl.channels.ManyToManyChannel 0x29f6aff5 "clojure.core.async.impl.channels.ManyToManyChannel@29f6aff5"]
=> (-> buf .-buf .size)
10

markmarkmark10:08:42

at 47:55 if the timestamp in the url doesn't work for some reason

rickmoynihan11:08:22

@cgrand: ahh of course that makes a lot of sense

rickmoynihan12:08:58

@markmarkmark thanks for the talk ref… that’s good to know. Also good to see that though the size isn’t actually fixed, it is a target size

markmarkmark12:08:33

it would be pretty awkward if someone made a buffer that was actually a fixed size

rickmoynihan12:08:46

Well transducers were retro fitted… I guess fixed sized buffers made much more sense prior to transducers being added.

cgrand12:08:50

Just to be clear with a transducing channel (equipped with a fixed buffer): • if the buffer is already full, the putter is parked • if the buffer becomes full during the transduction of the put element, the buffer is allowed to grow (because we are in nested calls and core async being “just a macro”, we can’t park in the middle of a non-core.async computation)

👍 4
markmarkmark12:08:16

right, but the issue is if someone is using a custom buffer whose underlying data structure can't grow (e.g. a j.u.c.ArrayBlockingQueue).

markmarkmark12:08:36

but, that probably doesn't happen often, and would only bite you once

cgrand12:08:05

Well it could go unnoticed for a long time as a blocking queue would cause the go block to block thus removing the thread from the thread pool. In the end you may have to wait a long time before reaching thread starvation.

markmarkmark13:08:32

I was imagining something like this, where the assumption is that add*! will only be called when there is room (as the Buffer docstring seems to state) and the non-blocking, throws-an-exception-when-full method add is used.

markmarkmark13:08:03

I admit, it does seem unlikely that someone would do this.

markmarkmark13:08:18

it's just the first thing I thought of when I was reminded of the quirk of the expanding fixed buffer

rickmoynihan13:08:51

I’m really only just starting to use core.async in anger; and I find it curious that there are effectively two forms of composition available. Composition of channels, and composition of transducers. Is this a fair observation? If so, what are the forces that drive one to prefer one form over the other?

rickmoynihan13:08:15

I should stay I have an understanding transducers already… so I’m wondering more about the composing channels side of things.

cgrand13:08:24

My own experience is that you shouldn’t compose channels. Don’t treat them as seqs. Use them to connect processes together.

markmarkmark13:08:17

perhaps illuminating: https://clojure.org/reference/transducers ctrl+f "compos" -> 7 results for compose/composition etc. https://clojure.org/news/2013/06/28/clojure-clore-async-channels ctrl +f "compos" -> 0 results

rickmoynihan13:08:52

I’m not sure it is illuminating. The process of tying channels together with processes in between, is effectively what I mean by composition. i.e. it seems common in core async, for everything to take an in and out channel. By channel composition all I mean is tying the ins and outs together.

rickmoynihan13:08:39

building channels out of other channels etc.

markmarkmark14:08:13

I see what you're saying. I guess I just don't like the word compose for channels.

markmarkmark14:08:56

I'm also a very light user of core.async, so I know a bunch of random trivia, but don't know that much about the nitty gritty of making things with core.async that people actually want to use.

rickmoynihan14:08:48

Like me a few days ago 🙂

markmarkmark13:08:01

that core.async news item is linked from the core.async repo as "Rationale"

rickmoynihan13:08:06

That makes some sense to me… but I’m not sure I grok all your nuance. By composition, I really mean taking an input channel and yielding an output channel with some transformation/process applied… https://clojurians.slack.com/archives/C05423W6H/p1567085983080300?thread_ts=1567074965.050600&amp;cid=C05423W6H