-
Notifications
You must be signed in to change notification settings - Fork 6
MULTITRANS #6
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
MULTITRANS #6
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -59,6 +59,7 @@ defmodule ExFSM do | |||||
| quote do | ||||||
| import ExFSM | ||||||
| @fsm %{} | ||||||
| @multitrans %{} | ||||||
| @bypasses %{} | ||||||
| @docs %{} | ||||||
| @to nil | ||||||
|
|
@@ -68,6 +69,7 @@ defmodule ExFSM do | |||||
| defmacro __before_compile__(_env) do | ||||||
| quote do | ||||||
| def fsm, do: @fsm | ||||||
| def event_multitrans, do: @multitrans | ||||||
| def event_bypasses, do: @bypasses | ||||||
| def docs, do: @docs | ||||||
| end | ||||||
|
|
@@ -107,6 +109,23 @@ defmodule ExFSM do | |||||
| def unquote(signature), do: unquote(body_block[:do]) | ||||||
| end | ||||||
| end | ||||||
|
|
||||||
| @doc """ | ||||||
| Define a function of type `multitransition` describing a transition and its | ||||||
| different states or not. The function name is the transition name, the mode (:excluded or :included) is the | ||||||
| first argument with a list of the states linked to this mode. A state object can be modified and is the second argument. | ||||||
| defmultitrans refund({:included, [:payed, :shipped, :partially_shipped], _}, order) do | ||||||
| {:next_state, :refunded, order} | ||||||
| end | ||||||
| """ | ||||||
| defmacro defmultitrans({event,_meta,[{_, _, [mode, states, _params]}| _rest]}=signature,body_block) do | ||||||
| quote do | ||||||
| @multitrans Map.put(@multitrans,{unquote(event), unquote(states), unquote(mode)},__MODULE__) | ||||||
| doc = Module.get_attribute(__MODULE__, :doc) | ||||||
| @docs Map.put(@docs,{:event_doc,unquote(event)},doc) | ||||||
| def unquote(signature), do: unquote(body_block[:do]) | ||||||
| end | ||||||
| end | ||||||
| end | ||||||
|
|
||||||
| defmodule ExFSM.Machine do | ||||||
|
|
@@ -179,6 +198,11 @@ defmodule ExFSM.Machine do | |||||
| def event_bypasses(state), do: | ||||||
| event_bypasses(State.handlers(state)) | ||||||
|
|
||||||
| def event_multitrans(handlers) when is_list(handlers), do: | ||||||
| (handlers |> Enum.map(&(&1.event_multitrans)) |> Enum.concat |> Enum.into(%{})) | ||||||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This can be compressed into
Suggested change
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We can do it for the |
||||||
| def event_multitrans(state), do: | ||||||
| event_multitrans(State.handlers(state)) | ||||||
|
|
||||||
| @doc "find the ExFSM Module from the list `handlers` implementing the event `action` from `state_name`" | ||||||
| @spec find_handler({state_name::atom,event_name::atom},[exfsm_module :: atom]) :: exfsm_module :: atom | ||||||
| def find_handler({state_name,action},handlers) when is_list(handlers) do | ||||||
|
|
@@ -187,6 +211,17 @@ defmodule ExFSM.Machine do | |||||
| _ -> nil | ||||||
| end | ||||||
| end | ||||||
|
|
||||||
| def find_multitrans({state_name,action},handlers) when is_list(handlers) do | ||||||
| case Enum.find(event_multitrans(handlers), fn | ||||||
| {{this_action, states, :included}, _} -> this_action == action and state_name in states | ||||||
| {{this_action, states, :excluded}, _} -> this_action == action and state_name not in states | ||||||
| end) do | ||||||
| {{_, states, mode},handler}-> {states, handler, mode} | ||||||
| _ -> nil | ||||||
| end | ||||||
| end | ||||||
|
|
||||||
| @doc "same as `find_handler/2` but using a 'meta' state implementing `ExFSM.Machine.State`" | ||||||
| def find_handler({state,action}), do: | ||||||
| find_handler({State.state_name(state),action},State.handlers(state)) | ||||||
|
|
@@ -195,6 +230,10 @@ defmodule ExFSM.Machine do | |||||
| event_bypasses(handlers_or_state)[action] | ||||||
| end | ||||||
|
|
||||||
| def find_multitrans({state, action}) do | ||||||
| find_multitrans({State.state_name(state),action},State.handlers(state)) | ||||||
| end | ||||||
|
|
||||||
| def infos(handlers,_action) when is_list(handlers), do: | ||||||
| (handlers |> Enum.map(&(&1.docs)) |> Enum.concat |> Enum.into(%{})) | ||||||
| def infos(state,action), do: | ||||||
|
|
@@ -214,10 +253,21 @@ defmodule ExFSM.Machine do | |||||
| @spec event(ExFSM.Machine.State.t,{event_name :: atom, event_params :: any}) :: meta_event_reply | ||||||
| def event(state,{action,params}) do | ||||||
| case find_handler({state,action}) do | ||||||
| nil -> | ||||||
| case find_bypass(state,action) do | ||||||
| nil-> {:error,:illegal_action} | ||||||
| handler-> case apply(handler,action,[params,state]) do | ||||||
| nil -> | ||||||
| case find_multitrans({state, action}) do | ||||||
| nil -> | ||||||
| case find_bypass(state, action) do | ||||||
| nil -> {:error,:illegal_action} | ||||||
| handler -> | ||||||
| case apply(handler,action,[params,state]) do | ||||||
| {:keep_state,state}->{:next_state,state} | ||||||
| {:next_state,state_name,state,timeout} -> {:next_state,State.set_state_name(state,state_name),timeout} | ||||||
| {:next_state,state_name,state} -> {:next_state,State.set_state_name(state,state_name)} | ||||||
| other -> other | ||||||
| end | ||||||
| end | ||||||
| {states, handler, mode} -> | ||||||
| case apply(handler,action,[{mode,states,params},state]) do | ||||||
| {:keep_state,state}->{:next_state,state} | ||||||
| {:next_state,state_name,state,timeout} -> {:next_state,State.set_state_name(state,state_name),timeout} | ||||||
| {:next_state,state_name,state} -> {:next_state,State.set_state_name(state,state_name)} | ||||||
|
Comment on lines
+305
to
308
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The problem is here. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The goal brought up beforehand should mean that we do not need a defmacro defmultitrans(
{event, _meta, [{states, _params} | _rest]} = signature,
body_block
) do
quote do
@fsm Map.merge(
@fsm,
Enum.map(
unquote(states),
fn state ->
{
{state, unquote(event)},
{__MODULE__, unquote(Enum.uniq(find_nextstates(body_block[:do])))}
}
end
)
|> Map.new()
)
[...]
def unquote(signature), do: unquote(body_block[:do])
@to nil
end
endYou're also more than welcome to add a new instance in the ExFSM's doctest to keep it updated. The defmodule Elixir.Door do
use ExFSM
defmultitrans open({[:closed, :opened], _}, s), do: {:next_state, :opened, s}
endI might try to setup github actions to run a simple There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To address my earlier misunderstanding regarding the fact that we'd still need a defmacro defmultitrans(
{event, _meta, [{states, _params} | _rest]} = signature,
body_block
) do
quote do
[...]
unquote(
for state <- states do
quote do
def unquote({state, meta, [{trans, params} | rest]}), do: unquote(body_block[:do])
end
end
)
@to nil
end
endWhat do you think?
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. IMO, what we want internally is a way to transpose But then the In the end I came up with: defmacro deftrans(signature, do: body) do
transition_ast(signature, body)
|> cleanup_to()
end
defmacro defmultitrans(signature, do: body) do
Enum.map(transpose(signature), &transition_ast(&1, body))
|> cleanup_to()
endwhere @doc false
# states to transitions
def transpose(signature) do
{transition, meta, params} = signature
[{states, input}, object] = params
Enum.map(states, fn state ->
{state, meta, [{transition, input}, object]}
end)
end
@doc false
# Define the function for the transition, as well as it's metadatas
def transition_ast(signature, body) do
{state, _meta, params} = signature
[{transition, _param}, _object] = params
quote do
@fsm Map.put(@fsm, {unquote(state), unquote(transition)}, {__MODULE__, @to || unquote(Enum.uniq(find_nextstates(body)))})
doc = Module.get_attribute(__MODULE__, :doc)
# I don't really understand why we don't directly use @doc
@docs Map.put(@docs, {:transition_doc, unquote(state), unquote(transition)}, doc)
def unquote(signature), do: unquote(body)
end
end
@doc false
# Remove the @to for the next transition definitions
def cleanup_to(ast) do
quote do
unquote(ast)
@to nil
end
endI really think we should wait another time to do the difference operation on the set of transitions. As stated, it is a source of implicit behaviour dependent on the global definition of the fsm (which may be more than the current module), which is really easy to forget when changing stuff.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really like your implementation that you describe here, I tried it but we have a problem here: the definition of the new deftrans has they are already existing is a problem. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @KBWilliamP actually, this is only a matter of warning:
Additional remarks:
whereas when using defmultitrans, you put:
Starting with the action, which can be misleading, as junior developper already tend to get mixed up on the syntax. Finally I would suggest keeping the original guards in the
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Sorry for the delay @KBWilliamP After talking a bit with @awetzel , it turns out that there might be a major API change some time this year for ExFSM. Taking that into account, the changes we were willing to make for the That means that we agreed that in order to get this QOL feature out and usable quickly enough, the easiest things to do is just to mute the compilation warning, using the quote option |
||||||
|
|
@@ -239,7 +289,13 @@ defmodule ExFSM.Machine do | |||||
| |> Enum.filter(fn {{from,_},_}->from==State.state_name(state) end) | ||||||
| |> Enum.map(fn {{_,action},_}->action end) | ||||||
| bypasses_actions = ExFSM.Machine.event_bypasses(state) |> Map.keys | ||||||
| Enum.uniq(fsm_actions ++ bypasses_actions) | ||||||
| multitrans_actions = ExFSM.Machine.event_multitrans(state) | ||||||
| |> Enum.filter(fn | ||||||
| {{_,from,:included},_}->State.state_name(state) in from | ||||||
| {{_,from,:excluded},_}->State.state_name(state) not in from | ||||||
| end) | ||||||
| |> Enum.map(fn {{action,_,_},_}->action end) | ||||||
| Enum.uniq(fsm_actions ++ bypasses_actions ++ multitrans_actions) | ||||||
| end | ||||||
|
|
||||||
| @spec action_available?(ExFSM.Machine.State.t,action_name :: atom) :: boolean | ||||||
|
|
||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a reason you don't handle the
toattribute ? like withdeftransThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just took the
defbypassas model because at the beginning it was more similar to thedefbypass.There isn't any particular reason.