Skip to content

Issues with finite_automaton architecture #25

Description

@bygu4

Current design of the finite_automaton module contains multiple cyclic imports, and that is due to a bigger problem: confusing architecture that makes the module hard to maintain. I think inheritance is the right way to design finite automata, but we need to refactor some methods and interfaces to restore a proper hierarchy. For example, to_deterministic abstract method is one of the causes of cyclic imports:

def to_deterministic(self):
""" Turns the automaton into a deterministic one"""
raise NotImplementedError

And similar situation with this method:
def remove_epsilon_transitions(self) -> "NondeterministicFiniteAutomaton":
""" Removes the epsilon transitions from the automaton
Returns
----------
dfa : :class:`~pyformlang.finite_automaton.\
NondeterministicFiniteAutomaton`
A non-deterministic finite automaton equivalent to the current \
nfa, with no epsilon transition
"""
from pyformlang.finite_automaton import NondeterministicFiniteAutomaton
nfa = NondeterministicFiniteAutomaton()
for state in self._start_state:
nfa.add_start_state(state)
for state in self._final_states:
nfa.add_final_state(state)
start_eclose = self.eclose_iterable(self._start_state)
for state in start_eclose:
nfa.add_start_state(state)
for state in self._states:
eclose = self.eclose(state)
for e_state in eclose:
if e_state in self._final_states:
nfa.add_final_state(state)
for symb in self._input_symbols:
for next_state in self._transition_function(e_state, symb):
nfa.add_transition(state, symb, next_state)
return nfa

I think the right way to handle this, is to make sure that inherited classes don't know anything about their descendants. To do this we can get rid of to_deterministic abstraction, and refactor it's implementations as class methods of the DFA class. And also do the same thing with remove_epsilon_transitions. It might look like this:

class DeterministicFiniteAutomaton(NondeterministicFiniteAutomaton):

    @classmethod
    def from_epsilon_nfa(cls, enfa: EpsilonNFA) \
            -> DeterministicFiniteAutomaton:
        # implementation for enfa

    @classmethod
    def from_nfa(cls, nfa: NondeterministicFiniteAutomaton): # ...

And similarly with remove_epsilon_transitions:

class NondeterministicFiniteAutomaton(EpsilonNFA):

    @classmethod
    def remove_epsilon_transitions(cls, enfa: EpsilonNFA) \
            -> NondeterministicFiniteAutomaton:
        #  implementation

This may require some refactoring to make sure we don't use any protected members, but it seems completely doable.

As an alternative, maybe we could try to somehow get rid of inheritance, or just leave a couple of shortcuts like in the current state of pyformlang, but neither of these seems like a good solution.

Also there there are cyclic imports between Regex and EpsilonNFA so there are more things we need to rework outside of finite_automaton module.

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions