2
votes

I have two unidirectional core.async channels :

  • channel out can only put!
  • channel in can only take!

And since this is ClojureScript the blocking operations are not available. I would like to make one bidirectional (in-out) channel out of those two (in and out).

  (def in (async/chan))
  (def out (async/chan))
  (def in-out (io-chan in out)) ;; io or whatever the solution is

  (async/put! in "test")
  (async/take! ch (fn [x] (println x))) ;; should print "test"
  (async/put! ch) ;; put into ch is equivalent to putting into `out`

I tried something like the following (not working) :

(defn io-chan [in-ch out-ch]
  (let [io (chan)]
    (go-loop []
      (>! out-ch (<! io ))
      (>! io (<! in-ch))
      (recur))
    io))

A schema might help :

    out                              in-out
 ---------------> (unused)         
 <---------------             <---------------

   in
---------------->             ---------------->
<---------------- (unused)

Also, closing the bidirectional channel should close both underlying channels.

Is is possible ?

2
Sorry, I don't get your use case, maybe you could add a code example of what you want ?Valentin Waeselynck
@ValentinWaeselynck I edited my question, hope it makes sense now.nha
Still not sure I get it :/ you mention 6 channel names in your description (A, B, in, out, io, ch). would help if you unified thisValentin Waeselynck
@ValentinWaeselynck yes right, corrected :)nha

2 Answers

1
votes

If I understand your use case right, I believe what you're trying to do is just a one-channel job.

On the other hand, if what you're trying to do is to present a channel-like interface for a composite of several channels (e.g some process takes data from in, processes it, and outputs the result to out), then you could always implement the right protocols (in the case of ClojureScript, cljs.core.async.impl.protocols/ReadPort and cljs.core.async.impl.protocols/WritePort).

I would personnaly not recommend it. Leaving aside the fact that you'd be relying on implementation details, I don't believe core.async channels are intended as encapsulation for processes, only as communication points between them. So in this use case, just pass the input channel to producers and the output channel to consumers.

1
votes

Your example shows a flow basicly like this:

io ---> out-ch ---> worker ---> in-ch ---> io
^-------------------------------------------*

If we assume that worker reads from in-ch and writes to out-ch then perhaps these two channels are reversed in the example. if worker does the opposite then it's correct. in order to prevent loops it's important that you use non-buffered queues so you don't hear your own messages echoed back to yourself.

as a side note, there is no such thing as unidirectional and bi-directional channels. instead there are buffered and unbufferd channels. If we are talking over a buffered channel then when I have something to say to you, I park until you happen to be listening to the channel, then once you are ready to hear it I put my message into the channel and you receive it. Then to get a response I park until you are ready to send it, and once you are, you put it on the channel and I get it from the channel (all at once). This feels like a bi-directional channel though it's really just that unbuffered channels happen to coordinate this way.

If the channel if buffered then I might get my own message back from the channel, because I would finish putting it on the channel and then be ready to receive the response before you where even ready to receive the original message. If you need to use buffered channels like this then use two of them, one for each direction and they will "feel" like uni-directional channels.