# HG changeset patch # User Steve Losh # Date 1508698480 14400 # Node ID f5e7f6bd113fa8d8edafbcf4795b2c06dee56144 # Parent e79382c9ffcca8445880a50729cef223faa39838 temperance: Update site. diff -r e79382c9ffcc -r f5e7f6bd113f temperance/usage/index.html --- a/temperance/usage/index.html Tue Apr 25 15:14:51 2017 +0000 +++ b/temperance/usage/index.html Sun Oct 22 14:54:40 2017 -0400 @@ -12,28 +12,332 @@

Temperance

-

Usage

+

Usage

This guide assumes you know the basics of logic programming. If you've never +done any logic programming before, you should probably start with an +introduction to that before trying to read this.

+

Hello, Temperance

+

Temperance is a pretty big system and we'll need to look at a few different +pieces one by one before you'll have a good picture of how it all fits +together, but let's at least get something on the screen. First we'll set up +a logic database with some basic facts:

+
(in-package :cl-user)
+(use-package :temperance)
+
+(push-logic-frame t)
+
+(facts t
+  (likes steve cats)
+  (likes steve beer)
+  (likes kim cats)
+  (likes kim tea)
+  (likes sally cats)
+  (likes sally beer))
+
+(rule t (likes sally ?who)
+  (likes ?who cats))
+
+(finalize-logic-frame t)
+
+ + +

Ignore the stuff about logic frames for now. We've added some facts saying:

+
    +
  • Steve and Sally like cats and beer
  • +
  • Kim likes cats and tea
  • +
  • Sally also likes anybody who likes cats
  • +
+

And now we can ask some questions:

+
(query-all t (likes ?who beer))
+; =>
+; ((?WHO STEVE)
+;  (?WHO SALLY))
+
+(query-all t (likes sally ?what))
+; =>
+; ((?WHAT CATS)
+;  (?WHAT BEER)
+;  (?WHAT STEVE)
+;  (?WHAT KIM)
+;  (?WHAT SALLY))
+
+(query-all t (likes ?who ?what))
+; =>
+; ((?WHAT CATS ?WHO STEVE)
+;  (?WHAT BEER ?WHO STEVE)
+;  (?WHAT CATS ?WHO KIM)
+;  (?WHAT TEA ?WHO KIM)
+;  (?WHAT CATS ?WHO SALLY)
+;  (?WHAT BEER ?WHO SALLY)
+;  (?WHAT STEVE ?WHO SALLY)
+;  (?WHAT KIM ?WHO SALLY)
+;  (?WHAT SALLY ?WHO SALLY))
+
+ +

Databases

+

The main data structure of Temperance is a database. Most functions in +Temperance's API take this as their first argument.

+

You can create a database with make-database:

+
(defparameter *db* (make-database))
+(defparameter *db2* (make-database))
+
+ + +

You can then use it in other functions:

+
(push-logic-frame *db*)
+
+(fact *db* (drink coffee))
+(fact *db* (drink tea))
+(fact *db* (drink water))
+
+(finalize-logic-frame *db*)
+
+(query-all *db* (drink ?what))
+; =>
+; ((?WHAT COFFEE)
+;  (?WHAT TEA)
+;  (?WHAT WATER))
+
+(query-all *db2* (drink ?what))
+; =>
+; NIL
+
+ + +

Temperance also creates a database by default and stores it in +*standard-database*. You can pass t to functions instead of a database +object to operate on this standard database if you don't want to bother creating +one of your own.

+

Databases are not (currently) thread-safe. The consequences are undefined if +you access the same database object concurrently. Thread-safety is on the +TODO list.

+

Logic Frames

+

Temperance supports adding/retracting rules and facts to/from a database at +runtime, but it has a special interface for doing this in the interest of speed.

+

(Note: from here on out we'll say "rules" to mean "rules and facts", because +facts are just rules with empty bodies.)

+

Assertion / Pushing

+

Each Temperance database has a "logic stack" consisting of zero or more "logic +frames". To add some rules to the database you push a logic frame onto the +stack, add the rules, and then finalize the logic frame to compile the rules +into Prolog bytecode:

+
(defparameter *db* (make-database))
+
+; Nothing's in the database yet.
+(query-all *db* (drink ?what))
+; => NIL
+
+; Push a logic frame.
+(push-logic-frame *db*)
+
+; Still nothing in the DB.
+(query-all *db* (drink ?what))
+; => NIL
+
+; Add some facts.
+(fact *db* (drink coffee))
+(fact *db* (drink tea))
+(fact *db* (drink water))
+
+; There's STILL nothing in the DB, because we haven't
+; finalized the frame yet!
+(query-all *db* (drink ?what))
+; => NIL
+
+; Finalize the frame.  This compiles `drink`'s rules
+; into bytecode.
+(finalize-logic-frame *db*)
+
+; And now we can finally see some results.
+(query-all *db* (drink ?what))
+; => ((?WHAT COFFEE) (?WHAT TEA) (?WHAT WATER))
+
+ + +

Retraction / Popping

+

You can pop a logic frame off the stack to retract everything inside it:

+
(query-all *db* (drink ?what))
+; => ((?WHAT COFFEE) (?WHAT TEA) (?WHAT WATER))
+
+(pop-logic-frame *db*)
+
+(query-all *db* (drink ?what))
+; => NIL
+
+ + +

Convenience

+

Most of the time you'll want to push a logic frame, add some stuff, and then +immediately finalize it. Temperance provides a push-logic-frame-with macro to +make this easier:

+
(push-logic-frame-with *db*
+  (princ "Adding some facts...")
+  (fact *db* (sound cat meow))
+  (fact *db* (sound dog woof))
+  (fact *db* (sound cow moo)))
+; => Adding some facts...
+
+(query-all *db* (sound cat ?what))
+; => ((?WHAT MEOW))
+
+ + +

Note how the body of push-logic-frame-with takes arbitrary code, not just +rules and facts.

+

Restrictions

+

There main limitation introduced by Temperance's logic stack is that any +particular predicate must exist entirely in a single logic frame. You cannot +spread the definition of a single predicate across multiple frames:

+
; This is fine
+(push-logic-frame-with *db*
+  (fact *db* (sound cat meow)))
+
+; ERROR!  `sound/2` was already defined in a previous frame!
+(push-logic-frame-with *db*
+  (fact *db* (sound dog woof)))
+
+ + +

Why?

+

Why does Temperance bother with this stack interface? Why not just allow +arbitrary assertion and retraction of facts?

+

Arbitrary assertion and retraction would be a friendlier interface, but would be +slower. Temperance was made with General Game Playing in mind, where speed is +important. Asserting and retracting facts with a stack like this allows several +speed improvements:

+
    +
  • Predicates only need to be compiled once (when their frame is finalized), + instead of being recompiled every time a new clause is asserted.
  • +
  • Retraction of a frame is lighting fast — it's just changing a single integer + and zeroing out a contiguous block of memory.
  • +
  • The VM's code store no longer needs to deal with memory fragmentation or + garbage collection.
  • +

Rules

-

Anonymous Variables

+

Once you've got an open logic frame you can add some rules to the database.

+

Basic Rule/Fact Macros

+

Temperance offers a number of macros for adding things to a logic frame. (fact +database fact) is the simplest, and will just add a single fact to the +database:

+
(fact *db* (cat scruffy))
+
+ + +

If you want to add more than one fact you can use (facts database fact...):

+
(facts *db*
+  (cat fluffy)
+  (dog rover)
+  (dog lassie)
+  (dog spot))
+
+ + +

Adding rules can be done with (rule database head body...):

+
(rule *db* (pet ?who)
+  (cat ?who))
+
+(rule *db* (pet ?who)
+  (dog ?who))
+
+ + +

Logic variables in Temperance are any symbols that start with the ? character.

+

Dynamic Rules

+

The fact, facts, and rule macros quote their arguments. If you need to +build rules at runtime you'll need to use the invoke-... variants and manage +the quoting yourself:

+
(defun add-cat-lover (database person)
+  (invoke-rule database
+      `(likes ,person ?who)
+    '(cat ?who)))
+
+ + +

The author is aware that invoke-... is an awful name and welcomes suggestions +for something better.

Queries

+

Once you've got some logic frames finalized and ready to go you can query the +database with the query... macros.

+

query

+

(query database body...) will return two values:

+
    +
  • A plist of the substitutions necessary to make body true (or nil if none + is possible).
  • +
  • t if the query was successful, nil otherwise.
  • +
+

The second value lets you distinguish a successful query that required no +bindings from an unsuccessful query. For example:

+
(defparameter *db* (make-database))
+
+(push-logic-frame-with *db*
+  (facts *db*
+    (cat scruffy)
+    (cat fluffy)))
+
+(query *db* (cat fluffy))
+; =>
+; NIL
+; T        successful, no bindings needed
+
+(query *db* (cat chad))
+; =>
+; NIL
+; NIL      unsuccessful
+
+(query *db* (cat ?who))
+; =>
+; (?WHO SCRUFFY)
+; T
+
+ + +

query only returns the first set of substitutions it finds. If you want to see all results you'll need to use query-all.

+

query-all

+

query-map

+

query-for

+

query-do

+

query-find

+

prove

+

Anonymous Variables

+

Each bare ? symbol is treated as a separate anonymous variable:

+

Dynamic Queries

Lists

-

Logic Frames

Cut

-

Dynamic Queries and Rules

+

Call

Built-In Predicates