ztellman/automat

`permissable?` predicate?

timvisher opened this issue · 5 comments

I'm trying to express the concept of valid transitions between states for one of the domain entities in my system.

I have an fsm that does this nicely: [1 (a/* [2 3]) 4].

Requests to move the domain entity from its current state (any of 1, 2, 3, or 4) to another state (say from 1 to 4 or 1 to 2 or 2 to 3 or 3 to 2) come in via http requests and I'd like to return a 409 in the event that the transition would not be allowable given the above fsm.

However, to do that, it appears that I have to construct an entire state map with knowledge of the internal state-indexes used by the fsm once it's been compiled. There doesn't appear to be any way to turn the compiled fsm into, say, a state transition table like {1 #{2 4} 2 #{3} 3 #{2 4}} which could then be used in turn to implement a possible? or permissible? predicate.

So, am I missing something or could something like permissible? be implemented directly in automat? Ideally, it would be something like (a/permissible? fsm (things-current-state-as-named-in-fsm-data thing) proposed-state)

I will say that I may be fundamentally misunderstanding this library and maybe even FSMs in general so forgive me if that's the case and maybe point me in the direction of some reading I could do/a different library I could use.

Thanks in advance!

domkm commented

(-> compiled-fsm meta :fsm) should return the state transition table that you are looking for. You can also generate that from a raw FSM using automat.core/precompile.

@domkm I had never thought to check the meta! That's awesome.

Sadly, it doesn't seem to work like I described above?

user=> (-> (a/compile [1 (a/* [2 3]) 4]) meta clojure.pprint/pprint)
{:fsm #<fsm$dfa$reify__4708 automat.fsm$dfa$reify__4708@24ffb4c2>,
 :state->index
 {#<State automat.fsm.State@7268f197> 3,
  #<State automat.fsm.State@4ae497bc> 2,
  #<State automat.fsm.State@f3d34e57> 1,
  #<State automat.fsm.State@6dfaacee> 0}}
nil
user=> (-> (a/compile [1 (a/* [2 3]) 4]) meta :fsm)
#<fsm$dfa$reify__4708 automat.fsm$dfa$reify__4708@3f57f350>

@domkm Worth noting also that I'm on the latest release (0.1.3) not the latest snapshot or alpha. automat.compiler.core doesn't seem to exist in that version?

If you look in automat.fsm, there are methods that will operate on the object under the :fsm key and give you the transition table you're looking for. This is true both in 0.1.3 and the latest snapshot.

@ztellman Ah! Perfect. I can see, I think, that fsm contains functionality that would allow me to do this, and I hadn't noticed that the :fsm key is operable on by that ns.

That said, I've been trying to piece together how to do this based on the code there and I'm coming up short. If you'll permit me to be a little lazy, what I have in hand is the previous input (say, 2) and the proposed-input (say, 4). This seems natural at least to me as serializing the state of the fsm to the db or something wouldn't be very meaningful to a human (although I suppose you could do both).

How can I take 2 and 4 and determine that it's not valid? I'm assuming that generally speaking there's no way to go from input -> state given that one input might lead to many states so there'd be no way given the previous input to get to the current state to then determine if the proposed input is valid or not. I feel like I'm fundamentally missing something about how fsms work. :)

I did figure out this sort of thing but I don't think this gets me very far. In this instance I get the internal state for a given input, but I'm not sure then how to turn that around and get the permissible? functionality given that it doesn't appear to work to pass it back into advance:

user=> (clojure.pprint/pprint (reduce into {} (map (partial fsm/input->state (-> charnock meta :fsm)) (fsm/states (-> charnock meta :fsm)))))
{3 #<State automat.fsm.State@296045fd>,
 1 #<State automat.fsm.State@296045fd>,
 2 #<State automat.fsm.State@862473d0>,
 4 #<State automat.fsm.State@16e83f6c>}
nil
user=> (a/advance charnock (get (reduce into {} (map (partial fsm/input->state (-> charnock meta :fsm)) (fsm/states (-> charnock meta :fsm)))) 1) 2)

IllegalArgumentException could not process input 2  automat.core/advance (core.clj:396)

If you're able and willing to help despite my rambling, I'd greatly appreciate it!