/SitCalc

A generic framework for passing round "state" in Logtalk applications based on Situation Calculus

Primary LanguageLogtalkApache License 2.0Apache-2.0

SitCalc

SitCalc is a framework for managing state in an application without mutation based on situation calculus.

Workflow Status Code Coverage Report

Dependencies

SitCalc depends upon Situations. The loader assumes it can be accessed as situations(loader).

Usage

In this library a situation is like a list of actions, but the syntax is a little more verbose. The more recent actions come first:

do(get_married, do(meet_spouse, do(..., do(learn_to_walk, do(be_born, s0))...)))

Where s0 denotes the situation in which nothing has happened yet. This list is what is passed around your application.

To change this list, you should only do so through actions, which extend the action prototype:

:- object(drop(_Item_),
    extends(action)).

    poss(Situation) :-
        holding(_Item_)::holds(Situation).

:- end_object.

This describes a drop action that is only possible when holding the item. All actions must have possible conditions, even if they're always possible by defining the predicate poss/1 as poss(_).

An action is done like so:

?- Sit = do(pick_up(ball), s0), drop(ball)::do(Sit, NextSit).

The values that change in the application are called fluents (because this is situation calculus). We need to define in what situations they hold with what values. Note, in Logtalk the objects are identified by their functors.

:- object(holding(_Item_),
    extends(fluent)).

	% Initial situation:
	holds(s0) :- _Item_ == pen.

	% Actions change what's held
	holds(do(A, _)) :-
	    A = pick_up(_Item_).

	% Or we were already holding it and we're not dropping it now
	holds(do(A, S)) :-
	   holds(S),
	   A \= drop(_Item_). % not holding it if we're dropping it

:- end_object.

Declaring these takes a little getting used to, but quickly becomes quite repetitive. We can query them like so:

?- holding(What)::holds(s0).
What = pen.

?- holding(What)::holds(do(pick_up(ball), s0)).
What = ball ;
What = pen.

?- holding(What)::holds(do(drop(pen), do(pick_up(ball), s0))).
What = ball.

Finally, the situation object has a couple of utility predicates:

?- sitcalc::poss(A, s0).
A = pick_up(ball).

?- sitcalc::prior(do(pick_up(ball), do(drop(pen), s0)), P).
P = do(drop(pen), s0) ;
P = s0.

?- sitcalc::holds(holding(pen) and holding(ball), do(pick_up(ball), s0)).
true.

The holds/2 predicate on the situation object is quite different, it also contains query composition operators: and, or, not, implies, and equivalentTo. These are transformed via the revised Lloyd Topper transformations from Reiter, and then the individual fluents or other goals are called. This is useful when defining poss/1 for actions:

:- object(boil_kettle,
    extends(action)).

	poss(S) :-
	    sitcalc::holds(power(kettle, on) and not kettle_water(empty), S).

:- end_object.

As we're in Logtalk, not has the same meaning as \+.

Finally, to persist a situation between sessions, persist the list of actions and reload. Long sequences of actions will become slow to query, choose the applications you use this for wisely. To aid with this, we apply tabling to the holds predicate if the Logtalk backend supports this feature. Should you require a state-handling solution that clobbers fluents, you may find STRIPState more useful.