nubank/state-flow

Repeat flow in defflow

Closed this issue ยท 9 comments

I'm facing a use case where I have flows which need to be run more than once, then check.

Whishes:

(def flow1
  (flow do-stuff))

(def flow2
  (flow do-stuff))

(defflow
  (repeat flow1 and flow2 n times)
  (flow "final check"
    (match? expected
            effects-flow2-flow2-after-repeat)))

How am I doing right now:

(def flow1
  (flow do-stuff))

(def flow2
  (flow do-stuff))

(defflow
  flow1
  flow2
  flow1
  flow2
  flow1
  flow2
  flow1
  (flow "final check"
    (match? expected
            effects-flow2-flow2-after-repeat)))

It will be very handy to be able to set how many times each flow need to be run. if they should be alternate, how many time one should run before the other one get to run.

This test shows my use case:
https://github.com/nubank/mortician/blob/a02293c7f872409af4bbebcaee2c75f40cb2608c/postman/postman/replay_count.clj#L53

Can you help with that please?

Without writing macros you can do something like this to make it a little more concise:

(def flow1+flow2
  (flow "two flows in one"
    flow1
    flow2))

(defflow
  flow1+flow2
  flow1+flow2
  flow1+flow2
  (match? ...))

If you want to do something more programmatic, you'll need to write a macro and I think that such a feature is niche enough to not be included in state-flow itself, but rather be a helper inside your own codebase.

The macro would probably look like this

(ns state-flow.scratch
  (:require [state-flow.core :as state-flow]
            [state-flow.cljtest :refer [match?]]
            [state-flow.state :as state]))

(def flow1
  (let [run-count (atom 0)]
    (state-flow/flow "print the my call count"
                     (state/wrap-fn #(println (swap! run-count inc))))))

(defmacro flow-repeat [flow n]
  `(state-flow/flow ~(str "a flow repeated " n " times")
                    ~@(repeat n flow)))

(clojure.pprint/pprint (macroexpand `(flow-repeat flow1 5)))
(state-flow/run!
  (state-flow/flow "counting flow"
                   (flow-repeat flow1 5)
                   (match? "whatever"
                           1
                           1)))

You could also have the signature take the n first. I think something like this would work:

(defmacro repeat-flows [n & flows]
  `(state-flow/flow ~(str "a flow repeated " n " times")
                    ~@(repeat n flows)))

Then you could call it with inline flows:

(defflow a-flow
  (repeat-flows 5 flow1 flow2))

;; or

(defflow a-flow
  (repeat-flows 5
    (flow "flow1" ,,,)
    (flow "flow2" ,,,)))

There is no need for macros:

(m/sequence (repeat n flow1+flow2))

will get what you want. The return value of m/sequence is the list of all return values, if that is useful.

So:

(defflow my-flow
  (m/sequence (repeat n flow1+flow2))
  (match? ...))

I implemented a version of sequence that also takes a predicate, for the purposes of probing: https://github.com/nubank/state-flow/blob/master/src/state_flow/probe.clj#L24

Maybe we could think about exposing m/sequence and also this one in the spirit of avoiding references to cats.

Deep inside I feel people should just learn how to work with monads though :/

Alternatively we can show this is possible in the docs. Give examples of fmap and sequence

Deep inside I feel people should just learn how to work with monads

Yeah, I get that state-flow is your subversive revolution ;)

I think that adding to a README section about writing helpers, with fmap and sequence as examples, would be better than exposing more cats via state-flow's API, which seems like a bottomless pit to me.

(def sequence m/sequence)
would it be enough? or there is some value in actually wrapping it?

That might be sufficient, but I don't think we should do it. It's not core to state-flow, it's a helper for making helpers. Let's stick to documenting that.

I added a Writing Helpers section to the README.