Fork me on GitHub
#core-async
<
2016-07-06
>
jmglov10:07:08

Hi all! I created a really simple ping-pong simulation: https://gist.github.com/jmglov/8b89fe00aefe3f94f3b904eae9db32c1

jmglov10:07:03

It works just fine with blocking puts and gets (`<!!` and >!!).

jmglov10:07:27

But when I switch to <! and >! and change my loop to go-loop, my play function simply returns immediately after printing "Ping" once.

jmglov10:07:57

Clearly I'm misunderstanding something very fundamental, but I'm not what what. 🙂

jmglov10:07:37

Is go-loop ... recur not the way to loop?

darwin10:07:39

@jmglov: go version of your code is non-blocking, that means the function with go-loop “spawns another thread” and returns immediately, (quotes used because in reality is it a bit more complex behind the scenes)

jmglov10:07:14

That makes sense, but shouldn't I still see pings and pongs printed from the spawned "thread"?

jmglov10:07:36

The go macro basically makes some sort of magic FSM, right?

darwin10:07:42

yes, if the process is still up and running, but if your -main quits after calling the play function, core.async helper threads quit as well I think

jmglov10:07:11

I was just doing this in my REPL.

jmglov10:07:26

So my main thread is still up.

darwin10:07:34

hmm, that should work, I guess

jmglov10:07:02

I guess I should check out the returned channel.

jmglov10:07:23

Is there a way to "peek" at a channel without consuming a value?

darwin10:07:00

I think lines #16 and #17 should be wrapped in <!

darwin10:07:17

can you post your non-blocking version?

darwin10:07:24

I could review it

jmglov10:07:46

Thanks! Coming right up.

jmglov10:07:55

Do I need to transduce the channel or something?

jmglov10:07:36

Am I effectively getting back a lazy seq from go-loop?

darwin11:07:21

go-loop gives you a normal channel back

darwin11:07:33

it is just a sugar for (go (loop…))

jmglov11:07:23

Interesting. So >! and <! always need to be wrapped in a go form, even when inside a go-loop?

darwin11:07:35

I’m not familiar with Clojure version of core.async, this is how I would write it in ClojureScript

jmglov11:07:00

That does work, but my timeout channels don't seem to have any effect, and I get this output:

Ping
Game over!
Pong
Ping
Pong
Ping
Pong
Ping
Pong
Ping
Pong
Ping

darwin11:07:43

<! must be inside go AFAIK, and “inside” means inside current code block, it does not transition across function call boundaries as you wrongly assume

jmglov11:07:22

OK, that actually makes sense.

jmglov11:07:01

But that still doesn't explain what the timeouts aren't honoured.

jmglov11:07:21

Let me try yet another version, but this time without functions.

darwin11:07:55

have to go, will be back in 10mins

jmglov11:07:08

And this one works perfectly:

(defn play []
  (let [ch (async/chan 1)]
    (async/go-loop [cnt 0]
      (if (= cnt 0)
        (do
          (>! ch :ping)
          (println "Ping")
          (<! (async/timeout 500)))
        (let [ball (<! ch)]
          (if (= ball :ping)
            (do
              (>! ch :pong)
              (println "Pong")
              (<! (async/timeout 500)))
            (do
              (>! ch :ping)
              (println "Ping")
              (<! (async/timeout 500))))))
      (if (< cnt 10)
        (recur (inc cnt))
        (println "Game over!")))))

jmglov11:07:16

Thanks for the help.

jmglov11:07:32

Now I'll do a super-yucky one with macros instead of functions.

jmglov11:07:06

BTW, what is the best practise for DRYing up core.async code, given that functions are a bit problematic?

darwin11:07:27

I think functions should work as well, no need for macros here

jmglov11:07:44

But like you said, go blocks don't carry across function boundaries.

jmglov11:07:09

Which seems to be problematic for my timeout channels at the very least.