53b3bd05714d

First stab at usage doc.  Not done yet
[view raw] [browse files]
author Steve Losh <steve@stevelosh.com>
date Sun, 22 Oct 2017 14:52:48 -0400
parents 6f43a660d22a
children a77e9fda3229
branches/tags (none)
files docs/02-usage.markdown

Changes

--- a/docs/02-usage.markdown	Wed Mar 29 18:20:14 2017 +0000
+++ b/docs/02-usage.markdown	Sun Oct 22 14:52:48 2017 -0400
@@ -1,15 +1,333 @@
 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.
+
 [TOC]
 
 ## 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:
+
+    :::lisp
+    (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:
+
+    :::lisp
+    (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`:
+
+    :::lisp
+    (defparameter *db* (make-database))
+    (defparameter *db2* (make-database))
+
+You can then use it in other functions:
+
+    :::lisp
+    (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:
+
+    :::lisp
+    (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:
+
+    :::lisp
+    (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:
+
+    :::lisp
+    (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:
+
+    :::lisp
+    ; 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:
+
+    :::lisp
+    (fact *db* (cat scruffy))
+
+If you want to add more than one fact you can use `(facts database fact...)`:
+
+    :::lisp
+    (facts *db*
+      (cat fluffy)
+      (dog rover)
+      (dog lassie)
+      (dog spot))
+
+Adding rules can be done with `(rule database head body...)`:
+
+    :::lisp
+    (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:
+
+    :::lisp
+    (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:
+
+    :::lisp
+    (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