How are clojurians handling control flow on their projects?

how do you handle forks in logical paths with interceptors?

Interceptors are made out of functions, so nothing special. You can enqueue more interceptors during the execution of the chain, that is a way to handle logical forks specific to interceptors.

I have an interceptor fetching paginated data, it enqueues another interceptor if more rows are available.

That’s something I really dislike about how interceptors are implemented, both for performance reasons and determinism

Performance is a trade-off, but with negligible impact when interceptors chain deals with I/O. I don’t understand the determinism issue, interceptor chain can be as deterministic as a composition of functions.

What I mean is they are not static. A FSM is a static representation of a similar idea.
When the interceptors chain is dynamically modified at run time, while you can derive the flow of the program, it is not explicit.
It hides use cases which may not be immediate when you first look at the “machine”

2 Likes

The idea of using BT in FSM came from the weakness of Hierarchical statemachines - they simply don’t compose. You can have factory functions, but you need always to pass environment information (at least where to continue - target transition).

Whereas in BT it’s very simple to compose, as children only communicate success/failure information to the parent, therefore children does not need to know anything about the environment/surrounding states.

I came up with something like this:

{:start-state   :a
 :success-state :b
 :states        {:a {:start-state   :a.1
                     :success-state :a.2
                     :fail-state    :a.3
                     :states        {:a.1 {:transitions [{:event  :action-1
                                                          :target :a.2}]}
                                     :a.2 {}
                                     :a.3 {}}
                     :transitions   [{:internal-event {:event :succeeded}
                                      :target         :b}]}
                 :b {}}}

FSM initializes into {:a {:a1 {}}} state waiting for :action-1. :action-1 transitions FSM to {:a {:a2 {}}} which is at the same time marked as :success-state. FSM generates internal event :succeeded (or failed for the :fail-state) which can be processed in parent without any other context.

Another use case, came from the demand of linear FSM flows. In normal (success) case flow is linear and I needed to create transitions linking states. With BT-like FSM, I needed only to have a special type of state, which can transition to the next child on previous child success:

{:type :sequence
 :states [:a {...}
          :b {...,
              :transitions [{:internal-event {:event :failed}
                             :target :a}]}
          :c {...}]}

States are ordered here (using [] notation instead of {}). The FSM initializes to {:a {}} state, and on success parent transitions to {:b {}}, which can transition back to :a in case of error or continue to :c.

1 Like

This topic was automatically closed 182 days after the last reply. New replies are no longer allowed.