Take:
while(fn -> Node.ping(some_node) != :ping end, 30_000)
Could be written: Stream.unfold(:pang, fn
:pang -> {:some_node, Node.ping(:"some_node")}
:ping -> nil
end)
|> Stream.zip(Stream.interval(1_000))
|> Stream.take_while(fn {_, ts_cnt} -> ts_cnt < 30 end)
|> Stream.run()
Which in Python would probably be something like (not using the Walrus, and made up system lib names): node_response = Node.ping("some_node")
start_time = Time.millis()
while node_response == "pang":
node_response = Node.ping("some_node")
if Time.time() - start_time > 30000:
break
Time.sleep(1000)
The Python version is pretty simple, but really isn't any shorter. It also intertwines the timing logic and node ping actions. Although it takes a bit of practice to become fluent with the stream approach, it's much more compose-able. Take the example in the article where you end up wanting to run a bunch of functions with a timeout on some interval, you can cut the middle 2 lines into a new function in a utility library. Then you could rewrite the code like (notice it's just cutting and pasting complete lines): Stream.unfold(:pang, fn
:pang -> {:"some_node", Node.ping(:"some_node")}
:ping -> nil
end)
|> MyUtils.with_time(max: 30_000, every: 1_000)
|> MyUtils.log_max_ping_count()
|> Stream.run()
Usually re-writing something into this form makes me think of many programming tasks as a set of discrete steps. Do the first stream, then add the second step, and so forth. This tends to make it easier to dive back into code after a while as each step has to be incremental. Also a side note, if you _really_ want to cheat and need to use a mutable variable, you can use (abuse?) `Process.get/1` and `Process.put/2` with a for-loop. CouchDB uses a similar idea to create stateful iterators if I recall the code correctly.