Friday, July 15, 2016

Rule based system applied to frontend tasks

Production rule systems have been used to define and reason about behavior of some system. A good introductory material can be found in Drools  - rules engine from JBoss - documentation, chapter on Artificial intelligence.
I was wondering what could happen if such approach was applied to manage the behavior of user interface. As a basis I took redux async example, you can find out more about it at redux documentation on async stuff.
We'll make a simple idealized or data-oriented production system, there is an assumption that any triggered conditions should be executed: the consequent actions will update the agent's knowledge, removing or adding data to the working memory - in our case changing the store in terms of redux. The system stops processing when no rules can be executed anymore.
For instance, given some state A user through UI can change it to B, then planner applies rules till no more rules are true and final result is being rendered to the browser.
The full source code is available.
Lets consider a simple rule
export function selectDefaultSubreddit() {
    if (s.selectedSubreddit !== null)
        return;
    s.selectedSubreddit = 'reactjs';
    notify();
}
This reads: if no subreddit is selected then select 'reactjs'. notify() notifies planner about state change - a thing for optimization via dependency tracking system like tracker or similar.
Another more complex example
export function fetchSelectedSubreddit() {
    if (s.selectedSubreddit === null)
        return;
    var selectedSubreddit = s.postsBySubreddit[s.selectedSubreddit];
    if (selectedSubreddit !== undefined
            && (selectedSubreddit.isFetching || selectedSubreddit.lastUpdated)
            && !selectedSubreddit.didInvalidate)
        return;
    var reddit = s.selectedSubreddit;
    if (s.postsBySubreddit[reddit] === undefined) {
        s.postsBySubreddit[reddit] = {
            items: []
        };
    }
    s.postsBySubreddit[reddit].isFetching = true;
    s.postsBySubreddit[reddit].didInvalidate = false;
    fetch(`https://www.reddit.com/r/${reddit}.json`)
        .then(response => response.json())
        .then(json => {
            let posts = json.data.children.map(child => child.data);
            s.postsBySubreddit[reddit].items = posts;
            s.postsBySubreddit[reddit].isFetching = false;
            s.postsBySubreddit[reddit].lastUpdated = new Date().getTime();
            notify();
        });
    notify();
}
So this is all code needed to handle asynchronicity. Again it 1:1 translates from business rules: if subreddit is selected and is not being fetched or has not been fetched or invalidated then start fetching it, when response is received update state with it and that is all except do not forget to notify planner when state changes (single threadiness of JS is at handy here).
When no rules to apply planner calls render which in our case is
planner.onRender(function() {
    render(
        <App {...state} />,
        document.getElementById('root')
    );
});
To handle UI events one just changes state via same actions, for instance
...
import {selectSubreddit, invalidateReddit} from '../actions'

export default class App extends Component {
    ...
    handleChange(nextReddit) {
        selectSubreddit(nextReddit);
    }
    ...
}
where
export function selectSubreddit(reddit) {
    s.selectedSubreddit = reddit;
    notify();
}
As a result we have the same functionality as in redux example but a lot simpler. Of course it has its own drawbacks such as
  • on every state change whole component tree is re-rendered - needed some sort of dependency tracking and connect()
  • because no dependency tracking one needs to notify planner upon state change
  • planner could implement Rete algorithm to effeciently pick rules to consider
  • etc
But as an example it is usable to show slightly different approach for your consideration.
P.S. next step is to try out something like behavior trees to do the same (not to be confused with Behavior trees to represend requirements). Take a look at Spore game Behavior Tree Docs.