Fork me on GitHub
#core-async
<
2017-07-01
>
diginomad16:07:57

I have been exploring core.async And tried to implement : gophers daisy chain whispering from here https://www.youtube.com/watch?v=f6kdp27TYZs&amp;t=1600

diginomad16:07:13

this is what I have come up with

diginomad16:07:20

lein run is not printing the final value from the channel and is permanently stuck

noisesmith16:07:32

doseq returns nil

diginomad16:07:57

what about println?

noisesmith17:07:01

oh wait your println is in a weird place, never mind

diginomad17:07:22

i am very new to this language

noisesmith17:07:25

putting gopher-talk inside go is pointless

noisesmith17:07:44

all it does is block a go block - for zero benefit

diginomad17:07:58

should i use future

diginomad17:07:05

because i tried that too

noisesmith17:07:15

no, use put!, that's what it's for

diginomad17:07:34

why is this not working?

noisesmith17:07:35

this isn't the cause of your problem, it's just a random problem with the code

noisesmith17:07:41

not sure yet,still reading

diginomad17:07:43

is the go thread pool exhausted?

diginomad17:07:08

because I read some where that : go thread pool = # cores + 2

noisesmith17:07:14

you could be doing that yes, because you are doing blocking ops inside go

joshjones17:07:15

@diginomad can you back up for a moment and describe the problem statement? like, what's supposed to happen? I see the youtube presentation but I don't know the go language. describe what should happen

diginomad17:07:49

I am expecting a value of 3 to be printed

noisesmith17:07:57

@diginomad yeah, if N is high enough that code is guaranteed to deadlock

joshjones17:07:34

that's what you expect to happen, but that's not describing what should happen in order to produce that result. more detail on the process?

diginomad17:07:07

you mean,my logic?

joshjones17:07:46

i think i see now -- but maybe it will help you to talk it out too, so go ahead ๐Ÿ™‚

diginomad17:07:41

So,let is creating two initial channels left,right

diginomad17:07:18

putting 1 on right

diginomad17:07:01

in that loop i will do - left <- right;right = left ; left = new channel

diginomad17:07:28

basically the function call does left <- right;left+1

diginomad17:07:53

I made them as atoms as their state has to be changed every loop

diginomad17:07:08

i didin't know any other option than that

diginomad17:07:40

so, each {left <- right;right = left ; left = new channel} is supposed to be in a new "thread"

diginomad17:07:05

{left<-right;left = left+1} has to be in a new thread

noisesmith17:07:19

@diginomad I added one more println, it stalls on the blocking read inside the println at the end

diginomad17:07:21

sorry if I am making no sense

noisesmith17:07:25

the channel is empty so it blocks

diginomad17:07:48

i don't see it in the code why final channel is empty

diginomad17:07:54

does it have to do with reset! ?

noisesmith17:07:28

I just checked, after thedoseq is done both right and left are empty

diginomad17:07:04

left is empty surely

noisesmith17:07:22

I wouldnt' say surely anything about this code, it's weird

diginomad17:07:45

but why right,as i am resetting it to a channel which just had some stuff put in by gopher-talk

diginomad17:07:07

ya,it felt the same

diginomad17:07:14

but I couldn't uncomplicate it

diginomad17:07:20

with my limited knowledge

noisesmith17:07:46

because it has to read another channel, and you are doing channel reads of both channels before writing to either

noisesmith17:07:01

there's no code path that writes without reading first, so it's a deadlock

noisesmith17:07:07

since nobody can possibly read

noisesmith17:07:17

wait... no, right should be readable...

diginomad17:07:05

this issue aside

diginomad17:07:18

can i create go blocks in a loop?

noisesmith17:07:33

@diginomad OK I added a println to gopher-talk - the doseq is exiting before gopher talk finishes its channel write

noisesmith17:07:50

yes, you can

noisesmith17:07:27

but don't call functions that use >!! inside a go block

joshjones17:07:34

i have a solution, if you'd like to see it @diginomad

joshjones17:07:45

or, you can try some more

joshjones17:07:35

i tried to go from what you have, but the atoms and structure threw me off so i just started it from scratch

diginomad17:07:04

without atoms then?

joshjones17:07:26

yes, any time atoms are created local within a function it's a strange thing to see

diginomad17:07:16

okay,how to deal with changing states inside a function?

joshjones17:07:43

it could be a lot better but it's an initial 10-minute solution ๐Ÿ˜‰

(defn gopher-talk [right left]
  (go
    (>! left (inc (<! right)))))

(defn gophers-daisy-chain [n]
  (let [leftmost (chan 1)
        rightmost (chan 1)]
    (put! rightmost 0)
    (loop [right rightmost
           left (chan 1)
           i 0]
      (if (= i n)
        (gopher-talk right leftmost)
        (do
          (gopher-talk right left)
          (recur left
                 (chan 1)
                 (inc i)))))
    (<!! leftmost)))

diginomad17:07:47

using local let bindings i mean

noisesmith17:07:37

@joshjones - nice translation

joshjones17:07:04

thx, I could not understand the go language code but i think the idea is the same

noisesmith17:07:44

@diginomad generally we prefer to substitute mutation with recursion setting new immutable bindings

noisesmith17:07:57

both function arguments and let bindings are immutable

diginomad17:07:09

got the code,nice one josh

noisesmith17:07:11

(and loop bindings)

diginomad17:07:35

the thing is,I tried to do stuff exacyly like in video

diginomad17:07:54

but,i still can't understand why my code won't work entirely

noisesmith17:07:05

@diginomad (recur new-x new-y) in a loop is equivalent to resetting x and y

noisesmith17:07:30

notice that @joshjones also replaced your blocking channel ops with parking ones

diginomad17:07:18

its weird actually

diginomad17:07:36

because Clojure doesn't allow (go (>!! ) )

joshjones17:07:45

yes @noisesmith -- actually, if you change the parking puts and takes in gopher-talk with blocking ones, it does not work

diginomad17:07:46

but it allows when in a function like here

noisesmith17:07:09

right, >!! is not meant for use in go blocks, >! is

joshjones17:07:28

almost @diginomad -- it allows >!! in a go block, but does not allow >! outside a go block

noisesmith17:07:02

@diginomad also you can change all your calls to chan in your original with (chan 1) and that makes the original work

noisesmith17:07:19

core.async chans are nonbuffered by default, and that code stalls without buffering

noisesmith17:07:08

but use loop and rebinding and >! inside go blocks like in @joshjones example anyway, that's the right way to do it in clojure

diginomad17:07:04

and also, is it a bad practice to use any of the refs like atoms in let bindings?

noisesmith17:07:13

it's just weird

noisesmith17:07:23

better to avoid if possible (though not always possible, it usually is)

joshjones17:07:12

@diginomad if you take your original example and make gopher-talk contain the go block, and change the >!! to >! and the <!! to <!, you get interesting results --

(gophers-daisy-chain 50)
45
=> nil
(gophers-daisy-chain 50)
47
=> nil
(gophers-daisy-chain 50)
50
so, a nice little race condition

noisesmith17:07:21

atoms are for stateful things that are modified from more than one thread, usually

joshjones17:07:01

@diginomad I can't say I've ever seen an atom in production clojure code in a let binding -- would definitely say it's bad practice

diginomad17:07:29

i have been using it pretty frequently

diginomad17:07:35

will note that

diginomad17:07:05

i found it was weird when I used atoms inside map

diginomad17:07:19

that was a horrible debugging time

noisesmith17:07:37

yeah, atoms and laziness are a bad mix

joshjones17:07:33

once you shift your thinking from variable assignment to evaluation of expressions, and buy the whole "values are values, and they don't change" approach, you have to really try to find opportunities to use atoms -- they just don't come up that often

joshjones17:07:51

and your race condition as shown above is a perfect example of why mutable state is just an impediment to everyday development. problems are hard enough to solve without involving state

diginomad17:07:09

great point that!

diginomad17:07:17

will practice harder then

joshjones17:07:51

it's a (relatively) simple matter of looking at idiomatic code and beginning to model those best practices in your own code, and writing it often enough for it to become habit

diginomad17:07:03

but i have been solving only some HackerRank problems

diginomad17:07:32

not sure if that will improve my Clojure idiomatic style

diginomad17:07:37

suggestions?

joshjones17:07:43

I don't actually know what hackerrank problems look like -- if only your solution matters, or ...?

diginomad17:07:02

yes,only solutions matter

noisesmith17:07:15

for exercises, there's http://4clojure.com where you can see other people's solutions

diginomad17:07:40

thanks for the suggestion @noisesmith

diginomad17:07:52

that was helpful

diginomad17:07:08

hope you guys found it interesting too

joshjones17:07:15

yes sir, best of luck and see you around

dm317:07:27

I think hereโ€™s the same thing (daisy chain) in core.async and manifold - https://github.com/dm3/manifold-cljs/blob/master/examples/src/manifold_test/daisy.cljs

joshjones18:07:50

@diginomad I just rewrote this, the way I initially wanted to. much shorter, and more or less clear, depending on your familiarity with functions like partition

(defn gopher-talk [right left]
  (go (>! left (inc (<! right)))))

(defn gophers-daisy-chain [n]
  (let [rightmost (chan 1)
        leftmost (chan 1)
        chans (concat [rightmost] (repeatedly n #(chan 1)) [leftmost])
        chan-pairs (partition 2 1 chans)]
    (run! (fn [[r l]] (gopher-talk r l)) chan-pairs)
    (put! rightmost 0)
    (<!! leftmost)))

diginomad18:07:28

why is this idiomatic than your initial solution?

diginomad18:07:40

*more idiomatic

joshjones18:07:25

I wouldn't say it's more idiomatic necessarily, but it does avoid the explicit loops, and the idea is more simple: create pairs of side-by-side channels, link them up, and you're done

joshjones18:07:02

there's not as much of the notion of "the previous right gopher is now the left gopher" -- just thinks of them in pairs

diginomad18:07:53

got to know what run! is

noisesmith18:07:09

it's map without the return value or the alternate arities

noisesmith18:07:17

(or the laziness)

joshjones18:07:23

it's relatively new (1.7) so it's not as common but it should be used in place of (doall (map ... in cases like this

noisesmith18:07:45

the real alternative is dorun on map

noisesmith18:07:51

but yes, run! is better ๐Ÿ˜„

joshjones18:07:57

right good call

diginomad18:07:58

for,doseq === map,run!?

noisesmith18:07:05

pretty much yeah

joshjones18:07:58

have a good afternoon/evening/morning all, i'm off to enjoy the sunshine while it lasts here ๐Ÿ™‚

diginomad18:07:52

using the same logic as above but with this function

diginomad18:07:11

it prints the value

diginomad18:07:25

but is not terminating the program

diginomad18:07:31

had to kill it

diginomad18:07:27

what could be the reason?

joshjones19:07:18

need to call shutdown-agents in your -main function. explained here: https://clojure.org/guides/faq#agent_shutdown @diginomad

joshjones19:07:23

@diginomad FWIW futures will fail for large n, not a good use case here. you were probably just trying things, but wanted to mention