eliza-cl.org Eliza Tests
Table of Contents
Preface
The computer code here is not original to me. and this document reflects several of my ongoing projects:
- Learning 'classic' AI by working through Peter Norvig's Paradigms of Artificial Intelligence: Case Studies in Common Lisp. Norvig's book is the source for the Lisp code in this project.
- Learning more Emacs in general and org-mode in particular.
- Developing a work process around Literate Programming.
Introduction
System
This is my first attempt at using ASDF to build a system. The system uses utility files available here.
;;; To build the system ;;; (asdf:operate 'asdf:load-op :eliza) ;;; then run ;;; (in-package :eliza) (load "../utils/norvig-utils") (load "../utils/norvig-debugger") (defpackage #:eliza (:use :common-lisp :asdf :norvig-utils :norvig-debugger :kludgecode)) (in-package :eliza) (defsystem eliza :serial t :components ((:file "eliza")))
:eliza package
As is the case with ASDF, this is also my first time developing using Common Lisp's package system.
(in-package :eliza)
Pattern Matching
simple-equal
(defun simple-equal (x y) "Are x and y equal. Does not check inside strings." (if (or (atom x) (atom y)) (eql x y) (and (simple-equal (first x) (first y)) (simple-equal (rest x) (rest y)))))
variable-p
This function brings up two interesting aspects of Common Lisp. The first is that atom
's are not truely atomic. The second is that predicate syntax in Common Lisp suffers from the same big-language inconsistencies as might be found in PHP.
(defun variable-p (x) "Is x a variable where a variable is a symbol beginning with '?'" (and (symbolp x) (equal (char (symbol-name x) 0) #\?)))
pat-match
The naive version is so straight forward it could be template code for recursing on a list (or two lists). There is a certain concision to the first part which avoids enumerating all the cases of atoms and lists. Though it arises more from the template style than anything else.
A naive version
(defun pat-match (input pattern) "Does pattern match input? Any variable can match anything." (if (variable-p pattern) t (if (or (atom pattern) (atom input)) (eql pattern input) (and (pat-match (first input) (first pattern)) (pat-match (rest input) (rest pattern))))))
A second buggy version
One problem is that (eql pattern input)
may return t
. While nil
is not a problem for append
, t
is…since t
is not a list. Booleans are actually tough if they're overloaded as they are many languages.
The second problem is semi-boolean contexts: those in which nil
may be returned from a successful match versus where nil
is returned because a predicate turned out to be false. Did I say booleans are tough?
The final problem is that we want the same variable (e.g. ?X
) to consistently be bound to the same expression when it appears multiple times in an input.
(defun pat-match (pattern input) "BUGGY Second version." (if (variable-p pattern) (list (cons pattern input)) (if (or (atom pattern) (atom input)) (eql pattern input) (append (pat-match (first pattern) (first input)) (pat-match (rest pattern) (rest input))))))
pattern matching constants
The first step in the third version is to turn pat-match
into a true predicate that only returns nil
for failure. It's worth noting that only having a single value that indicates false
is helpful here.
(defconstant fail nil "indicates pat-match failure.") (defconstant no-bindings '((t . t)) "Indicates a pattern match success, with no variables.")
abstractions over assoc
Note: The ASDF build system loads these from my norvig-utils repository. They are shown here to maintain consistency with the text.
Building abstractions over assoc
enables using the language of variables and bindings rather than low level lisp.
(defun get-bindings (var bindings) "Find a (variable . value) pair in a binding list." (assoc var bindings)) (defun binding-val (binding) "Get the value part of a single binding." (cdr binding)) (defun lookup (var bindings) "Get the value part (for var) from a binding list." (binding-val (get-bindings var bindings))) (defun extend-bindings (var val bindings) "Add a (var . value) pair to a binding list." (cons (cons var val) bindings))
A third version
There are five cases for pat-match
:
- Bindings list is
fail
and the match fails. - Pattern is a single variable.
- Pattern and input are
eql
. - Pattern and input are both lists.
- None of these holds and the match fails.
(defun pat-match (pattern input &optional (bindings non-bindings)) "Match pattern against input in the context of bindings." (cond ((eq bindings fail) fail) ((variable-p pattern) (match-variable pattern input bindings)) ((eql pattern input) bindings) ((and (consp pattern) (consp input)) (pat-match (rest pattern) (rest input) (pat-match (first pattern) (first input) bindings))) (t fail)))
(defun match-variable (var input bindings) "Does var match input. Uses bindings. Returns bindings with or without an update depending on match." (let ((binding (get-bindings var bindings))) (cond ((not binding) (extend-bindings var input bindings)) ((equal input (binding-val binding)) bindings) (t fail))))
Segment Pattern Matching
It is useful to have two types of variables, those that match individual input elements and others that behave like Lisp's &rest
parameter. The latter can be thought of as matching segments. This would allow a syntax such as (?P need . ?X)
.
;;; Example (pat-match '((?* ?p) need (?* ?x)) '(Mr Hulot and I need a vaction)) ((?P MR HULOT AND I) (?X A VACTION))
Updating pat-match
for the new behavior:
(defun pat-match (pattern input &optional (bindings no-bindings)) "Match pattern against input in context of the bindings." (cond ((eq bindings fail) fail) ((variable-p pattern) (match-variable pattern input bindings)) ((eql pattern input) bindings) ((segment-pattern-p pattern) (segment-match pattern input bindings)) ((and (consp pattern) (consp input)) (pat-match (rest pattern) (rest input) (pat-match (first pattern) (first input) bindings))) (t fail)))
Adding the new predicate highlights Common Lisp's flexibility in regards to naming symbols by allowing a notation evocative of the kleene star.
(defun segment-pattern-p (pattern) "Is this a segment matching pattern: ((?* var) . pat)" (and (consp pattern) (starts-with (first pattern) '?*)))
The first pass at segment-match
address the problems that arise when the next element of the pattern is contained in the input but there are intervening elements between what has been matched already and the match for the next element of the pattern. It allows looking further ahead in the input at the expense of requiring a segment-variable to be the last variable in a pattern.
(defun segment-match (pattern input bindings &optional (start 0)) "Match the segment patter ((?* var) . pat) against input." (let ((var (second (first pattern))) (pat (rest pattern))) (if (null pat) (match-variable var input bindings) ;; We assume pat starts with a constant ;; In other words, a pattern can't have 2 consectutive vars (let ((pos (position (first pat) input :start start :test #'equal))) (if (null pos) fail (let ((b2 (pat-match pat (subseq input pos) bindings))) ;; If this match failed try another longer one ;; If it worked, check that the variables match (if (eq b2 fail) (segment-match pattern input bindings (+ 1 pos)) (match-variable var (subseq input 0 pos) b2))))))))
Final
<<eliza-package>> <<pat-match-constants>> <<binding-utilities>> <<variable-p>> <<match-variable>> <<pat-match>> <<segment-match>> <<segment-pattern-p>>
Tests (experimental)
Introduction
This is mostly an experiment:
- At the emacs level it is about building compound documents using
#+INCLUDE:
to break up org documents. - At the literate programming level it is about beginning to incorporate testing into literate documents.
- At the Common Lisp level is about learning
lisp-unit
for unit testing. - At the Common Lisp development environment level it is about learning
quicklisp
. It is good that I brokequicklisp
andASDF
into seperate exercises. Without understanding a bit aboutASDF
I was confused when looking atquicklisp
and when I looked atquicklisp
it just made me confused aboutASDF
.
Base File
<<lu-install>> <<lu-test-one>>
Installing lisp-unit
(ql:quickload :lisp-unit)
Test One
(in-package :eliza) (define-test test-one (assert-equal 5 5))