1: %%\documentclass[11pt]{article}
2: \documentclass{llncs}
3:
4: %%\usepackage{paper}
5: \usepackage{times}
6: \usepackage{scriptcode}
7:
8: % for drafting purposes
9: %\usepackage{remarks}
10: %\usepackage[notref,notcite]{showkeys}
11:
12: \title{An Approach to the Implementation of Overlapping Rules in Standard ML}
13: \author{Riccardo Pucella}
14: \institute{Department of Computer Science\\Cornell University\\riccardo@cs.cornell.edu}
15: %%\author{Riccardo Pucella\\[.1in]
16: %%Department of Computer Science\\
17: %%Cornell University}
18: %%\date{\textbf{Draft of \today}}
19:
20:
21: \newcommand{\COMMENTOUT}[1]{}
22: \newcommand\kw[1]{\textbf{#1}}
23: \newcommand\expr[1]{\textit{#1}}
24:
25:
26: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
27:
28: \begin{document}
29: \maketitle
30:
31: \begin{abstract}
32: We describe an approach to programming rule-based systems in Standard
33: ML, with a focus on so-called overlapping rules, that is rules that
34: can still be active when other rules are fired. Such rules are useful
35: when implementing rule-based reactive systems, and to that effect we
36: show a simple implementation of Loyall's Active Behavior Trees, used
37: to control goal-directed agents in the Oz virtual environment. We
38: discuss an implementation of our framework using a reactive library
39: geared towards implementing those kind of systems.
40: \end{abstract}
41:
42:
43:
44: \section{Introduction}
45:
46: Rule-based systems\footnote{In this paper, we focus exclusively on
47: production rule systems. Related systems, such as those based on a
48: notion of term rewrite rule, have not been considered at this point.}
49: have had a long history in AI and powerful
50: implementations have been developed. The
51: most problematic aspect of this work
52: has always been that of integrating the rule-based approach to a
53: general-purpose language for application support. As an example along
54: those lines, in \cite{Crawford96}, Crawford \emph{et al} describe R++,
55: a rule-based extension to C++ completely integrated with the
56: object-oriented features of the language, implemented as a rewrite of
57: R++ into C++ code.
58:
59: \COMMENTOUT{
60: Our aim in this paper is somewhat similar, but along different
61: lines. We attempt to integrate rule-based programming with Standard ML
62: (SML) \cite{Milner97}, in such a way as to be able to use both
63: rule-based code and ordinary SML code. Our integration should be as
64: much a library as possible, that is it should use facilities of the
65: }
66:
67: One common use of rules, and indeed the primary motivation for this
68: work, is to help write rule-based reactive systems, reactive in the sense of having the
69: system react to changes in the environment. A change in the
70: environment should enable a certain number of appropriate rules that
71: can be fired to react to the environment change, possibly effecting
72: new changes to the environment that will enable other rules.
73:
74: \COMMENTOUT{With this
75: point of view in mind, it is not surprising that the approach we
76: investigate relies on various reactive programming approaches. More
77: precisely, we investigate the use of a general reactive library
78: introduced in \cite{Pucella98} and based on the Reactive Approach of
79: Boussinot \cite{Boussinot91,Boussinot96b}. }
80:
81: One aspect of rule-based reactive systems we focus on is that of
82: long-acting overlapping rules. In
83: most rule-based systems, a single rule is active at any given time:
84: when the system determines that a rule is enable, the rule is fired,
85: the environment is updated, and a new rule can be selected. This is
86: not so great is some of the rules are computationally intensive: when
87: such a rule is fired, it will take time to execute and if the system
88: relies on other rules to ensure say responsiveness of the interface,
89: the system will not respond to the user until the rule has finished
90: executing. In general one, may want rules that span multiple other
91: rules firing. For example, one may pre-fire a rule when certain
92: conditions are met, perhaps pre-computing some values, and get ready
93: for the ``real'' firing once the ``real'' conditions are in place. Or
94: one may want a rule that when fired will perform a given action at
95: every subsequent rule firing until maybe a condition occurs that stops
96: this behavior. It is of course possible to achieve these effects in
97: certain rule-based systems, by a process of chaining rules (at the end
98: of one rule, setting up the firing conditions of the next rule), along
99: with a suitable notion of concurrently fired rules.
100:
101: In this paper, we describe an approach to the integration of
102: rule-based programming in Standard ML (SML) \cite{Milner97}, with a
103: strong focus on overlapping rules to achieve the effects described
104: above. The resulting framework is suitable for the design of
105: domain-specific abstractions for various rule systems. In contrast with
106: other work, we do not worry about efficiency issues in this paper, but
107: rather concentrate on expressiveness and applicability of the
108: framework. We assume throughout the paper a basic knowledge of SML, as
109: described in various introductory material such as
110: \cite{Paulson96,Ullman98}.
111:
112: After reviewing the basic notions of rule-based programming in Section
113: \ref{s:review}, we discuss the framework in Sections
114: \ref{s:basic}--\ref{s:overlapping}. We give an application of the
115: flexibility of the framework in Section \ref{s:abts}, where we design
116: domain-specific abstractions for controlling a goal-directed
117: agent. Section \ref{s:impl} focuses on implementation details,
118: including a reference implementation in terms of an existing reactive
119: library for programming reactive systems in SML, introduced in
120: \cite{Pucella98}, and based on the reactive approach of Boussinot
121: \cite{Boussinot91,Boussinot96b}.
122:
123:
124: \section{OPS-style production rules}
125: \label{s:review}
126:
127: In this section, we establish the terminology and the model of rules
128: we are interested in, namely OPS-style production rules
129: \cite{Brownston85}. The \emph{production-system} model of
130: computation is a paradigm on the same footing as the procedural
131: paradigm, the functional paradigm or the object-oriented paradigm: it
132: is a view of what a computation ought to be to best achieve a given
133: goal.
134:
135: A \emph{production-system program} is an unordered collection of basic
136: units of computation called \emph{production rules} (henceforth simply
137: called rules). Each rule has a condition part and an action part. An
138: \emph{inference engine} is used to execute the rules: it determines
139: which rules are \emph{enabled} by checking which conditions are true,
140: and select rules to execute or \emph{fire} from the enable rules. A
141: rule is fired by executing its action part, which typically will have
142: a side effect of performing input and output or computing a value and
143: updating some date in memory. Thus rule-based systems fundamentally
144: based on the notion of side effects.
145:
146: Many mechanisms can be used to select which enabled rule to fire. In
147: the literature, the term \emph{conflict set} is often used to name
148: the set of rules which are enabled, reflecting the intuition that
149: somehow these rules can be conflicting, that is update the store in
150: different incompatible ways. To ensure such problems do not occur,
151: production systems will typically select a single rule to fire, by
152: methods involving various notions such as priority or probabilities,
153: along with notions such as the best matching of the conditions and so
154: on.
155:
156: In a rule-based system, control is \emph{data-driven}, that is the
157: data determines which part of the program will execute ---
158: furthermore, communication between different units is done solely
159: through the use of data. There is no concept of a subroutine call to
160: another rule, or anything of that sort. Rule-based systems allow a
161: cleaner separation of knowledge (in the form of rules) from the control
162: (encapsulated in the inference engine). This makes rule-based systems
163: well-suited to program expert systems for analysis problems, and for
164: programs for which the exact flow of control is not known. In this
165: paper, we will make the further refined point that with the
166: appropriate extensions, rule-based systems are well-suited for the
167: compositional development of reactive systems.
168:
169:
170: \section{A framework for rule-based programming}
171: \label{s:basic}
172:
173: We begin by considering a general framework for the handling of simple OPS-style
174: rules in SML, where actions are executed atomically and terminate
175: before a new rule can be fired. We discuss the framework abstractly,
176: that is in terms its interface.
177:
178: The first notion of importance is that of a set of rules, as an
179: abstract type with a single basic operation that creates an empty set
180: of rules. The reason for keeping the set of rules abstract is to
181: allow for different implementations, some possibly aimed at optimizing
182: the evaluation of the conditions.
183: \begin{code}
184: \kw{type} rule_set
185: \kw{val} newSet : unit -> rule_set
186: \end{code}
187:
188: A rule is simply defined as a pair of a condition and an action, as in
189: OPS. Since we want the condition to be dynamically
190: evaluated, it is implemented as a function (the standard way to delay
191: evaluation in an eager language such as SML). A
192: condition evaluates to a positive integer (a word), which we call the
193: \emph{fitness} of the condition, indicating the degree to which the
194: condition is satisfied. There is no \emph{a priori} semantics or range
195: associated with those, they are left to the discretion of the
196: programmer. The only restriction is that a fitness of zero is used to
197: indicate that a rule is not enabled. One can implement simple boolean conditions as values 0
198: and 1, if need be. \COMMENTOUT{A more involved implementation would push the
199: fitness into an abstract type with an associated ordering, which is
200: what we really require from words.} The only operation on rules is to
201: add them to rule sets. Note that because functions are first-class in
202: SML, rules become first-class as well. That is, they can be passed as
203: argument to functions, and returned from functions.
204: \begin{code}
205: \kw{type} rule = \{cond : unit -> word,
206: action : unit -> unit\}
207: \kw{val} addRule : rule * rule_set -> rule_set
208: \kw{val} mkSet : rule list -> rule_set
209: \end{code}
210:
211: The final ingredient of the system is the inference
212: engine, that selects which of the rules will be fired. At this point, the
213: issue of \emph{when} the rules should be fired must be
214: addressed. Many systems tie the firing of the rules, or at least the
215: evaluation of the conditions, to a change to variables that affect
216: the conditions. We choose a much more fundamental approach using an
217: explicit call that monitors the conditions, from which we can derive a
218: \emph{change-of-state} trigger. While inefficient, this approach has
219: the advantage of being general. The other issue this does
220: not address is that of conflict resolution, which rules to fire
221: of all those that are enabled. We provide a selection of conflict
222: resolution strategies. A function \expr{monitor} is used to select
223: rules to fire in a given set of rules. It takes as argument a conflict
224: resolution flag determining the resolution strategy to use:
225:
226: \begin{code}
227: \kw{datatype} conflict_res = AllBest
228: | RandBest
229: | AllDownTo \kw{of} word
230: | RandDownTo \kw{of} word
231: \kw{val} monitor : conflict_res -> rule_set -> unit
232: \end{code}
233: \expr{AllBest} fires all the rules that qualify as the best rule to
234: apply, sorted according to fitness. \expr{RandBest} randomly picks one
235: of the rules that qualify as the best rule. \expr{AllDownTo} and
236: \expr{RandDownTo} perform similarly, but consider all the rules whose
237: fitness is at least the given value. One can easily extend
238: the framework to allow for custom conflict resolution, which we do not
239: pursue in this paper for simplicity.
240:
241: Our notion of fitness is general. As we noted,
242: we can imagine a binary fitness (0-1) for boolean firing, but also a
243: fitness based on how close the conditions are to being completely
244: satisfied, or even so far as how many conditions are actually
245: satisfied (if we allow firing based on partially satisfied
246: conditions). An easy extension to the
247: framework would be to pass information from the condition to the
248: action. We can mimic this easily by using a reference cell which can
249: also be hidden in a closure of the rule, as follows:
250: \begin{code}
251: \kw{let}
252: \kw{val} r = ref 0
253: \kw{in}
254: \{cond = \textit{compute fitness, store something in r},
255: action = \textit{some action using the value in r}\}
256: \kw{end}
257: \end{code}
258:
259: In striking difference with other systems, rules are not persistent in
260: this framework: once a rule fires and executes, it is removed from the
261: rule set. To make a rule persistent, we can use the function
262: \expr{persistent}. This allows us to dispense
263: with a function to remove rules
264: from rule sets. Moreover, \expr{persistent} comes for
265: free given our extension for managing overlapping rules, as we will
266: see in the next section.
267: \begin{code}
268: \kw{val} persistent : rule -> rule
269: \end{code}
270:
271: As an example of how to use the framework, consider a simple
272: rule-based program to compute the greatest common divisor of two
273: integers, the classic Euclid's algorithm. The example is artificial
274: (it is easily implemented in SML without rules), but serves well to
275: illustrate the basics. A more complete example is presented in Section
276: \ref{s:abts}. The program can be expressed
277: as follows in Dijkstra's language of guarded commands:
278: \[ \kw{do}~ X > Y \rightarrow X := X-Y ~|~ Y > X \rightarrow Y := Y - X ~\kw{od}\]
279: The corresponding code in our framework is more verbose, but
280: essentially similar:
281: \begin{code}
282: \kw{fun} gcd (x,y) = \kw{let}
283: \kw{val} rx = ref x
284: \kw{val} ry = ref y
285: \kw{fun} r (r1,r2) = persistent
286: \{cond = \kw{fn} () => \kw{if} (!r1) > (!r2)
287: \kw{then} 1 \kw{else} 0,
288: action = \kw{fn} () => r1 := (!r1) - (!r2)\}
289: \kw{val} rs = mkSet [r (rx,ry),r (ry,rx)]
290: \kw{fun} loop () = \kw{if} ((!rx) = (!ry)) \kw{then} (!rx)
291: \kw{else} (monitor AllBest rs; loop ())
292: \kw{in}
293: loop ()
294: \kw{end}
295: \end{code}
296:
297: The above example is interesting because it shows how the fact that
298: rules are first-class in the framework allow for parametrized rules:
299: the rule \expr{r} in the above code is parametrized over the reference
300: cells containing the two arguments, parameterization which nicely
301: showcases the symmetry of the rules.
302:
303:
304: As a final remark, we note that the rules as we have presented them in
305: this section are simpler than they are in the actual framework. The rules
306: in the implemented framework contain an extra field generically called
307: \emph{data} whose type is a parameter to the structure implementing
308: the rules (in effect, the library is implemented as a functor). The
309: monitoring function takes as an extra argument a function to compute a
310: fitness both from the result of the evaluation of the condition and
311: the data (which for example can contain notions such as rule priority
312: and so on). Since describing this explicitly would require us to go
313: into the details of both the module system and the type system of SML, we punt
314: on these issues in this paper.
315:
316: \COMMENTOUT{
317: One advantage of our approach is that we can fairly easily derive new
318: rule representations with hard-wired behavior. For example, assume we
319: want to implement prioritized rules. We can create a new structure
320: \begin{code}
321: \kw{structure} PriorityRules :> \kw{sig}
322: \kw{type} rule_set
323: \kw{val} new_set
324: \kw{type} rule = \{fitness : unit -> word,
325: action : unit -> unit,
326: priority : unit -> word\}
327: \kw{val} addRule : rule_set * rule -> rule_set
328: \kw{val} monitor : rule_set -> rule_set
329: \kw{end} = \kw{struct}
330: \kw{structure} PR = RulesFn (\kw{type} rules_data = unit -> word)
331: \kw{type} rule_set = PR.rule_set
332: \kw{val} new_set = PR.new_set
333: \kw{type} rule = \{fitness : unit -> word,
334: action : unit -> unit,
335: priority : unit -> word\}
336: \kw{fun} add_rule rs \{fitness,action,priority\} = PR.add_rule rs \{fitness=fitness,action=action,data=priority\}
337: \kw{val} monitor = PR.monitor (\kw{fn} \{fitness,data\} => (fitness() * data ()),PR.RandBest)
338: \kw{end}
339: \end{code}
340: }
341:
342: \section{Managing overlapping rules}
343: \label{s:overlapping}
344:
345: The main point of this work was to introduce \emph{overlapping rules},
346: that is rules that can span multiple invocations and be performed in
347: parallel with other rules. From an interface point of view, the
348: framework only requires the addition of a single primitive, which we
349: call \expr{wait}, to add the desired functionality:
350: \begin{code}
351: \kw{val} wait : (unit -> word) option\footnote{a primitive SML type defined as \cd{\kw{datatype} 'a option = NONE | SOME \kw{of} 'a}.} -> unit
352: \end{code}
353:
354: The semantics of \expr{wait} is simple. Fundamentally,
355: \expr{wait} interrupts the action of the current rule, as if the rule
356: was finished executing, except that at the next time the monitoring
357: function is invoked to fire a rule, the rules that were interrupted are
358: allowed to resume while the new rule fires. Hence the term
359: \emph{overlapping}. In fact, the \expr{wait} primitive takes two
360: forms. The form \expr{wait (NONE)} behaves as an unconditional
361: interruption. Execution continues the next time the monitoring
362: function is invoked. The form \expr{wait (SOME (f))} with \expr{f} as
363: a condition (that is, a \expr{unit $\rightarrow$ word} function)
364: also interrupts, but only resumes the rule the next time the
365: monitoring function is invoked with the condition \expr{f} being
366: satisfied. In effect, \expr{wait (SOME (f))} interrupts the rule and
367: conceptually replaces it with a new rule containing the remainder of
368: the interrupted rule, with a condition \expr{f}.
369:
370: As we mentioned in the previous section, rules are not persistent:
371: once they are fired and execute, they are removed from the rule
372: set.We can implement the \expr{persistent} function using \expr{wait}:
373: \begin{code}
374: \kw{fun} persistent \{cond,action\} =
375: \{cond = cond,
376: action = \kw{let}
377: \kw{fun} loop () = (action ();
378: wait (SOME (cond));
379: loop ())
380: \kw{in}
381: loop
382: \kw{end}\}
383: \end{code}
384: This nicely shows the power of first-class rules.
385:
386:
387:
388:
389: \section{An application: goal-directed agent control}
390: \label{s:abts}
391:
392: We describe in this section one of the motivating applications for the
393: development of the framework in the first place, that of controlling
394: goal-directed agents. The architecture we have in mind is inspired by
395: Hap, a reactive, goal-driven architecture for controlling agents in
396: the Oz virtual environment \cite{Loyall91}. The main structure in Hap
397: is an \emph{active behavior tree} (ABT), which represents all the
398: goals and behaviors an agent is pursuing at any given point
399: \cite{Loyall97}. An agent chooses the next step to perform by selecting
400: one of the leaves of its ABT. Three types of actions can be performed
401: depending on the type of the node selected.
402: \begin{enumerate}
403: \item \textbf{Primitive physical action}: an action sent to
404: the action server, which can either succeed or fail depending on the
405: state of the world.
406: \item \textbf{Primitive mental action}: an action that simply performs
407: a computation (possibly with side effects) and which always succeeds.
408: \item \textbf{Subgoal}: an action corresponding to a new subgoal; an
409: appropriate behavior is selected that matches that subgoal, and the
410: ABT is expanded by adding the steps specified by the behavior to the
411: tree as children of the subgoal selected.
412: \end{enumerate}
413:
414: (For our purposes, we drop the distinction between mental and physical
415: actions, since we can model mental actions as physical actions that
416: always succeed). Programming an agent reduces to programming behaviors
417: for various goals. Goals have no intrinsic meaning, they are simply
418: names on top of which the programmer can
419: attach any semantics she desires. Behaviors are defined by specifying
420: to which goal they apply, a pre-condition for the application of that
421: behavior (simply a predicate over the state of the world), and the
422: steps that the behavior prescribes (physical actions, mental actions,
423: subgoals). At this point, many details enter the description, to
424: provide control over managing goals and behaviors. There are three kind
425: of behaviors:
426: \begin{enumerate}
427: \item \textbf{Sequential}: the steps are performed in order, and
428: failure of any step signifies the failure of the corresponding goal to
429: which the behavior is attached; success of the last step signifies
430: success of the corresponding goal.
431: \item \textbf{Concurrent}: the steps are performed in any order, but
432: again failure of any step signifies the failure of the corresponding
433: goal.
434: \item \textbf{Collection}: the steps are performed in any order, but
435: success or failure of the steps are irrelevant. When all steps have
436: succeeded or failed, the corresponding goal succeeds.
437: \end{enumerate}
438: Subgoals steps in behaviors can moreover be annotated as \emph{persistent},
439: that is when they succeed or fail, they are not removed, but rather
440: persist as a continuing goal. Typically, top-level goals are
441: persistent. Conversely, subgoals can be annotated with a \emph{success
442: test}, a predicate over the state of the world, which gets tested
443: every time the ABT is activated. If the success test of a subgoal
444: is true, the subgoal automatically succeeds.
445:
446: Choosing a step to perform is by default done at random over all the
447: applicable steps in an ABT, that is all the leaves that can be either
448: executed right away or subgoals that can be expanded because a
449: behavior applies (no behavior may apply because either none has been
450: defined or no pre-condition is satisfied). Similarly, choosing a
451: behavior to perform once a subgoal step has been chosen is by default
452: done at random over all applicable behaviors. One can modify this
453: default by assigning \emph{priorities} to various subgoals.
454:
455: All of this is meant to evoke the kind of structure we would like to
456: express in our framework. Since Hap revolves around the notion
457: of goals, we abstractly provide a notion of goal to the framework of
458: the previous sections, where goals are for simplicity represented as
459: strings.
460: \begin{code}
461: \kw{datatype} goal_status = Success | Failure
462: | Active | Available
463: | NoSuch
464: \kw{val} goalSet : string -> unit
465: \kw{val} goalSucceed : string -> unit
466: \kw{val} goalFail : string -> unit
467: \kw{val} goalStatus : string -> goal_status
468: \kw{val} goalClear : string -> unit
469: \end{code}
470: where \expr{goalSet} enables the given goal, such that it is
471: to be pursued by the agent (it becomes available). The functions
472: \expr{goalSucceed} and \expr{goalFail} are used to record that a goal
473: has succeeded or failed. The function \expr{goalStatus} returns the
474: status of the given goal. The status of a goal is either
475: \expr{Success} or \expr{Failure} if the goal has been recorded as
476: such, or \expr{Active} if a behavior is actively pursuing the goal,
477: but is not done with it yet. A status of \expr{Available} indicates
478: that the goal is enabled, but that no behavior is pursuing it, while a
479: status of \expr{NoSuch} indicates that no such goal exists. The
480: function \expr{goalClear} removes a goal from the active
481: list of goals. We define the boolean-valued helper functions
482: \expr{isAvailable} and \expr{isDone} to check the status
483: of a goal to be respectively \expr{Available} or
484: \expr{Success}/\expr{Failure}.
485:
486: We interpret an ABT behavior as a rule, triggered both by the
487: pre-condition of the behavior (if present) and the apparition as
488: \expr{Available} of the goal the behavior is meant to pursue. We do
489: not worry about either sequential, collection or concurrent
490: annotations, choosing rather to let the programmer manage the steps of
491: the behavior explicitly. Patterns quickly emerge. For instance, a behavior for goal
492: \expr{g} triggered by a condition \expr{c} and sequentially performing
493: subgoal \expr{g1}, action \expr{a} and subgoal \expr{g2} can be
494: interpreted as a rule:
495: \begin{code}
496: \kw{val} beh1 =
497: \{cond = \kw{fn} () => \kw{if} isAvailable (\expr{g}) \kw{andalso} \expr{c}
498: \kw{then} 1 \kw{else} 0,
499: action = \kw{fn} () =>
500: (goalSet (\expr{g1});
501: wait (SOME (\kw{fn} () => isDone (\expr{g1})));
502: \kw{case} (goalStatus (\expr{g1}))
503: \kw{of} Success => (goalClear (\expr{g1});
504: \expr{a};
505: wait (NONE);
506: goalSet (\expr{g2});
507: wait (SOME (\kw{fn} () => isDone(\expr{g2})));
508: \kw{case} (goalStatus (\expr{g2}))
509: \kw{of} Success => (goalClear (\expr{g2});
510: goalSucceed (\expr{g}))
511: | _ => (goalClear (\expr{g2});
512: goalFail (\expr{g})))
513: | _ => (goalClear (\expr{g1});
514: goalFail (\expr{g})))\}
515: \end{code}
516:
517: Similarly, the previous behavior can be implemented concurrently by
518: setting all the goals at once and waiting for all the goals to be done.
519: \begin{code}
520: \kw{val} beh2 =
521: \{cond = \kw{fn} () => \kw{if} isAvailable (\expr{g}) \kw{andalso} \expr{c}
522: \kw{then} 1 \kw{else} 0,
523: action = \kw{fn} () =>
524: (goalSet (\expr{g1});
525: goalSet (\expr{g2});
526: \expr{a};
527: wait (SOME (\kw{fn} () => isDone (\expr{g1}) \kw{andalso} isDone (\expr{g2})));
528: \kw{case} (goalStatus (\expr{g1}),goalStatus (\expr{g2}))
529: \kw{of} (Success,Success) => (goalClear (\expr{g1});
530: goalClear (\expr{g2});
531: goalSucceed (\expr{g}))
532: | (_,_) => (goalClear (\expr{g1});
533: goalClear (\expr{g2});
534: goalFail (\expr{g})))\}
535: \end{code}
536:
537: By virtue of the \expr{andalso} in the first waiting condition of
538: \expr{beh2}, the conditions must all be true for the system to proceed
539: at that point. Although it does sequentializes the testing of the
540: conditions, this is not an issue given our current framework since
541: the rule is resumed when all the conditions are satisfied. It may
542: however become an issue if we attempt to optimize the satisfaction of
543: the conditions. Persistence of goals can be implemented by
544: clearing them and setting them again, while goal success tests can be
545: wrapped inside the \expr{wait} condition for that goal.
546:
547: One difficulty of this approach, immediately noticeable from the above
548: code, is
549: that it is very error-prone, even if it is much more
550: flexible than ABTs. In effect, we have to implement the handling
551: of the goals explicitly, for every single behavior. However, the
552: flexibility of
553: first-class rules and first-class functions allows us to easily
554: generate such rules from a declarative description of the intended
555: behavior. Consider a type \expr{behavior} that describes a behavior
556: declaratively:
557: \begin{code}
558: \kw{type} behavior = \{goal : string,
559: precond : (unit -> bool) option,
560: kind : behavior_kind,
561: steps : behavior_step list\}
562:
563: \kw{datatype} behavior_kind = Sequential | Concurrent
564: \kw{datatype} behavior_step = Subgoal \kw{of} string
565: | Action \kw{of} unit -> bool
566: \end{code}
567: (For simplicity, we drop the collective kind of behavior, its handling
568: similar enough to the concurrent one to not cause a problem). We can
569: describe the previous two behaviors \expr{beh1} and \expr{beh2} as
570: follow (this time using behavior parameterization!):
571: \begin{code}
572: \kw{fun} beh (k) = \{goal = \expr{g},
573: precond = SOME (\kw{fn} () => \expr{c}),
574: kind = k,
575: steps = [Subgoal (\expr{g1}),
576: Action (\kw{fn} () => \expr{a}),
577: Subgoal (\expr{g2})]\}
578: \kw{val} beh1 = beh (Sequential)
579: \kw{val} beh2 = beh (Concurrent)
580: \end{code}
581:
582: We can then interpret such descriptions in our framework, by a
583: function \expr{behaviorRule}, which takes a description of type
584: \expr{behavior} and returns a rule of type \expr{rule}. The
585: implementation of \expr{behaviorRule} is simply a question of writing
586: a rule whose action is an interpreter for lists of
587: \expr{behavior\_step}. For the truly interested, the code for
588: \expr{behaviorRule} is given in Figure \ref{f:behaviorrule}.
589:
590: \begin{figure}
591: \hrule
592: \medskip
593: \begin{code}
594: \kw{fun} behaviorRule \{goal,precond,kind,steps\} = \kw{let}
595: \kw{fun} split [] = ([],[])
596: | split (x::xs) = \kw{let}
597: \kw{val} (sg,acts) = split (xs)
598: \kw{in}
599: \kw{case} x
600: \kw{of} Subgoal (g) => (g::sg,acts)
601: | Action (a) => (sg,a::acts)
602: \kw{end}
603: \kw{fun} cond () = \kw{if} isAvailable (goal) \kw{andalso}
604: (\kw{case} precond \kw{of} NONE => true
605: | SOME (pc) => pc ())
606: \kw{then} 1 \kw{else} 0
607: \kw{in}
608: \kw{case} kind
609: \kw{of} Sequential => \kw{let}
610: \kw{fun} perform_steps [] = goalSucceed (goal)
611: | perform_steps (Action (a)::r) =
612: (a ();
613: wait (NONE);
614: perform_steps (r))
615: | perform_steps (Subgoal (g)::r) =
616: (goalSet (g);
617: wait (SOME (\kw{fn} () => isDone (g)));
618: \kw{case} (goalStatus (g))
619: \kw{of} Success => (goalClear (g);
620: perform_steps (r))
621: | _ => (goalClear (g);
622: goalFail (goal)))
623: \kw{in}
624: \{cond = cond,
625: action = \kw{fn} () => perform_steps (steps)\}
626: \kw{end}
627: | Concurrent =>
628: \{cond = cond,
629: action = \kw{fn} () => \kw{let}
630: \kw{val} (subgoals,actions) = split (steps)
631: \kw{in}
632: app goalSet subgoals;
633: app (\kw{fn} a => a ()) actions;
634: wait (SOME (\kw{fn} () => List.all (\kw{fn} x => x)
635: (map isDone subgoals)));
636: \kw{if} (List.all (\kw{fn} g => \kw{case} (goalStatus (g))
637: \kw{of} Success => true
638: | _ => false)
639: subgoals)
640: \kw{then} (app goalClear subgoals;
641: goalSucceed (goal))
642: \kw{else} (app goalClear subgoals;
643: goalFail (goal))
644: \kw{end}\}
645: \kw{end}
646: \end{code}
647: \hrule
648: \caption{Code for \expr{behaviorRule}}
649: \label{f:behaviorrule}
650: \end{figure}
651:
652:
653: \COMMENTOUT{
654: To help concretize this description, consider the following simplified
655: example. We model a baby with two high top-level goals,
656: \emph{enjoy-self} and \emph{not-hungry}, with \emph{not-hungry} having
657: higher priority. These top-levels goals are setup by an initial
658: behavior called \emph{setup} which installs them as a collection of
659: persistent goals. We assume a variable \emph{hunger} which is
660: increased at regular intervals to indicate that the baby is getting
661: hungry. Note that \emph{not-hungry} has a success test that
662: basically makes it succeed when hunger is 0, which should happen when
663: the baby eats.
664:
665: \begin{centercode}
666: val setup = Beh \{name="B-setup",
667: goal="main",
668: pre_condition=NONE,
669: kind=Coll,
670: steps = [(Subgoal \{priority=10,
671: importance=2,
672: name="enjoy-self",
673: success_test = NONE\},
674: true,false,false),
675: (Subgoal \{priority=100,
676: importance=10,
677: name="not-hungry",
678: success_test = SOME (fn () => (!hunger)=0)\},
679: true,false,false)]\}
680: \end{centercode}
681:
682: We define a single behavior for the \emph{enjoy-self} goal, namely to
683: smile contently. This is straightforward enough: the behavior has a
684: single step, to perform a mental action of broadcasting that the baby
685: smiles to everyone (this could also have been a physical action that
686: succeeds).
687:
688: \begin{centercode}
689: val smile = Beh \{name="B-smile",
690: goal="enjoy-self",
691: pre_condition=NONE,
692: kind = Seq,
693: steps = [(Mental (fn () => \textit{broadcast: baby smiles}),
694: false,false,false)]\}
695: \end{centercode}
696:
697: Handling hunger is slightly more complicated, but not much more. We
698: have a single behavior
699:
700: \begin{centercode}
701: val eat = Beh \{name="B-eat",
702: goal="not-hungry",
703: pre_condition=NONE,
704: kind=Seq,
705: steps = [(Subgoal \{priority=100,
706: importance=0,
707: name="unsatisfiable",
708: success_test = SOME (fn () => \textit{hunger > threshold})\},
709: false,false,false),
710: (Physical (fn () => (if \textit{bottle near baby}
711: then \textit{baby drinks, hunger=0, succeed}
712: else \textit{baby cries, fails})),
713: false,true,false)]\}
714: \end{centercode}
715:
716: Finally, we can create the ABT for the baby and the control loop to
717: drive the actions:
718:
719: \begin{centercode}
720: \kw{let}
721: \kw{val} baby_abt = mk_abt \{behaviors=[setup,smile,eat],
722: goal="main"\}
723: \kw{fun} loop () = (\textit{wait 2 seconds};
724: execute (baby_abt);
725: \textit{increment hunger};
726: loop ())
727: \kw{in}
728: \textit{hunger = 0};
729: loop ()
730: \kw{end}
731: \end{centercode}
732: }
733:
734:
735:
736: \section{Implementation}
737: \label{s:impl}
738:
739: The framework we have described has been implemented for the Standard
740: ML of New Jersey compiler \cite{Appel91} using the reactive library
741: described in \cite{Pucella98}. One advantage of this approach is that
742: the semantics of the system is easily derivable from the one in
743: \cite{Pucella98}. Before discussing the implementation, let us give an
744: overview of the reactive library.
745:
746: The library defines a type \expr{rexp} of reactive expressions, which
747: are expressions that define control points. A reactive expression is
748: created through a function \expr{rexp} that expects a \expr{unit
749: $\rightarrow$ unit} function as argument. The argument function calls
750: the function \expr{stop} to define a control point. The function \expr{react}
751: is used to take a reactive expression and to evaluate the code starting
752: from the last control point reached until the next control point is
753: reached. This is called \emph{activating} a reactive
754: expression. When a reactive expressions evaluates to a value without
755: reaching a control point, it is said to \emph{terminate}. Interesting
756: combinators can be defined to take reactive
757: expressions and combining them. The most important of such combinators
758: is the \expr{merge} combinator, which takes two reactive expression
759: $e_1$ and $e_2$ and creates a new reactive expression $e$ that behaves
760: as follows: when $e$ is activated, $e_1$ and $e_2$ are activated,
761: one after the other. In effect, this interleaves the execution of
762: $e_1$ and $e_2$. The combinator \expr{loop} takes a reactive
763: expressions $e_1$ and creates a new reactive expression $e$ that
764: behaves as follows: when $e$ is activated, $e_1$ is activated. If
765: $e_1$ terminates, it is reset (the reactive expression is
766: re-instanciated) and activated again. The reactive expression
767: \expr{nothing} simply terminates immediately.
768:
769: A more fine-grained notion of control point is also
770: available. A reactive expression can call \expr{suspend} to suspend
771: its current execution. A \expr{suspend} acts as a \expr{stop}, except
772: that a special combinator \expr{close} is available. Given a reactive
773: expression $e_1$, \expr{close} returns a new reactive expression which
774: behaves as follows: when $e$ is activated, it repeatedly activates
775: $e_1$ until all its reactive subexpressions have reached
776: {stop}-defined control points. This allows finer control over the
777: order of execution of the reactive subexpressions, an example of which
778: we will see in this section.
779:
780: It is clear, given this description, how the library may be useful to
781: implement the details of overlapping rules. For brevity, we assume
782: the reactive library has been
783: bound to a structure \expr{R}. The implementation of the framework is
784: rather simple, although it is complicated by technical
785: details and some mismatches with the underlying reactive library. We
786: define a rule set as a reactive expression, a \emph{merge} of all the
787: relevant rules.
788: \begin{code}
789: \kw{type} rule_set = R.rexp
790: \kw{fun} newSet () = R.nothing
791: \end{code}
792:
793: A rule is defined as before, while adding a rule to a set of rules
794: consists of merging the reactive expression corresponding to the rule
795: in the merge of the rule set.
796: \begin{code}
797: \kw{type} rule = \{cond : unit -> word,
798: action : unit -> unit\}
799:
800: \kw{fun} addRule rs \{cond,action\} = \kw{let}
801: \kw{val} r = R.exp (\kw{fn} () => (condition (cond);
802: action ()))
803: \kw{in}
804: R.merge (rs,r)
805: \kw{end}
806:
807: \kw{val} mkSet = foldr addRule (newSet ())
808: \end{code}
809:
810: The function \expr{condition} terminates immediately if monitoring
811: determines that it should be fired (according to the fitness of the
812: condition), otherwise it stops to wait for another instant
813: where the condition is deemed fit. To bypass a limitation of the current reactive library,
814: which is more geared towards locally determining whether a given
815: reactive expression is allowed to continue rather than being selected
816: through a global check, we introduce a global variable to hold a list
817: of all computed fitnesses and allow the system to select the
818: ones that will execute \footnote{This does make the library non-reentrant. This could
819: be corrected by an appropriate change to the reactive library
820: (implementing reaction-specific data, for instance), or by including a
821: notion of execution context to bind the use of the variable to a given
822: context.}.
823: \begin{code}
824: \kw{val} globalFitnesses = ref ([]) : (unit ref * word) list ref
825: \end{code}
826:
827: We uniquely tag each condition being computed using a value of type
828: \expr{unit ref}, a trick commonly used in SML to get unique
829: identifiers that can be quickly compared for identity. When the
830: function \expr{condition} is encountered, the current fitness is
831: computed and stored along with a unique identifier for the
832: condition. The reactive expression is then \emph{suspended} (not
833: stopped). This gives the other reactive expressions running \emph{in
834: parallel} a chance to evaluate their conditions. Upon resumption of the
835: suspension, each condition checks if it is allowed to continue by
836: seeing if it is listed in a list of \emph{allowed-to-continue}
837: conditions, stored in the previous \expr{globalFitnesses} variable.
838: \begin{code}
839: \kw{fun} condition (f) = \kw{let}
840: \kw{val} r = ref ()
841: \kw{fun} loop () = (globalFitnesses := (r,f)::(!globalFitnesses)
842: suspend ();
843: \kw{if} (List.exists (\kw{fn} (r',_) => r=r') (!globalFitnesses))
844: \kw{then} ()
845: \kw{else} (stop (); loop ()))
846: \kw{in}
847: loop ()
848: \kw{end}
849: \end{code}
850:
851: The function \expr{wait} that implements an
852: interruption of the execution of the rule is simple:
853: \begin{code}
854: \kw{fun} wait (NONE) = (stop (); suspend ())
855: | wait (SOME (f)) = (stop (); condition (f))
856: \end{code}
857: (The suspend in \expr{wait (NONE)} is a technicality, needed to
858: prevent the firing of stopped rules with no conditions until all the
859: conditions of the other rules have been checked).
860:
861: The core of the work is done in \expr{monitor}, which is in charge of activating the
862: reactive expression corresponding to the rule set, gather the results,
863: compute which conditions are satisfied, and resume the suspensions.
864: \begin{code}
865: \kw{fun} monitor c rs = \kw{let}
866: \kw{val} clearVar = R.loop (R.rexp (\kw{fn} () => (globalFitnesses := [];
867: stop ())))
868: \kw{val} r = R.loop (R.rexp (\kw{fn} () => (computeEnabled (c);
869: stop ())))
870: \kw{in}
871: R.react (R.close (R.merge (clearVar,R.merge (rs,r))))
872: \kw{end}
873: \end{code}
874:
875: The above code encompasses the control flow of the monitoring process,
876: and relies heavily on the fact that merges are deterministic. A
877: non-deterministic implementation could play with \expr{suspend} calls
878: to achieve the right order of execution: we need to make sure at every
879: instant that \expr{clearVar} is executed first, and
880: \expr{computeEnabled} is activated after all suspensions.
881: The actual rule selection is performed by \expr{computeEnabled}:
882: \begin{code}
883: \kw{fun} computeEnabled (AllBest) = \kw{let}
884: \kw{fun} max (curr,[]) = curr
885: | max (curr,(_,x)::xs) = \kw{if} x>curr \kw{then} max (x,xs) \kw{else} max (curr,xs)
886: \kw{val} bestVal = max (1,!globalFitnesses)
887: \kw{val} lst = List.filter (\kw{fn} (_,a) => a=bestVal) (!globalFitnesses)
888: \kw{in}
889: globalFitnesses := lst
890: \kw{end}
891: | computeEnabled (RandBest) = \textit{(as AllBest, but return random element)}
892: | computeEnabled (AllDownTo (v)) = \kw{let}
893: \kw{val} lst = List.filter (\kw{fn} (_,a) => (a>=v)) (!globalFitnesses)
894: \kw{in}
895: globalFitnesses := lst
896: \kw{end}
897: | computeEnable (RandDownTo (v)) = \textit{(as AllDownTo, but return random element)}
898: \end{code}
899:
900:
901:
902: \section{Conclusion}
903:
904: So what have we done? We have developed a general framework for
905: rule-based programming in SML, flexible enough to handle standard
906: OPS-style rules, as well as overlapping rules. The fact that rules are
907: first-class in the framework gave us enough flexibility to
908: express Loyall's active behavior trees through an interpreter of
909: declarative behaviors.
910:
911: We have not worried about the efficiency of the framework. Some rather
912: standard optimizations as found in modern rule-based systems can
913: easily be applied, although optimizations of the conditions may
914: require them to be lifted into a more structured type, such as for
915: example
916: \begin{code}
917: \kw{datatype} condition = Basic \kw{of} unit -> word
918: | And \kw{of} condition list
919: | Or \kw{of} condition list
920: | Not \kw{of} condition
921: | ...
922: \end{code}
923: in order to allow for such things as caching of condition evaluations
924: and so on. On another note, evaluating a condition is only required
925: if the references the condition refers to have been changed, so an
926: optimized framework should maintain a list of the references used by
927: conditions and record changes accordingly.
928:
929: The tradeoff between generality and conciseness is not new. If we are
930: willing to restrict flexibility, we can design an appropriate surface language
931: which we can translate into this framework for execution. This is what
932: we did for the implementation of behaviors in Section
933: \ref{s:abts}. This makes our framework a target language for the
934: interpretation of domain-specific rule-based languages. In a similar
935: way, the reactive library of \cite{Pucella98} was designed as a target
936: language for the interpretation of domain-specific reactive
937: languages, which is the way it is being used in this paper.
938:
939: Using the SugarCubes framework described in \cite{Boussinot98}, we could
940: move most of the framework to Java, but SML has distinct advantages,
941: at least at first brush: we have seen how first-class rules and
942: first-class functions helped design suitable domain-specific
943: abstractions; the module system, although not used in this paper,
944: plays a crucial role in the generalization of the framework to general
945: rules that can carry arbitrary data (not only conditions and
946: actions). The whole approach is also helped by the fact that SML
947: already implements state-based programming through the use of explicit
948: references. It will be interesting to see how much of this can be
949: carried over to Java.
950:
951: This frameworks, one of the first implemented using the library in
952: \cite{Pucella98}, also points to some conclusions about the reactive
953: approach. If the order in which parallel reactive expressions are
954: activated is important, then we need to be careful, appropriately
955: using \expr{suspend} calls to get the order right; this makes the
956: resulting system brittle and the code hard to see correct. A better
957: approach may be to allow one to explicitly control the order of
958: execution of the branches of a merge. On a related note, the reactive
959: library is geared towards determining reactive expression activation
960: locally, while we have encountered in this paper a reasonable instance
961: where the activation decision is taken on a global level. It would be
962: interesting to find an extension of the reactive library to do that
963: cleanly, without resorting to a list of unique identifiers indicating
964: which expression is allowed to resume.
965:
966: \COMMENTOUT{
967: \textbf{Future work.} Eventually, comparison with CML \cite{Reppy99},
968: in the style of \cite{Boussinot00}. Ensure actual reactivity.
969: }
970:
971: \bibliographystyle{plain}
972: \bibliography{main}
973:
974: \end{document}
975:
976: