%% -*- Mode:TeX; Fonts:(hl12fb) -*- %% *-* File: /usr/local/gbbopen/doc-source/tutorial.tex *-* %% *-* Last-Edit: Sun Jan 1 05:55:15 2012; Edited-By: cork *-* %% *-* Machine: phoenix.corkills.org *-* %% Copyright (C) 2005-2012, Dan Corkill %% Part of the GBBopen Project. %% Licensed under Apache License 2.0 (see LICENSE for license information). %% %% ======================================================================== %% The LaTeX and hyperlatex code used in producing GBBopen documentation %% is placed under and covered by the GBBopen software license that %% accompanies each GBBopen distribution and is also available at %% http://GBBopen.org/downloads/LICENSE. %% ======================================================================== %% %% Note: To keep both LaTeX and hyperlatex happy with indexing, index %% commands must be positioned with care (including the use of %% % after all entries). Follow the placement used here to %% minimize the creation of anchors by hyperlatex. Indexes %% should only be placed at locations where an inserted   %% anchor does not visibly affect the html layout. %% %% \indexit doesn't play well with @\xxx{} formating, so use %% the basic \index{...|itidx} rather than \indexit shorthand. \documentclass[10pt,twoside,english,pdftex]{article} \usepackage{hyperlatex} \usepackage{color} \usepackage{makeidx} \T\usepackage{atbeginend} %\T\usepackage{palatino} \T\usepackage{newcent} \T\usepackage[T1]{fontenc} % Use smaller TT font throughout \W\begin{iftex} \makeatletter \DeclareRobustCommand\smallttfamily {\not@math@alphabet\ttfamily\mathtt \fontfamily\ttdefault\small\selectfont} \makeatother \W\end{iftex} \T\usepackage{fancyhdr} \W\usepackage{frames} %% Req'd for pdflatex -- note use epstopdf to convert eps figures; %% ps2pdf won't create correct bounding boxes %\T\newif\ifpdfg\ifx\pdfoutput\undefined\pdffalse\else\pdfoutput=1\pdftrue\fi %\T\newcommand{\pdfgraphics}{\ifpdf\DeclareGraphicsExtensions{.pdf,.jpg}\else\fi} \T\usepackage{graphicx} \newcommand{\docname}{Tutorial} \input{common.tex} %% This should work, but the current-working-directory gets confused when %% running from the same AucTeX session: %% %%\htmldirectory{../hypertutorial} \htmldirectory{/usr/local/gbbopen/hypertutorial} \htmladdress{\xml{a target="_top" class="address" href="http://GBBopen.org/"}The GBBopen Project\xml{/a}} \htmlcss{gbbopen.css} \newcommand{\homepage}{http://GBBopen.org} \setcounter{htmldepth}{2} %\setcounter{secnumdepth}{3} \W\newcommand{\GoToTopTarget}{\xml{a target="_top" href="tutorial.html"}} \W\newcommand{\fndocrule}{\xml{hr color="99CCCC"}} \T\newcommand{\ctrl}[1]{\textasciicircum{}#1} \W\newcommand{\ctrl}[1]{^#1} %% ---------------------------------------------------------------------------- %% Titlepage (LaTeX version only) \W\begin{iftex} \makeatletter %% Add logo to article.cls version \def\@maketitle{% \newpage \hfill\includegraphics[scale=0.5]{GBBopen-logo}\\ \null \vskip 2em% \begin{center}% \let \footnote \thanks {\LARGE \@title \par}% \vskip 1.5em% {\large \lineskip .5em% \begin{tabular}[t]{c}% \@author \end{tabular}\par}% \vskip 1em% {\large \@date}% \end{center}% \par \vskip 1.5em} \makeatother \W\end{iftex} %% ---------------------------------------------------------------------------- \title{\vspace{3in}{\LARGE\textbf{GBBopen Tutorial}}\\[14pt] {\Large\textbf{Version \gbbopenversion}}} \author{\vspace{1in}~\\{\Large\textbf{Dan Corkill}}\\~\\~\\ {\Large\textbf{The GBBopen Project}}\\[4pt] {\large\textbf{\xsitelink{http://GBBopen.org}{http://GBBopen.org}}}} \date{\today\\[4pt] \hhmm~\timezone} \W\renewcommand{\navigationname}{{\Large Tutorial}} \newcommand{\inprogress}{\vfill\textcolor{darkergray}{\textsf{\textbf{This tutorial is under construction;\\additional exercises will be added.}}}} \W\begin{iftex} \usepackage[bookmarks=true, plainpages=false, linktocpage=true, colorlinks=true, linkcolor=blue, pagecolor=blue, urlcolor=blue, pdfstartview=FitH, pdfpagemode=None]{hyperref} \W\end{iftex} \makeindex %% ======================================================================== %% Global index entries \index{slot, link|see{link slot}}% \index{Common Lisp!REPL|see{REPL}}% \index{KS|see{knowledge source}}% \index{KSA|see{knowledge-source activation}}% \index{read-eval-print loop|see{REPL}}% %% ======================================================================== \begin{document} \T\pagenumbering{roman} \T\pagestyle{plain} \T\thispagestyle{empty} \T\raggedright \T\sloppy \T\parskip=0.5\baselineskip \T\parindent=0pt \T\maketitle \T\renewcommand{\headrulewidth}{0pt} \T\begin{ifhtml} \xml{p class="tiny" align="right"} \xsitelink{PDF version}{http://gbbopen.org/downloads/tutorial.pdf} \xml{/p} \xml{img align="left" src="GBBopen-logo.png"} \xml{br clear="both"} {\LARGE\bf Tutorial}\\ {\large\bf GBBopen Version \gbbopenversion} \T\end{ifhtml} \begin{center} \inprogress\\ \end{center} %% ======================================================================== %% Introduction (HTML version only) \T\begin{ifhtml} \label{sec:introduction}% \input{tutorial-intro.tex} \reflink{Let's get started...}{sec:starting-up} % Turn off the auto Navigation menu: \setcounter{htmlautomenu}{0} \T\end{ifhtml} % This should be redundant, given the one above, but it is needed! \T\thispagestyle{empty} %% ======================================================================== %% Copyright page \T\newpage \T~ \T\vfill \W\xname{tutorial-copyright} \W\section*{Copyright} Copyright \copyright{} 2005--2012 by Daniel D. Corkill for the GBBopen Project. This tutorial may be reproduced and distributed in whole or in part, subject to the following conditions: \begin{tightitemize} \item The copyright notice above and this permission notice must be preserved complete on all complete or partial copies. \item Any translation or derivative work of this tutorial must be approved by the copyright holder in writing before distribution. \item If you distribute this tutorial in part, instructions and a means for obtaining a complete version of this tutorial must be included. \item Small portions may be reproduced as illustrations for reviews or quotes in other works without this permission notice if proper citation is given. \item Distribution of this work or a derivative of this work in any standard (hard copy) book form is prohibited without prior written permission from the copyright holder. \end{tightitemize} All source code examples in this work are placed under and covered by the GBBopen software license that accompanies each GBBopen distribution and is also available at \xsitelink{\nobr{\code{http://GBBopen.org/svn/GBBopen/trunk/LICENSE}}}% {http://GBBopen.org/svn/GBBopen/trunk/LICENSE}. \T\bigskip % This work is licensed and provided ``as is'' without warranty of any kind, express or implied, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose or a warranty of non-infringement. GBBopen software and the information in this tutorial are subject to change without notice. \T\bigskip % Please help improve this tutorial by reporting any errors, inaccuracies, bugs, misleading or confusing statements, missing or unhelpful index entries, and typographical errors that you find. E-mail bug reports, comments, and suggestions to \xsitelink{\nobr{\code{bugs@GBBopen.org}}}{mailto:bugs@GBBopen.org}. Your help is greatly appreciated and will be acknowledged. \T\bigskip % GBBopen is a trademark of the GBBopen Project.\\ Any other brand or product names are trademarks or registered trademarks of their respective holders. \T\bigskip % \hfill\begin{tabular}{@{}l@{}} \textbf{The GBBopen Project}\\ 181 Pondview Drive\\ Amherst, Massachusetts ~ 01002\\~\\ \xsitelink{\nobr{\code{GBBopen@GBBopen.org}}}{mailto:GBBopen@GBBopen.org}\\ \xsitelink{\nobr{\code{http://GBBopen.org}}}{http://GBBopen.org}\\ \end{tabular} \T\vspace{0.5in} \W\bigskip \W\begin{iftex} This tutorial was produced using \xsitelink{\LaTeX}{http://www.latex-project.org} and PDF\LaTeX. \W\end{iftex} \T\begin{ifhtml} This tutorial was produced using \xsitelink{\LaTeX}{http://www.latex-project.org} and \xsitelink{Hyperlatex}{http://hyperlatex.sourceforge.net/} 2.9a. \T\end{ifhtml} \T\cleardoublepage \W\xname{tutorial-contents} \T\bgroup \T\parskip 0pt \tableofcontents \T\egroup %% ======================================================================== %% Acknowledgments \T\clearpage \W\xname{tutorial-acknowledgments} % Turn the auto Navigation menu back on: \W\setcounter{htmlautomenu}{1} \input{acknowledgments.tex} The random-walk example used in this tutorial is adapted from \textit{Getting Started with GBB\/}, written by Dan Corkill and Suzanne Tromara. %% ======================================================================== %% Introduction (LaTeX version only) \W\begin{iftex} \markright{}% \cleardoublepage \setcounter{page}{1} \pagenumbering{arabic} \pagestyle{fancy} \thispagestyle{fancybottom} \section*{Introduction} \addcontentsline{toc}{section}{\textbf{Introduction}}% \label{sec:introduction}% \input{tutorial-intro.tex} \W\end{iftex} %% ======================================================================== %% Starting Up \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-starting-up} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \section{Starting GBBopen} \label{sec:starting-up}% This initial exercise requires that you: \begin{tightitemize} \item Obtain and install Common Lisp \item Obtain and install GBBopen \item Subscribe to the GBBopen mailing lists \item Interact with Common Lisp's read-eval-print loop (REPL) \item Recover from errors \item Prepare to use GBBopen in the next exercises by loading the \nobr{\code{:gbbopen-user}} module \end{tightitemize} \fndocrule \subsection*{Step 1: Obtain and install Common Lisp} \index{Common Lisp!installing}% GBBopen requires a supported Common Lisp implementation. If one has already been installed on your system, then this step is finished. (That was easy!) If not, then you will need to choose, obtain, and install a Common Lisp implementation before moving on to the next step. The list of Common Lisp implementations, with version numbers, on which GBBopen is supported is maintained on the ``\xsitelink{Current ports}{http://GBBopen.org/ports.html}'' page of the GBBopen Project \xsitelink{web site}{http://GBBopen.org/}. The list includes commercial products and open-source implementations made available under varying license arrangements. The vendors of commercial Common Lisp products offer no-cost ``Trial'' or ``Personal'' editions that can support the tutorial exercises and allow you to become familiar with their products before making a purchase decision. Choosing a particular implementation is a subjective decision. Some Common Lisp implementations run on only a single platform. Some implementations do not provide multiprocessing (thread) support on some or all of the platforms that they run on. Some implementations come with their own integrated development environment (IDE), interactive graphics facilities, and supported libraries and extensions. When it comes to selecting an implementation, there is no ``best'' answer (but there is also no wrong answer, if the implementation meets your current needs). All these Common Lisp implementations strive to conform to the ANSI (American National Standards Institute) standard for the Common Lisp language. By writing GBBopen applications to remain consistent with the ANSI standard (including portable extensions to the standard that are provided by GBBopen), we can easily run our code on any Common Lisp implementation that provides similar capabilities (such as threads, for example). % If you would rather not explore the space of Common Lisp implementations for % your platform, Peter Seibel (the author of \xsitelink{\textit{Practical Common % Lisp\/}}{http://www.gigamonkeys.com/book/}) provides % \xsitelink{Lispbox}{http://www.gigamonkeys.com/lispbox/}. Lispbox % (``Lisp in a Box'') offers an easy to install, no-cost, Common Lisp % environment for Linux/86, Macintosh OS X, and Windows. Lispbox packages % together a Common Lisp implementation, % \xsitelink{Emacs}{http://www.gnu.org/software/emacs/emacs.html}, and the % \xsitelink{SLIME}{http://common-lisp.net/project/slime/} Common Lisp % development environment for Emacs. For the less adventurous, installing % Lispbox can be an appealing way to get started. However you choose to obtain Common Lisp, you must have an installed and operating implementation for your system before proceeding to the next step. \subsection*{Step 2: Obtain and install GBBopen} \index{installing GBBopen}% \index{GBBopen!installing}% Unless someone has already installed GBBopen on your machine, you will need to obtain and install it. GBBopen is available in source form from \xsitelink{\nobr{\code{http://GBBopen.org/}}}{http://GBBopen.org/}. In this step I discuss three different approaches for downloading GBBopen, and you can follow whichever approach is most familiar for you. (In the absence of any preference, I recommend the Subversion client approach.) \subsubsection*{Downloading the snapshot archive} A snapshot archive of the GBBopen source-code repository can be downloaded from \xsitelink{\nobr{\code{http://gbbopen.org/downloads/GBBopen.tar.gz}}}{http://gbbopen.org/downloads/GBBopen.tar.gz}. Extract the archive into a directory of your choosing, and follow the ``Compiling All GBBopen Modules'' instructions that are contained in the \code{README} file of the installation. \subsubsection*{Downloading with Subversion} \codeindexit{values}% \index{Subversion client}% Alternatively, if you are familiar with \xsitelink{Subversion}{http://subversion.tigris.org} and have a Subversion client installed on your computer, you can checkout the latest files directly from the GBBopen repository. For example, the shell command: % \W\supp \begin{example} \textcolor{darkergray}{% [~]\$ \textcolor{black}{svn checkout http://GBBopen.org/svn/GBBopen/trunk/ gbbopen}} \end{example} % will create a GBBopen repository tree rooted at the directory named \code{gbbopen} in your current working directory. As above, follow the ``Compiling All GBBopen Modules'' instructions that are contained in the \code{README} file of the installation. \index{updating GBBopen}% \index{GBBopen!updating}% \index{Subversion client}% \index{TortoiseSVN}% \index{TortoiseCVS}% GBBopen development is ongoing, and you should update your GBBopen installation regularly in order to obtain the latest capabilities and enhancements. Subversion provides an easy way to keep current, and you are strongly encouraged to install a Subversion client and use it perform frequent updates. Simply issue the command: % \W\supp \begin{example} \textcolor{darkergray}{% [~/gbbopen]\$ \textcolor{black}{svn update}} \end{example} % from the root directory of your GBBopen repository tree. The \xsitelink{TortoiseSVN}{http://tortoisesvn.tigris.org/} Subversion client is highly recommended for Windows users. TortoiseSVN is smoothly integrated with the Windows Shell (Explorer) and is as easy to use as \xsitelink{TortoiseCVS}{http://www.tortoisecvs.org/} (also highly recommended if aren't already using it as your CVS client on Windows). Subversion \code{.svn} administrative directories are included in the GBBopen snapshot archive, so a Subversion \code{update} command can be used to freshen a GBBopen installation that was originally installed using the snapshot archive download-and-extraction approach described previously. \subsubsection*{Downloading with clbuild} \index{clbuild}% % If you use \xsitelink{clbuild}{http://common-lisp.net/project/clbuild/}, you can use it to download the latest GBBopen sources by issuing the following command: % \W\supp \begin{example} \textcolor{darkergray}{% [~]\$ \textcolor{black}{clbuild install gbbopen}} \end{example} At any later time, you can issue the command: % \W\supp \begin{example} \textcolor{darkergray}{% [~]\$ \textcolor{black}{clbuild update gbbopen}} \end{example} % to obtain the latest sources from the GBBopen repository. If your clbuild doesn't know about GBBopen, add the following line to the clbuild wnpp-projects file: % \W\supp \begin{example} gbbopen get_svn http://gbbopen.org/svn/GBBopen/trunk/ #blackboard-system framework, tools, \& utilities \end{example} As was the case with the other two download methods, follow the ``Compiling All GBBopen Modules'' instructions that are contained in the GBBopen \code{README} file to complete the installation. \subsubsection*{Downloading with Quicklisp} \index{Quicklisp}% % If you use \xsitelink{Quicklisp}{http://www.quicklisp.org/beta/}, you can use it to download and install GBBopen by evaluating the following form once Quicklisp is set up: % \W\supp \begin{example} \textcolor{darkergray}{% > \textcolor{black}{(ql:quickload "gbbopen")} ;; GBBopen is installed in \var{\/} ;; Your "home" directory is \var{\/} ;; Predefining :SWANK-BACKEND package for SLIME... ;; No shared module command definitions were found in \var{\/}/gbbopen-modules/. ;; No personal module command definitions were found in \var{\/}/gbbopen-modules/. ;; GBBopen is installed in \var{\/} ;; Your "home" directory is \var{\/} ;; Loading \var{\/}/extended-repl.lisp ;; Loading \var{\/}/commands.lisp ;; Loading \var{\/}/gbbopen-modules-directory.lisp ;; No shared module command definitions were found in \var{\/}/gbbopen-modules/. ;; No personal module command definitions were found in \var{\/}/gbbopen-modules/. ;; Defining an ASDF defsystem for each Module Manager module... To load "gbbopen": Load 1 ASDF system: gbbopen ; Loading "gbbopen" ("gbbopen") >} \end{example} As was the case with the other download methods, follow the ``Compiling All GBBopen Modules'' instructions that are contained in the GBBopen \code{README} file to complete the installation. \subsection*{Step 3: Subscribe to the GBBopen mailing lists} \index{GBBopen!mailing lists}% I strongly recommend that you subscribe to the GBBopen Announcements (\nobr{\code{gbbopen-announce}}) and the GBBopen Users (\nobr{\code{gbbopen-list}}) mailing lists. To subscribe, go to the ``Mailing Lists'' page on the \xsitelink{GBBopen web site}{http://GBBopen.org/} and click on the appropriate \code{subscribe} links. The traffic is low, and the messages and advice will save you time and frustration and get you writing quality GBBopen applications easier and sooner. (You might even want to browse the archived messages that are accessible from the ``Mailing Lists'' page.) If you need help or advice, the GBBopen Users list is the place to ask (should this tutorial and archived messages from the GBBopen Users list come up short). \subsection*{Step 4: Interact with the REPL} \index{REPL}% % This tutorial assumes that you have a basic understanding of Common Lisp and, in particular, how to interact with the Common Lisp implementation that you are using. Thus, you should be able to start up your Common Lisp system and enter forms into the ``Lisp Listener'' (also called the read-eval-print loop or simply the REPL) that it provides. When you start your Common Lisp system, it may first display some informational messages and then you should see a prompt for input that looks something like: % \W\supp \begin{example} \textcolor{darkergray}{% >} \end{example} % The specific format of the prompt differs depending on your Common Lisp implementation. The prompt character may vary, such as \code{*}, \code{:}, or \code{?}. The prompt might include a entry number that is incremented each time an expression is entered, such as: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% [1]>} \end{example} % or it might also include the name of the current package (symbol namespace) being used by the REPL: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% cl-user(1)>} \end{example} For the remainder of this tutorial, we will include the package name in our example prompts to make it clear what package should be current: % \W\supp \begin{example}% \textcolor{darkergray}{% cl-user>} \end{example} % The REPL prompt indicates that it is awaiting a Common Lisp expression to evaluate and then display the evaluation results. For example, enter the expression: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{(+ 1 2)} 3 cl-user>} \end{example} % Note that what you need to enter is shown in black and other items, such as the REPL prompt and displayed result, \code{3}, are shown in gray. We will follow this convention throughout the tutorial to help make it clear what you must provide in the context of other information. You may find it easier and faster to cut and paste text from this tutorial (if you are working with it on-line) rather than typing what is requested. On the other hand, some feel that they learn faster (and improve retention) through the action of typing. Either text-entry approach, however, is preferable to simply reading through the exercises. This tutorial is about \textit{learning by doing!} \codeindexit{*print-case*}% While we are on the subject of what is displayed, enter the following expression: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{'symbol} symbol cl-user>} \end{example} % The displayed result that you see might be in lower case, as shown, or upper case, or even capitalized. In this tutorial, results are shown with Common Lisp symbols displayed in lower case, which I prefer as being slightly easier to read. If you wish, you can duplicate this behavior in your REPL by entering the following form: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{(setf *print-case* ':downcase)} :downcase cl-user>} \end{example} \codeindexit{values}% Your Common Lisp implementation may also differ slightly on how it displays multiple returned values. For example: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{(values 1 2 3)} 1 2 3 cl-user>} \end{example} In this tutorial, we show multiple returned values as displayed on separate lines. Your Common Lisp implementation may show them differently, such as with semicolon value-separator characters: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{(values 1 2 3)} 1 ; 2 ; 3 cl-user>} \end{example} \subsection*{Step 5: Recover from an error} \index{errors!recovering from}% \index{Common Lisp!debugger}% Even you are a very careful typist, sooner or later you will enter a Common Lisp expression that signals an error. Therefore, it is important that you know how to get back on track when the inevitable occurs. Let's intentionally generate an error. Enter: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{(/ 1 0)} Error: Attempt to divide 1 by zero. cl-user>>} \end{example} The behavior of your Common Lisp environment should look similar, and you should now be in your implementation's debugger or ``break loop.'' The debugger prompt may differ from the standard REPL prompt to help remind you that you are in the break loop. Alternatively, your implementation may open another window or buffer in response to the error. Your implementation may allow you to continue entering Common Lisp expressions at the break prompt that are evaluated and the results displayed just as if you were in the normal REPL (opening the possibility of causing another error and triggering another instance of the debugger). You can use the debugger to inspect the nesting, or ``backtrace,'' of function calls that led to the error, view and edit local variable bindings, and so forth, and you can often correct the cause of the error and resume the broken evaluation directly from the debugger. For this tutorial, you only need to know how to abort out of the evaluation and return back to the REPL if you trigger an error. The details for aborting the computation and exiting the debugger are implementation dependent, so you may need to investigate how to abort out of an error on your Common Lisp implementation. For example, it might be as easy as entering an abort debugger command: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> (/ 1 0) Error: Attempt to divide 1 by zero. cl-user>> \textcolor{black}{:a} cl-user>} \end{example} % Consult your Common Lisp documentation or a knowledgeable friend if you need assistance with the debugger in your Common Lisp implementation (\code{:a} or \code{q} are typical abort commands). \subsubsection*{Breaking a computation} There are other mistakes that don't signal an error. Suppose I foolishly evaluate this expression: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{(loop (print "This is repetitive...") (sleep 1))} "This is repetitive..." "This is repetitive..." \textrm{\ldots{}}} \end{example} % Once I grow tired of watching this phrase repeat, it would be good to terminate the evaluation without having to kill the entire Common Lisp program. Again, the specifics depend on your Common Lisp environment, but all that is needed is to interrupt, or ``break,'' the computation. Often this is associated with typing one or more Control-c (\code{\ctrl{c}}) characters: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> (loop (print "This is repetitive...") (sleep 1)) "This is repetitive..." "This is repetitive..." \textrm{\ldots{}} "This is repetitive..." "This is repetitive..." "This is rep\textcolor{black}{\ctrl{C}} Error: Received keyboard interrupt \ctrl{C} cl-user>>} \end{example} % Typically a keyboard interrupt invokes the debugger where the computation can be resumed or aborted: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% cl-user> (loop (print "This is repetitive...") (sleep 1)) "This is repetitive..." "This is repetitive..." \textrm{\ldots{}} "This is repetitive..." "This is repetitive..." "This is rep\ctrl{C} Error: Received keyboard interrupt \ctrl{C} cl-user>> \textcolor{black}{:a} cl-user>} \end{example} \subsubsection*{Help! My REPL is broken!} Here is one last difficulty. Suppose I enter: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{(list "This" "is "also" "a" "problem!")} cl-user> cl-user> cl-user>} \end{example} % My REPL seems dead: no result is displayed and I continue to be prompted again and again for input. I suppose I should try the entering the expression again: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% cl-user> (list "This" "is "also" "a" "problem!") cl-user> cl-user> cl-user> cl-user> \textcolor{black}{(list "This" "is" "also" "a" "problem!")} cl-user> cl-user>} \end{example} % Nope, my REPL is still broken. Of course the cause of the problem is that I forgot the closing double-quote character on \code{"is"} when I entered the first expression. The REPL is still patiently waiting for me to finish that first Common Lisp expression. (I didn't break it after all!) So how do I get out of this? I could try entering a sequence of double-quote and close parentheses and hope I that I get lucky: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> (list "This" "is "also" "a" "problem!") cl-user> cl-user> cl-user> cl-user> (list "This" "is" "also" "a" "problem!") cl-user> cl-user> cl-user> \textcolor{black}{"} cl-user> \textcolor{black}{)} Error: Attempt to take the value of the unbound variable `also'. cl-user>>} \end{example} % or I could interrupt the REPL read operation, just as I did with the infinite loop situation above, and abort back from the debugger to the REPL and try again: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% cl-user> (list "This" "is "also" "a" "problem!") cl-user> cl-user> cl-user> cl-user> (list "This" "is" "also" "a" "problem!") cl-user> cl-user> cl-user> \textcolor{black}{\ctrl{C}} Error: Received keyboard interrupt \ctrl{C} cl-user>> \textcolor{black}{:a} cl-user> \textcolor{black}{(list "This" "is" "also" "a" "problem!")} ("This" "is" "also" "a" "problem!") cl-user>} \end{example} You may discover even more creative ways to get into problems, but these example situations should give you enough experience to get through the rest of the tutorial. \subsection*{Step 6: Load the \code{:gbbopen-user} module} GBBopen is packaged with its own \xreflink{module system}{sec:module-manager} that supports compiling and loading GBBopen components. \indexit{clbuild}% \indexit{Quicklisp}% To compile and load the \nobr{\code{:gbbopen-user}} module and all the GBBopen modules it requires, you need to evaluate the following forms within your Common Lisp environment. Unless you are using GBBopen with ASDF, clbuild, or Quicklisp (see below), load GBBopen's \nobr{\code{\var{\/}/initiate.lisp}} file from wherever GBBopen was installed: % \W\supp \begin{smallexample} \textcolor{darkergray}{% cl-user> \textcolor{black}{(load "\var{\/}/initiate.lisp")} ;; Loading \var{\/}/initiate.lisp ;; GBBopen is installed in \var{\/} ;; Your "home" directory is \var{\/} ;; Loading \var{\/}/extended-repl.lisp ;; Loading \var{\/}/commands.lisp ;; Loading \var{\/}/gbbopen-modules-directory.lisp ;; No shared module command definitions were found in \var{\/}/gbbopen-modules/. ;; No personal module command definitions were found in \var{\/}/gbbopen-modules/. #P"\var{\/}/initiate.lisp" cl-user> \textcolor{black}{:gbbopen-user} ;; Loading \var{\/}/startup.lisp ;; GBBopen is installed in \var{\/} ;; Your "home" directory is \var{\/} ;; Loading \var{\/}/source/module-manager/module-manager-loader.lisp ;; Loading \var{\/}/\var{\/}/module-manager/module-manager.lisp \textrm{\ldots{}} ;; Loading \var{\/}/modules.lisp ;; No shared module definitions were found in \var{\/}/gbbopen-modules/. ;; No personal module definitions were found in \var{\/}/gbbopen-modules/. \textrm{\ldots{}} ;; Compiling \var{\/}/source/gbbopen/gbbopen-user.lisp ;; Loading \var{\/}/\var{\/}/gbbopen/gbbopen-user.fasl t gbbopen-user>} \end{smallexample} GBBopen should compile (if necessary) and load all the files needed for the next exercise without error. The output on your Common Lisp implementation may vary somewhat from that shown above. For example, the file extension for compiled files, shown as \code{.fasl} throughout this tutorial, is dependent on your Common Lisp implementation and platform. \indexit{clbuild}% \indexit{Quicklisp}% \begin{notebox}{ASDF, clbuild, and Quicklisp users} GBBopen's Module Manager Facility provides an interface that allows \xsitelink{ASDF}{http://common-lisp.net/project/asdf/} (and therefore \xsitelink{clbuild}{http://common-lisp.net/project/clbuild/} and \xsitelink{Quicklisp}{http://quicklisp.org/beta/}) to play nice with Module Manager. If you installed GBBopen using clbuild or Quicklisp, ASDF has been informed of GBBopen's \nobr{\code{\var{\/}/gbbopen.asd}} system-definition file. Otherwise, to use ASDF to set up GBBopen, you must add the \nobr{\code{gbbopen.asd}} file to ASDF's Registry manually. Then, instead of loading the \nobr{\code{\var{\/}/initiate.lisp}} file, the Module Manager and GBBopen module definitions can be loaded using ASDF by entering: % \T\smallskip \W\supp\notpretop \begin{smallexample} \textcolor{darkergray}{% cl-user> \textcolor{black}{(asdf:operate 'asdf:load-op :gbbbopen)} ;; Loading \var{\/}/initiate.lisp ;; GBBopen is installed in \var{\/} ;; Your "home" directory is \var{\/} \textrm{\ldots{}} ;; Loading \var{\/}/startup.lisp ;; GBBopen is installed in \var{\/} ;; Your "home" directory is \var{\/} ;; Loading \var{\/}/source/module-manager/module-manager-loader.lisp ;; Loading \var{\/}/\var{\/}/module-manager/module-manager.lisp \textrm{\ldots{}} ;; Loading \var{\/}/modules.lisp ;; No shared module definitions were found in \var{\/}/gbbopen-modules/. ;; No personal module definitions were found in \var{\/}/gbbopen-modules/. \textrm{\ldots{}} ;; Defining an ASDF defsystem for each Module Manager module... cl-user>} \end{smallexample} % or when ASDF is integrated with Common Lisp's \textbf{require}: % \T\smallskip \W\supp\notpretop \begin{smallexample} \textcolor{darkergray}{% cl-user> \textcolor{black}{(require :gbbopen)} ;; Loading \var{\/}/initiate.lisp ;; GBBopen is installed in \var{\/} ;; Your "home" directory is \var{\/} \textrm{\ldots{}} ;; Loading \var{\/}/startup.lisp ;; GBBopen is installed in \var{\/} ;; Your "home" directory is \var{\/} ;; Loading \var{\/}/source/module-manager/module-manager-loader.lisp ;; Loading \var{\/}/\var{\/}/module-manager/module-manager.lisp \textrm{\ldots{}} ;; Loading \var{\/}/modules.lisp ;; No shared module definitions were found in \var{\/}/gbbopen-modules/. ;; No personal module definitions were found in \var{\/}/gbbopen-modules/. \textrm{\ldots{}} ;; Defining an ASDF defsystem for each Module Manager module... cl-user>} \end{smallexample} Notice that loading the \nobr{\code{\var{\/}/initiate.lisp}} file loaded only GBBopen's REPL command processing extensions, global REPL command definitions, and module-directory processing into Common Lisp--the Module Manager is not loaded until it is needed (such as when we performed the \nobr{\code{:gbbopen-user}} REPL command). The ASDF \code{:gbbopen} ``system'' start up, on the other hand, must also load the Module Manager and module definitions, as they are required in order to define an ASDF system for each Module Manager module. \end{notebox} \subsubsection*{Packages} \index{package}% \codeindex{in-package}% % Note that when the \code{:gbbopen-user} module was loaded, the Module Manager changed the current package to the \nobr{\code{:gbbopen-user}} package. In the Common Lisp implementation above, this is shown by the new package name in the REPL prompt. The current package can also be seen by evaluating: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{*package*} # gbbopen-user>} \end{example} In Common Lisp, a \textit{package\/} is a namespace that maps names to symbols. One package is always designated as the ``current'' package, and it is this package that is used by default for creating and finding symbols by their names. Initially, the current package is set to the \nobr{\code{:common-lisp-user}} package. The \nobr{\code{:gbbopen-user}} module creates a new package, named \nobr{\code{:gbbopen-user}}, that includes GBBopen Tools and GBBopen Core symbols in addition to the standard Common Lisp symbols. If the current package is not set to the \nobr{\code{:gbbopen-user}} package in the exercises to follow, references to GBBopen functions and variables will not map to the proper symbols. \subsubsection*{If it didn't work$\ldots$} If you are running GBBopen in a Common Lisp environment that doesn't support REPL commands and the \nobr{\code{:gbbopen-user}} REPL command didn't work, all is not lost. Loading \nobr{\code{\var{\/}/initiate.lisp}} also defines functions in the \nobr{\code{:common-lisp-user}} package with the same name as the REPL command: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{(load "\var{\/}/initiate.lisp")} ;; Loading \var{\/}/initiate.lisp ;; GBBopen is installed in \var{\/} ;; Your "home" directory is \var{\/} ;; Loading \var{\/}/extended-repl.lisp ;; Loading \var{\/}/commands.lisp ;; Loading \var{\/}/gbbopen-modules-directory.lisp ;; No shared module command definitions were found in \var{\/}/gbbopen-modules/. ;; No personal module command definitions were found in \var{\/}/gbbopen-modules/. #P"\var{\/}/initiate.lisp" cl-user> \textcolor{black}{(gbbopen-user)} ;; Loading \var{\/}/startup.lisp \textrm{\ldots{}} ;; Loading \var{\/}/\var{\/}/gbbopen/gbbopen-user.fasl gbbopen-user>} \end{example} \subsubsection*{File protection problems?} \index{errors!file protection}% \index{compiling!all of GBBopen}% \codeindex{:compile-gbbopen}% \codeindex{compile-gbbopen}% % If one or more GBBopen files needs to be compiled, you must have write permission for those files and directories on your file system. If you do not have write permission and someone else is maintaining your GBBopen installation, you must remind them that they should perform the ``Compiling All GBBopen Modules'' operations (described in the GBBopen installation \code{README} file) every time they update GBBopen to ensure that all the latest GBBopen files have been compiled. \subsubsection*{REPL command syntax} \label{sec:REPL-command-syntax} GBBopen's REPL commands are defined to mimic the syntax of existing REPL commands in your Common Lisp environment. REPL commands, including those that have arguments, are normally specified using a non-list (``spread'') representation. For example: % \W\supp \begin{example} cl-user> :gbbopen-user :create-dirs ;; Loading \var{\/}/startup.lisp \textrm{\ldots{}} ;; Loading \var{\/}/\var{\/}/gbbopen/gbbopen-user.fasl gbbopen-user> \end{example} % Some Common Lisp implementations (\xsitelink{Clozure CL}{http://trac.clozure.com/ccl}, \xsitelink{LispWorks}{http://www.lispworks.com}, and \xsitelink{SBCL}{http://sbcl.sourceforge.net}) and the \xsitelink{SLIME}{http://common-lisp.net/project/slime/} REPL interface, also support REPL commands in non-spread (list) form in addition to the spread notation. For example: % \W\supp\notpretop \begin{example} cl-user> (:gbbopen-user :create-dirs) ;; Loading \var{\/}/startup.lisp \textrm{\ldots{}} ;; Loading \var{\/}/\var{\/}/gbbopen/gbbopen-user.fasl gbbopen-user> \end{example} As noted above, equivalent functions in the \nobr{\code{:common-lisp-user}} package are always defined for each REPL command, and these functions can be used in place of the keyword REPL command processing: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{(cl-user:gbbopen-user :create-dirs)} ;; Loading \var{\/}/startup.lisp \textrm{\ldots{}} ;; Loading \var{\/}/\var{\/}/gbbopen/gbbopen-user.fasl gbbopen-user>} \end{example} We will show the \reflink{spread command syntax}{sec:REPL-command-syntax} throughout the rest of the Tutorial. (GBBopen's REPL commands are summarized in the \xsitelink{GBBopen REPL Commands Quick Reference Card}{http://GBBopen.org/downloads/command-refcard.pdf}, available on the GBBopen web site.) \subsubsection*{Beyond \code{\var{\/}/initiate.lisp}} Although loading GBBopen's \nobr{\code{\var{\/}/initiate.lisp}} file is a very easy way to get started with GBBopen, setting up your computing environment for serious GBBopen development requires only a few moments that will be recouped almost immediately. The steps needed to set everything up will be detailed soon in the \reflink{Enhancing Your Development Environment exercise}{sec:environment}. %% ======================================================================== %% Creating a Unit Instance \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-creating-a-unit-instance} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \section{Creating a Unit Instance} \label{sec:unit-instance}% Blackboard objects in GBBopen are called \textit{unit instances}. Each unit instance is a member of a \textit{unit class}. The unit class defines the structure of all instances of the unit class, such as the slots in each unit instance. At a more precise Common Lisp level, every GBBopen unit instance is member of a (possibly non-direct) subclass of GBBopen's unit class, \nobr{\code{standard-unit-instance}}, which is itself a subclass of Common Lisp's \nobr{\code{standard-object}} class. Even more technically, the metaclass of each GBBopen unit class is an instance of the GBBopen metaclass, \nobr{\code{standard-unit-class}}, which is a subclass of Common Lisp's \nobr{\code{standard-class}} metaclass. In other words, GBBopen classes and unit instances fit naturally into the CLOS (the Common Lisp Object System) and MOP (\xsitelink{Metaobject Protocol}{http://www.lisp.org/mop/index.html}) hierarchies. Fortunately, understanding such details is not required to put GBBopen to use. The word ``unit'' is used in GBBopen because it has a very neutral meaning that is unlikely to be confused with programming terminology, such as ``object,'' or with application-domain concepts, such as ``location'' or ``goal''. When someone refers to ``unit instances,'' it is clear that they are talking about GBBopen's blackboard objects. Thus, ``unit instance'' always refers to an instance of a particular unit class, and ``unit class'' refers to a class of unit instances. So let's start using them$\ldots$ \fndocrule This exercise shows you how to: \begin{tightitemize} \item Define a unit class \item Create a unit instance \item Display a description of a unit instance \item Find a unit instance by its name \item Read and write slot values \end{tightitemize} \fndocrule \subsection*{Prerequisites} \bfindexit{compile-module}% \codeindexit{in-package}% If you ended the Common Lisp session used in the last exercise, begin a new session and evaluate the following forms: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{(load "\var{\/}/initiate.lisp")} \textrm{\ldots{}} cl-user> \textcolor{black}{:gbbopen-user} \textrm{\ldots{}} gbbopen-user>} \end{example} \indexit{clbuild}% \indexit{Quicklisp}% \begin{notebox}{ASDF, clbuild, and Quicklisp users} Remember to \nobr{\code{(asdf:operate 'asdf:load-op :gbbbopen)}} or \nobr{\code{(require :gbbopen)}} in place of loading \nobr{\code{\var{\/}/initiate.lisp}}. \end{notebox} \subsection*{Step 1: Define the \code{location} unit class} \index{unit class!defining}% \bfindex{define-unit-class}% % We begin by defining a unit class \nobr{\code{location}} that has two slots, named \code{x} and \code{y}. % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(define-unit-class location () (x y))} # gbbopen-user>} \end{example} This unit-class definition instructs GBBopen to: % \begin{tightitemize} \item Define initialization argument \code{:x} for the \code{x} slot and \code{:y} for the \code{y} slot. You will use these initialization arguments in the next step, to specify initial slot values for an instance. \item Define reader and writer methods for the \nobr{\code{x-of}} and \nobr{\code{y-of}} generic slot-accessor functions, used to reading and modifying the \code{x} and \code{y} slot values. \end{tightitemize} \subsection*{Step 2: Create a unit instance} \index{unit instance!creating}% \codeindexit{defvar}% Next, you can create a \textit{unit instance\/} for the \nobr{\code{location}} unit class. To simplify access to the unit instance during subsequent activities, first define a global variable called \code{ui} by entering the following form: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(defvar ui)} ui gbbopen-user>} \end{example} \bfindex{make-instance}% % Now, create a unit instance and assign it to the variable \code{ui} by entering the form: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(setf ui (make-instance 'location :x 40 :y 60))} # gbbopen-user>} \end{example} This creates a unit instance of the \nobr{\code{location}} class, initializes the instance's \code{x} and \code{y} slots with the values specified by the \code{:x} and \code{:y} initialization arguments. The created unit instance is then assigned to the variable \code{ui}. \subsection*{Step 3: Display a description of the unit instance} \index{unit instance!displaying a description of}% \bfindex{describe-instance}% % Now, display a description of the unit instance by using GBBopen's \nobr{\textbf{describe-instance}} function. Enter the following form: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-instance ui)} Location # Instance name: 1 Space instances: None Dimensional values: None Non-link slots: x: 40 y: 60 Link slots: None gbbopen-user>} \end{example} \bfindex{instance-name-of}% Note that you didn't specify a name for the unit instance when you created it. In fact, there is often no natural reason to name each unit instance you create. By default, GBBopen names unit instances by giving it a sequentially increasing number. (For example, the unit instance you just created is named \code{1}.) GBBopen requires that each instance of a unit class is uniquely named within the class and the accessor \nobr{\textbf{instance-name-of}} can be used to access this name: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(instance-name-of ui)} 1 gbbopen-user>} \end{example} \subsection*{Step 4: Find a unit instance by its name} \index{unit instance!finding!by name}% \bfindex{find-instance-by-name}% % You can look up a particular unit instance by its name. For example, to find the \nobr{\code{location}} \code{1} unit instance by name, enter the form: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instance-by-name 1 'location)} # gbbopen-user>} \end{example} Of course, we assigned the \nobr{\code{location}} unit instance to the global variable \code{ui}, but it is nice to know that we can always find our unit instance using its name. \subsection*{Step 5: Change the \code{x} slot value} \index{unit instance!slot!getting value of}% \index{unit instance!slot!setting value of}% \index{slot!getting value of}% \index{slot!setting value of}% \index{x-of@\code{x-of} slot reader method|itidx}% \index{(setf x-of)@\code{(setf x-of)} slot writer method|itidx}% \codeindexit{(setf x-of)}% % Use the \code{x-of} reader and writer methods GBBopen defined for the \code{x} slot (in Step 3 above) to get the value of the \code{x} slot of the unit instance: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(x-of ui)} 40 gbbopen-user>} \end{example} % change the slot value to 50: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(setf (x-of ui) 50)} 50 gbbopen-user>} \end{example} % and then get the (new) value of the \code{x} slot: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(x-of ui)} 50 gbbopen-user>} \end{example} \bfindexit{describe-instance}% Display the description of the \nobr{\code{location}} unit instance again, observing the changed \code{x} slot value: % \begin{example} \W\supp \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-instance ui)} Location # Instance name: 1 Space instances: None Dimensional values: None Non-link slots: x: 50 y: 60 Link slots: None gbbopen-user>} \end{example} %% ======================================================================== %% Creating a Space Instance \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-creating-a-space-instance} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \section{Creating a Space Instance} \label{sec:space-instance}% In GBBopen, \textit{space instances} serve as containers for unit instances. As we saw in the last exercise, unit instances need not be placed in a space instance. They are perfectly useful on their own. In this exercise we will create a space instance and add and remove unit instances from it. A unit instance can also be contained in multiple space instances at the same time. Although containment ``in'' a space instance is a more technically correct phrase, historically developers talk of unit instances being ``on'' a space instance or of adding a unit instance ``to'' a space instance. Whichever preposition is used, the meaning is the same. As with unit instances, each space instance is a member of a \textit{space class}. The space class defines the structure of all instances of the space class, such as any slots associated with each space instance. Unlike unit instances, however, the standard GBBopen space class, \nobr{\code{standard-space-instance}}, is often sufficient for most applications. Therefore, space-instance subclasses rarely need to be defined. Space instances can be organized into hierarchical structures, much like the directories in a hierarchical file system. A useful, but not exact, analogy is to think of unit instances as being similar to files and space instances as similar to directories. Stretching this analogy one step further, adding a unit instance to a space instance is akin to creating a symbolic link to a file in a directory. Removing the unit instance from the space instance is like removing the symbolic link: the unit instance itself, like the file, is not deleted by the removal. \fndocrule This exercise shows you how to: \begin{tightitemize} \item Create a space instance \item Find a space instance by its path \item Add and remove a unit instance from the space instance \item Display a description of the blackboard repository \item Find unit instances on a space instance \end{tightitemize} \fndocrule \subsection*{Prerequisites} \bfindexit{compile-module}% \codeindexit{in-package}% \codeindexit{defparameter}% If you ended the Common Lisp session used in the last exercise, begin a new session and evaluate the following forms: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{(load "\var{\/}/initiate.lisp")} \textrm{\ldots{}} cl-user> \textcolor{black}{:gbbopen-user} \textrm{\ldots{}} gbbopen-user> \textcolor{black}{(define-unit-class location () (x y))} # gbbopen-user> \textcolor{black}{(defparameter ui (make-instance 'location :x 40 :y 60))} ui gbbopen-user>} \end{example} \indexit{clbuild}% \indexit{Quicklisp}% \begin{notebox}{ASDF, clbuild, and Quicklisp users} Remember to \nobr{\code{(asdf:operate 'asdf:load-op :gbbbopen)}} or \nobr{\code{(require :gbbopen)}} in place of loading \nobr{\code{\var{\/}/initiate.lisp}}. \end{notebox} \subsection*{Step 1: Create a space instance} \index{space instance!creating}% \codeindexit{defvar}% Create a \textit{space instance\/} named \nobr{\code{known-world}}. To simplify access to the space instance during subsequent activities, first define a global variable called \code{si} by entering the following form: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(defvar si)} si gbbopen-user>} \end{example} \bfindex{make-space-instance}% % Now, create the space instance and assign it to the variable \code{si} by entering the form: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(setf si (make-space-instance '(known-world)))} # gbbopen-user>} \end{example} The argument to the \nobr{\textbf{make-space-instance}} is the ``path'' to be used for the created space instance. A \textit{space-instance path} is the complete list of space-instance names, starting with the name of the most distant indirect parent, that uniquely identifies a space instance in the blackboard repository. Our \nobr{\code{known-world}} space instance does not have a parent, so its path is simply \nobr{\code{(known-world)}}. \subsection*{Step 2: Display a description of the blackboard repository} \index{blackboard repository, describing}% \bfindex{describe-blackboard-repository}% % Display a description of the blackboard repository, which now contains the \nobr{\code{known-world}} space instance: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-blackboard-repository)} Space Instance Contents -------------- -------- known-world Empty Unit Class Instances ---------- --------- location 1 standard-space-instance 1 --------- 2 instances gbbopen-user>} \end{example} The description indicates that: \begin{tightitemize} \item One space instance, \code{known-world}, exists in the repository \item The \code{known-world} space instance is empty; there are no unit instances stored in it \item The \nobr{\code{location}} unit instance exists, but it does not reside on any space instance \item There is one \code{standard-space-instance}, our \nobr{\code{known-world}} space instance, which also does not reside on any space instance \end{tightitemize} \subsection*{Step 3: Find a space instance by its path} \index{space instance!finding by path}% \bfindex{find-space-instance-by-path}% % You can look up a particular space instance by its space-instance path: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-space-instance-by-path '(known-world))} # gbbopen-user>} \end{example} We assigned the \code{known-world} space instance to the global variable \code{si}, but it is nice to know that we can always find it again using its path. \subsection*{Step 4: Add the unit instance to the space instance} \index{unit instance!adding to a space instance}% \bfindex{add-instance-to-space-instance}% % Now, add the \nobr{\code{location}} unit instance to the space instance. Enter the following form: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(add-instance-to-space-instance ui si)} # gbbopen-user>} \end{example} \subsection*{Step 5: Again, display the blackboard-repository description} \bfindexit{describe-blackboard-repository}% Display the description of the blackboard repository again: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-blackboard-repository)} Space Instance Contents -------------- -------- known-world 1 instance (1 location) Unit Class Instances ---------- --------- location 1 standard-space-instance 1 --------- 2 instances gbbopen-user>} \end{example} This time the description indicates that the \nobr{\code{known-world}} space instance has one instance of the \nobr{\code{location}} unit class stored on it. \subsection*{Step 6: Display the description of the unit instance} \bfindexit{describe-instance}% Display the description of the \nobr{\code{location}} unit instance once again, observing the change in the space instances from the last exercise: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-instance ui)} Location # Instance name: 1 Space instances: ((known-world)) Dimensional values: None Non-link slots: x: 50 y: 60 Link slots: None gbbopen-user>} \end{example} \subsection*{Step 7: Find the unit instance on the space instance} \index{unit instance!finding!on a space instance}% \bfindex{find-instances}% % Now that the \nobr{\code{location}} unit instance is on the \nobr{\code{known-world}} space instance, we can find it on the space instance. The form is as follows, where \code{:all} is a very basic retrieval pattern specifying that all unit instances on the \nobr{\code{known-world}} space instance are to be returned: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) :all)} (#) gbbopen-user>} \end{example} % The list of found unit instances is returned. (In this case, there is only one in the list.) This is a very simple retrieval; however, GBBopen can perform extremely complex searches as well. We will use more complex retrieval patterns in upcoming exercises. \subsection*{Step 8: Add another unit instance to the space instance} \bfindexit{make-instance}% \bfindexit{add-instance-to-space-instance}% Create a second instance of the \nobr{\code{location}} unit class, with \code{x} and \code{y} slot values 80 and 90, respectively, and add it to the \nobr{\code{known-world}} space instance: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(add-instance-to-space-instance (make-instance 'location :x 80 :y 90) si)} # gbbopen-user>} \end{example} \bfindexit{find-instance-by-name}% This time we did not assign the new \nobr{\nobr{\code{location}}} unit instance to a global variable but, as before, we can find the unit instance by its name: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instance-by-name 2 'location)} # gbbopen-user>} \end{example} \bfindexit{find-instance-by-name}% \bfindexit{find-instances}% As you would expect, we can retrieve both \nobr{\code{location}} unit instances from the \nobr{\code{known-world}} space instance using \nobr{\textbf{find-instances}}: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) :all)} (# #) gbbopen-user>} \end{example} \subsection*{Step 9: Again, display the blackboard-repository description} \bfindexit{describe-blackboard-repository}% Display the description of the blackboard repository again: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-blackboard-repository)} Space Instance Contents -------------- -------- known-world 2 instances (2 location) Unit Class Instances ---------- --------- location 2 standard-space-instance 1 --------- 3 instances gbbopen-user>} \end{example} This time the description indicates that the \nobr{\code{known-world}} space instance has both instances of the \nobr{\code{location}} unit class stored on it. \subsection*{Step 10: Remove a unit instance from the space instance} \index{unit instance!removing from a space instance}% \bfindexit{remove-instance-from-space-instance}% Now, remove the first \nobr{\code{location}} unit instance from the \nobr{\code{known-world}} space instance. Enter the following form: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(remove-instance-from-space-instance ui si)} # gbbopen-user>} \end{example} % \bfindexit{find-instances}% As you would expect, only the second \nobr{\code{location}} unit instance remains on the \nobr{\code{known-world}} space instance: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) :all)} (#) gbbopen-user>} \end{example} % and describing the \nobr{\code{location}} unit instance confirms this: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-instance ui)} Location # Instance name: 1 Space instances: None Dimensional values: None Non-link slots: x: 50 y: 60 Link slots: None gbbopen-user>} \end{example} %% ======================================================================== %% Deleting Instances \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-deleting-instances} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \section{Deleting Instances} \label{sec:deleting-instance}% GBBopen retains created unit and space instances until they are explicitly deleted. This behavior is important to blackboard applications, where shared information in the form of unit instances remains available until a decision is made to remove them. In this exercise, we explore some implications of deleting unit and space instances. \fndocrule This exercise shows you how to: \begin{tightitemize} \item Delete a unit instance \item Create a space-instance hierarchy \item Delete a space instance \item Delete all unit and space instances from the blackboard repository \end{tightitemize} \fndocrule \subsection*{Prerequisites} \bfindexit{compile-module}% \codeindexit{in-package}% \codeindexit{defparameter}% If you ended the Common Lisp session used in the last exercise, begin a new session and evaluate the following forms: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{(load "\var{\/}/initiate.lisp")} \textrm{\ldots{}} cl-user> \textcolor{black}{:gbbopen-user} \textrm{\ldots{}} gbbopen-user> \textcolor{black}{(define-unit-class location () (x y))} # gbbopen-user> \textcolor{black}{(defparameter ui (make-instance 'location :x 50 :y 60))} ui gbbopen-user> \textcolor{black}{(defparameter si (make-space-instance '(known-world)))} si gbbopen-user> \textcolor{black}{(add-instance-to-space-instance (make-instance 'location :x 80 :y 90) si)} # gbbopen-user>} \end{example} \indexit{clbuild}% \indexit{Quicklisp}% \begin{notebox}{ASDF, clbuild, and Quicklisp users} Remember to \nobr{\code{(asdf:operate 'asdf:load-op :gbbbopen)}} or \nobr{\code{(require :gbbopen)}} in place of loading \nobr{\code{\var{\/}/initiate.lisp}}. \end{notebox} \subsection*{Step 1: Create a few more unit instances} Just to review, we created two \nobr{\code{location}} unit instances. The unit instance named \code{1} is no longer on the \nobr{\code{known-world}} space instance, but it is still assigned to the global variable \code{ui}: % \codeindexit{print}% \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{ui} # gbbopen-user>} \end{example} % The \nobr{\code{location}} unit instance named \code{2} is the only \nobr{\code{location}} unit instance on the \nobr{\code{known-world}} space instance: % \bfindexit{find-instances}% \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) :all)} (#) gbbopen-user>} \end{example} Now, let's create five more \nobr{\code{location}} unit instances. Enter: % \bfindexit{make-instance}% \codeindexit{dotimes}% \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(dotimes (i 5) (make-instance 'location))} nil gbbopen-user>} \end{example} % Note that we did not specify \code{x} and \code{y} slot values for these new unit instances. We will explore the implications of this shortly. \subsection*{Step 2: Apply a function to all instances of a unit class} \index{unit class!applying a function to all instances of}% \index{unit instance!applying a function to all instances of a class}% \index{applying a function to all instances of a class}% \bfindexit{find-instance-by-name}% % We did not assign the new \nobr{\code{location}} unit instances to global variables or add them to the \nobr{\code{known-world}} space instance. Can we still reference them? If we know the names of the new unit instances, we can use the \nobr{\textbf{find-instance-by-name}} function that we learned earlier. For example: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instance-by-name 5 'location)} # gbbopen-user>} \end{example} \bfindex{map-instances-of-class}% \codeindexit{print}% % It is often useful to perform some action on all instances of a unit class. GBBopen provides a \textit{mapping function}, or ``iterator,'' that repeatedly calls a function with each instance of a unit class as the argument to the function. For example: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(map-instances-of-class #'print 'location)} # # # # # # # nil gbbopen-user>} \end{example} % displays each of our \nobr{\code{location}} unit instances. Note that the exact order that \nobr{\code{location}} unit instances are supplied to the \code{print} function may differ from the above example in your Common Lisp implementation. \bfindexit{find-instances}% \bfindexit{map-instances-of-class}% Currently, there is only one \nobr{\code{location}} unit instance on the \nobr{\code{known-world}} space instance: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) :all)} (#) gbbopen-user>} \end{example} Let's use \nobr{\textbf{map-instances-of-class}} to add all the \nobr{\code{location}} unit instances to the \nobr{\code{known-world}}: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(map-instances-of-class #'(lambda (instance) (add-instance-to-space-instance instance si)) 'location)} Warning: In add-instance-to-space-instance: # is already on space instance #. nil gbbopen-user>} \end{example} % GBBopen warns us that the \nobr{\code{location}} \code{2} unit instance is already on the \nobr{\code{known-world}} and adds all the other \nobr{\code{location}} instances. We can verify this by using \nobr{\textbf{find-instances}}: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) :all)} (# # # # # # #) gbbopen-user>} \end{example} \bfindexit{map-instances-of-class}% For those who prefer a more iterative programming style, GBBopen provides a \texttt{dolist}-style macro, \nobr{\textbf{do-instances-of-class}}, as an alternative to \nobr{\textbf{map-instances-of-class}}. So, to add all the \nobr{\code{location}} unit instances to the \nobr{\code{known-world}}, we could have chosen to use the form: % \W\supp \begin{example} \textcolor{darkergray}{% (do-instances-of-class (instance 'location) (add-instance-to-space-instance instance si))} \end{example} % instead of the \nobr{\textbf{map-instances-of-class}} version. As with things stylistic, the choice is yours. \subsection*{Step 3: Delete a unit instance} \index{unit instance!deleting}% \bfindex{delete-instance}% % Let's delete the first \nobr{\code{location}} unit instance that we created: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(delete-instance ui)} # gbbopen-user>} \end{example} % Note that the displayed representation indicates that the unit instance has been deleted. \bfindexit{find-instance-by-name}% \bfindexit{map-instances-of-class}% \codeindexit{print}% % We can no longer find the deleted unit instance by its name: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instance-by-name 1 'location)} nil gbbopen-user>} \end{example} % and it is no longer included in a \nobr{\textbf{map-instances-of-class}} iteration: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(map-instances-of-class #'print 'location)} # # # # # # nil gbbopen-user>} \end{example} % and it is no longer on the \nobr{\code{known-world}} space instance: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) :all)} (# # # # # #) gbbopen-user>} \end{example} \bfindexit{add-instance-to-space-instance}% However, the deleted \nobr{\code{location}} unit instance is still assigned to the \code{ui} global variable: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{ui} # gbbopen-user>} \end{example} % and that can lead to problems. Let's try to place the deleted \nobr{\code{location}} unit instance on the \nobr{\code{known-world}} space instance: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(add-instance-to-space-instance ui si)} Error: No methods applicable for generic function # with args (# #) of classes (deleted-unit-instance standard-space-instance) gbbopen-user>> \textcolor{black}{:a} gbbopen-user>} \end{example} Most of GBBopen's operations signal an error if they are given a deleted unit instance. This is because \nobr{\textbf{delete-instance}} changes the class of the deleted unit instance to \nobr{\code{deleted-unit-instance}} which, despite its name, is not a \nobr{\code{standard-unit-instance}}. We cannot use \nobr{\textbf{describe-instance}} to verify this, because the deleted unit instance is no longer a unit instance, but we can use Common Lisp's \code{describe} function: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe ui)} # is an instance of #: The following slots have :instance allocation: instance-name 1 original-class # gbbopen-user>} \end{example} % Note that the slots that we defined for \nobr{\code{location}} unit instances are not present in the \nobr{\code{deleted-unit-instance}} object. A deleted unit instance has only two slots, \nobr{\code{instance-name}} and \nobr{\code{original-class}}, which are set when the class of the deleted unit instance is changed to \nobr{\code{deleted-unit-instance}}. Several GBBopen operations on a deleted unit instance do not signal errors. In particular: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(instance-name-of ui)} 1 gbbopen-user>} \end{example} % \bfindexit{instance-name-of}% \bfindex{original-class-of}% The value returned by \nobr{\textbf{instance-name-of}}, along with that by \nobr{\textbf{original-class-of}}, can be used to identify the unit instance that, when deleted, became the \nobr{\code{deleted-unit-instance}}. \bfindex{instance-deleted-p}% % Typically, blackboard applications obtain unit instances from the blackboard repository (or as we will see in \texorhtml{Exercise~\ref{sec:control-shell}}{the \link{Using a Control Shell}{sec:control-shell} exercise}, % as an event argument) rather than maintaining references to them in variables. This limits the possibility of retaining a deleted unit instance and performing GBBopen operations on it. The deletion status of a unit instance can be determined using the \nobr{\textbf{instance-deleted-p}} predicate: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(instance-deleted-p ui)} t gbbopen-user>} \end{example} \subsection*{Step 4: Create a simple space-instance hierarchy} \index{space instance!hierarchy, creating}% \bfindexit{make-space-instance}% In the last exercise, we noted that space instances can be organized in hierarchical structures. To illustrate this, let's create a few more space instances: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(make-space-instance '(known-world my-town))} # gbbopen-user> \textcolor{black}{(make-space-instance '(known-world my-town east-side))} # gbbopen-user> \textcolor{black}{(make-space-instance '(known-world my-town west-side))} # gbbopen-user>} \end{example} \bfindexit{describe-blackboard-repository}% Recall that the \nobr{\var{space-instance-path\/}} argument to \nobr{\textbf{make-space-instance}} is the complete list of space-instance names, starting with the name of the most distant indirect parent. So, the \nobr{\code{my-town}} space instance has the \nobr{\code{known-world}} as it's parent and the \nobr{\code{east-side}} and \nobr{\code{west-side}} as children. We can use \nobr{\textbf{describe-blackboard-repository}} to see this organization: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-blackboard-repository)} Space Instance Contents -------------- -------- known-world 6 instances (6 location) my-town Empty east-side Empty west-side Empty Unit Class Instances ---------- --------- location 6 standard-space-instance 4 --------- 10 instances gbbopen-user>} \end{example} \bfindex{standard-space-instance}% \index{space instance!is a unit instance}% Observe from the above description that child space instances that we created are not placed on their parent. Unlike the directories in a file system in the analogy that we presented in the last exercise, in GBBopen a space-instance hierarchy is orthogonal to containment. In fact, space instances are actually unit instances (of the class \nobr{\textbf{standard-space-instance}}, by default) and a space instance can be placed on other space instances---or even on itself. Typical blackboard applications do not involve using space instances as unit instances, but this property of space instances is very powerful when it is needed. \bfindex{parent-of}% \bfindex{children-of}% The functions \nobr{\textbf{parent-of}} and \nobr{\textbf{children-of}} are provided to traverse the space-instance hierarchy. For example: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(children-of (find-space-instance-by-path '(known-world my-town)))} (# #) gbbopen-user>} \end{example} \subsection*{Step 5: Add a unit instance to multiple space instances} \bfindexit{add-instance-to-space-instance}% \bfindexit{find-instance-by-name}% Now, let's add \nobr{\code{location}} \code{2} to the \nobr{\code{my-town}} and \nobr{\code{east-side}} space instances: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(let ((location-2 (find-instance-by-name 2 'location))) (add-instance-to-space-instance location-2 '(known-world my-town)) (add-instance-to-space-instance location-2 '(known-world my-town east-side)))} # gbbopen-user>} \end{example} % \bfindexit{find-instance-by-name}% \bfindexit{describe-blackboard-repository}% As we expect, \nobr{\code{location}} \code{2} is now on three space instances: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-instance (find-instance-by-name 2 'location))} Location # Instance name: 2 Space instances: ((known-world my-town east-side) (known-world my-town) (known-world)) Dimensional values: None Non-link slots: x: 80 y: 90 Link slots: None gbbopen-user>} \end{example} % and the description of the blackboard repository is: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-blackboard-repository)} Space Instance Contents -------------- -------- known-world 6 instances (6 location) my-town 1 instance (1 location) east-side 1 instance (1 location) west-side Empty Unit Class Instances ---------- --------- location 6 standard-space-instance 4 --------- 10 instances gbbopen-user>} \end{example} Placing a unit instance on multiple space instances is useful when each space instance represents a different view of unit instances. In this case, \nobr{\code{location}} \code{2} is in the \nobr{\code{known-world}}, in \nobr{\code{my-town}}, and on the \nobr{\code{east-side}}: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world my-town) :all)} (#) gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world my-town east-side) :all)} (#) gbbopen-user>} \end{example} \subsection*{Step 6: Delete a space instance} \index{space instance!deleting}% \bfindex{delete-space-instance}% % Now let's delete some of what we just created. Let's delete the \nobr{\code{my-town}} space instance: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(delete-space-instance '(known-world my-town))} # gbbopen-user>} \end{example} % \bfindexit{describe-blackboard-repository}% and describe the blackboard repository: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-blackboard-repository)} Space Instance Contents -------------- -------- known-world 6 instances (6 location) Unit Class Instances ---------- --------- location 6 standard-space-instance 1 --------- 7 instances gbbopen-user>} \end{example} % Notice that GBBopen has also deleted all the child space instances of \nobr{\code{my-town}}: both \nobr{\code{east-side}} and \nobr{\code{west-side}} were also deleted. So, just as with our file-system directory analogy, space-instance deletion is recursive over children and should be performed with caution. \bfindex{map-instances-on-space-instances}% \bfindexit{delete-instance}% % Also note that deleting space instances did not delete any of the unit instances that were stored on them. If we want to delete a space instance \textit{and\/} all the unit instances that are stored on it, we must explicitly delete the unit instances before deleting the space instance. For example, we could do: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> (map-instances-on-space-instances #'delete-instance 'location '(known-world my-town)) nil gbbopen-user>} \end{example} % to delete the unit instances in \nobr{\code{my-town}}. However, we must keep in mind that a unit instance can reside on multiple space instances; so deleting a unit instance that is on other space instances might not be the desired action. \bfindex{do-instances-on-space-instances}% % As is the case with \nobr{\textbf{map-instances-of-class}}, GBBopen provides a \nobr{\textbf{do-instances-on-space-instances}} macro as an alternative to \nobr{\textbf{map-instances-on-space-instances}}. So we could have chosen to use the form: % \W\supp \begin{example} \textcolor{darkergray}{% (do-instances-on-space-instances (instance 'location '(known-world my-town)) (delete-instance instance))} \end{example} % to explicitly delete the \nobr{\code{location}} unit instances in \nobr{\code{my-town}}. \subsection*{Step 7: Delete the Blackboard Repository} \bfindex{delete-blackboard-repository}% \bfindexit{describe-blackboard-repository}% \bfindexit{map-instances-of-class}% If we really want to get rid of all our unit and space instances, we can use the \nobr{\textbf{delete-blackboard-repository}} function: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(delete-blackboard-repository)} t gbbopen-user> \textcolor{black}{(describe-blackboard-repository)} There are no space instances in the blackboard repository. gbbopen-user> \textcolor{black}{(map-instances-of-class #'print location)} nil gbbopen-user>} \end{example} \bfindexit{make-instance}% Calling \nobr{\textbf{delete-blackboard-repository}} has deleted every space and unit instance, but it has not eliminated our \nobr{\code{location}} unit class definition. Let's create a \nobr{\code{location}} unit instance once again: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(make-instance 'location)} # gbbopen-user>} \end{example} % Note that deleting the blackboard repository also reset the counter for \nobr{\code{location}} instance names, so the created unit instance is again named \code{1}. %% ======================================================================== %% Enhancing Your Development Environment \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-development-environment} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \section{Enhancing Your Development Environment} \label{sec:environment}% Now that you are experienced creating and deleting unit and space instances in GBBopen, we will take a short break before working further on our random-walk application. In the exercises thus far, we have been working directly in Common Lisp's REPL. As our application develops, we want to save our code in files. In this exercise, we will provide recommendations for making your GBBopen and Common Lisp environment more productive. Even if you have already customized your Common Lisp setup, I recommend surveying this exercise for useful GBBopen tips. \fndocrule This exercise shows you how to: \begin{tightitemize} \item Add GBBopen keyword commands to your Common Lisp implementation \item Customize your Common Lisp initialization file \item Set up GBBopen HyperDoc and Common Lisp HyperSpec access \end{tightitemize} \fndocrule \subsection*{Step 1: Autoloading GBBopen's \code{initiate.lisp} file} Thus far, we have entered the forms: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{(load "\var{\/}/initiate.lisp")} \textrm{\ldots{}} cl-user> \textcolor{black}{:gbbopen-user} \textrm{\ldots{}} gbbopen-user>} \end{example} % to compile and load needed GBBopen components and to set the current package to \nobr{\code{:gbbopen-user}}. We can set up our Common Lisp environment so that we can do this (and eventually compile and load our random-walk application) by issuing only a single REPL command. \subsubsection*{Customizing your Common Lisp initialization file} I'm lazy and would rather not have to explicitly load the \nobr{\code{\var{\/}/initiate.lisp}} file each time I start a new Common Lisp session. So, I have my Common Lisp's initialization file load it for me. To make it easy to similarly customize different Common Lisp implementations, I use the following strategy: \begin{enumerate} \item I create a file, \nobr{\code{shared-init.lisp}}, in my home directory, containing the following: % \W\supp\notpretop \begin{example} (in-package :common-lisp-user) ;; My personal preferences. Note: Allegro CL requires ;; tpl:setq-default during initialization to retain changes ;; to these global variables (done in .clinit.cl): (setf *print-case* ':downcase) (setf *compile-verbose* 't) (setf *load-verbose* 't) (let ((defaults *load-truename*)) (load (make-pathname ;; where GBBopen is installed: :directory '(:absolute \textcolor{red}{"usr" "local" "gbbopen"}) :name "initiate" :type "lisp" :defaults defaults))) \end{example} The \code{:directory} argument to \nobr{\code{make-pathname}} is a Common Lisp \textit{absolute pathname} that is portable across all operating systems. I installed my GBBopen in \nobr{\code{/usr/local/gbbopen/}}, and I specified this with the \code{"usr"}, \code{"local"}, and \code{"gbbopen"} elements in the \code{:directory} argument. Change these elements as appropriate for the location of your GBBopen installation. \item For each Common Lisp implementation that I use, I create a personal initialization file that performs any implementation-specific initializations and then loads \nobr{\code{shared-init.lisp}}. For example, here is the \code{.clisprc} initialization file that I use for \xsitelink{CLISP}{http://www.clisp.org/}: % \W\supp\notpretop \begin{example} (in-package :common-lisp-user) ;; enable maximum ANSI compliance: (setf custom:*ansi* 't) (let ((defaults *load-truename*)) (load (make-pathname :name "shared-init" :type "lisp" :defaults defaults))) \end{example} \xsitelink{SBCL}{http://sbcl.sourceforge.net} has a very strict interpretation of \nobr{\code{*load-truename*}} semantics, so my \code{.sbclrc} initialization file is: % \W\supp \begin{example} (in-package :common-lisp-user) ;; *load-truename* returns nil when used in SBCL's .sbclrc ;; initialization file, so use (user-homedir-pathname): (let ((defaults (user-homedir-pathname))) (load (make-pathname :name "shared-init" :type "lisp" :defaults defaults))) \end{example} Here is my \nobr{\code{.clinit.cl}} initialization file for \xsitelink{Allegro CL}{http://franz.com/products/allegrocl/}: % \W\supp \begin{example} (in-package :common-lisp-user) ;; Allegro CL requires tpl:setq-default during initialization ;; to retain changes to these global variables: (tpl:setq-default *print-case* ':downcase) (tpl:setq-default *compile-verbose* 't) (tpl:setq-default *load-verbose* 't) (let ((defaults *load-truename*)) (load (make-pathname :name "shared-init" :type "lisp" :defaults defaults))) \end{example} The initialization file names for various Common Lisp implementations are: % \begin{tabular}{llll} ~~~~~~ & \xsitelink{ABCL}{http://common-lisp.net/project/armedbear/} & ~~~~~~ & \code{.abclrc} \\ & \xsitelink{Allegro CL}{http://franz.com/products/allegrocl/} & & \code{.clinit.cl} \\ & \xsitelink{CLISP}{http://www.clisp.org/} & & \code{.clisprc} \\ & \xsitelink{Clozure CL}{http://trac.clozure.com/ccl} & & \code{ccl-init.lisp} \\ & \xsitelink{CMUCL}{http://www.cons.org/cmucl/} & & \code{init.lisp} \\ & \xsitelink{Digitool MCL}{http://www.digitool.com} & & (see below) \\ & \xsitelink{ECL}{http://common-lisp.net/project/ecl/} & & \code{.eclrc} \\ & \xsitelink{LispWorks}{http://www.lispworks.com} & & \code{.lispworks} \\ & \xsitelink{SBCL}{http://sbcl.sourceforge.net} & & \code{.sbclrc} \\ & \xsitelink{Scieneer CL}{http://www.scieneer.com/scl/} & & \code{init.lisp} \\ & \xsitelink{XCL}{http://armedbear.org/} & & \code{.xclrc} \\ \end{tabular} \xsitelink{Digitool MCL}{http://www.digitool.com} does not look for an initialization file in the user's home directory. Instead it loads the file \nobr{\code{init.lisp}} in it's installation directory. One approach is to have that file load a personal initialization file, say \nobr{\code{mcl-init.lisp}}, from the user's home directory, if a \nobr{\code{mcl-init.lisp}} file is present. The Personal Edition of \xsitelink{LispWorks}{http://www.lispworks.com} does not load initialization files, requiring you to manually load your \code{.lispworks} file each time you start up \xsitelink{LispWorks}{http://www.lispworks.com}. The interpretation of where a user's ``home'' directory is located is inconsistent on Windows. Ideally, the ``home directory'' location used for the Common Lisp implementation's initialization file and the result of the Common Lisp function \nobr{\code{user-homedir-pathname}} should be consistent. Then, by using the \nobr{\code{shared-init.lisp}} scheme, you only need to determine where the initialization file should be for each Common Lisp implementation that you use, and then you can have those implementation-specific initialization files load your \nobr{\code{shared-init.lisp}} file from whichever directory \underline{you} deem as your ``home'' directory. \label{ref:your-homedir}% Here is a quick way to have Common Lisp tell you where it thinks your ``home'' directory is located: % \W\supp \begin{example} \textcolor{darkergray}{% cl-user> \textcolor{black}{(not (princ (truename (user-homedir-pathname))))} C:\bkslash{}Documents and Settings\bkslash{}corkill\bkslash nil cl-user>} \end{example} % Alternatively, loading GBBopen's \nobr{\code{\var{\/}/initiate.lisp}} file will display the ``home'' directory. We will refer to this directory from now on as your ``homedir'' directory. \W\\~\\ \item With this setup in place, all that is needed to use GBBopen is to start up a Common Lisp and type a GBBopen command, such as \nobr{\code{:gbbopen-user}}. My fingers thank me! \end{enumerate} \subsection*{Step 2: Set up HyperDoc and HyperSpec access} \codeindex{browse-hyperdoc.el}% If you are using \xsitelink{Emacs}{http://www.gnu.org/software/emacs/emacs.html} in your Common Lisp development environment, you can make it easy to bring up appropriate Common Lisp and GBBopen documentation in your browser. The file \nobr{\code{\var{\/}/browse-hyperdoc.el}} defines an interactive Emacs command named \nobr{\code{browse-hyperdoc}} and binds it to \nobr{\code{META-?}} (on most keyboards, \nobr{\code{META-?}} means pressing both \code{Alt} and \code{?} keys at the same time) . To enable this Emacs command, add a command to load \nobr{\code{\var{\/}/browse-hyperdoc.el}} in your \code{.emacs} initialization file: % \W\supp \begin{example} ;; GBBopen hyperdoc (where GBBopen was installed): (load "\var{\/}/browse-hyperdoc") \end{example} If there is no \code{.emacs} file present in your home directory, simply create one containing the above command. Once again, Windows users need to worry about where Emacs looks for their ``home'' directory. While you are editing your \code{.emacs} file, you might also want to add a command to load GBBopen's Emacs indentation customizations: % \W\supp \begin{example} ;; GBBopen indentations (where GBBopen was installed): (load "\var{\/}/gbbopen-indent") \end{example} % \begin{notebox}{Lispbox users} % If you are using % \xsitelink{\textit{Lispbox}}{http://www.gigamonkeys.com/book/lispbox/}, you % will have to enable loading of the \code{.emacs} initialization file, as it is % disabled in the Lispbox startup file. Edit the Lispbox startup file % (\nobr{\code{lispbox.bat}} on Windows, \nobr{\code{lispbox.sh}} on Unix, and % \nobr{\code{/Applications/Lispbox/Emacs.app/Contents/MacOS/lispbox.sh}} on Mac % OS) and remove the \nobr{\code{-{}-no-init-file}} option from the Emacs % invocation line. % \end{notebox} \subsubsection*{Adding the Common Lisp HyperSpec} The \xsitelink{\nobr{\code{hyperspec.el}}}{http://GBBopen.org/downloads/hyperspec.el} utility is included in the \xsitelink{SLIME}{http://common-lisp.net/project/slime/} and \xsitelink{ILISP}{http://sourceforge.net/projects/ilisp/} distributions. However, if \nobr{\code{hyperspec.el}} is not already part of your Emacs, you can download it and explicitly load it from your \code{.emacs} initialization file. Once \nobr{\code{hyperspec.el}} is present, GBBopen's \nobr{\code{browse-hyperdoc}} Emacs command will automatically defer to the \xsitelink{Common Lisp HyperSpec}{http://www.lispworks.com/documentation/HyperSpec/} when given a non-GBBopen entity. I prefer to download a local copy of the Common Lisp HyperSpec using the \xsitelink{down-loadable archive}{ftp://ftp.lispworks.com/pub/software_tools/reference/HyperSpec-7-0.tar.gz} provided by \xsitelink{LispWorks}{http://www.lispworks.com}, LTD. This allows me to quickly reference the HyperSpec without a network connection. I set the value of \nobr{\code{common-lisp-hyperspec-root}} in my \code{.emacs} initialization file to a URL that points to my local copy of the HyperSpec: % \W\supp \begin{example} (setf common-lisp-hyperspec-root "file:/usr/local/CLHS/") \end{example} \subsubsection*{Non-Emacs access} \bfindex{browse-hyperdoc}% If you are not using an Emacs-based environment, GBBopen provides a Common Lisp function, \nobr{\textbf{browse-hyperdoc}}, that can be used to access GBBopen HyperDoc pages from Common Lisp. GBBopen's \nobr{\code{:os-interface}} module must be loaded to make \nobr{\textbf{browse-hyperdoc}} available. For example: % \W\supp \begin{smallexample} \textcolor{darkergray}{% cl-user> \textcolor{black}{:os-interface} ;; Loading \var{\/}/startup.lisp \textrm{\ldots{}} ;; Loading \var{\/}/\var{\/}/tools/os-interface.fasl gbbopen-tools> \textcolor{black}{(browse-hyperdoc 'define-unit-class)} t gbbopen-tools>} \end{smallexample} % will display the GBBopen HyperDoc page for \textbf{define-unit-class} in your browser. Note that the \nobr{\code{:gbbopen-user}} module requires (and therefore loads) the \nobr{\code{:os-interface}} module, so if you have loaded \nobr{\code{:gbbopen-user}}, you do not need to do anything further in order to call \nobr{\textbf{browse-hyperdoc}}. %% ======================================================================== %% Working Within a File \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-working-within-a-file} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \section{Working Within a File} \label{sec:file}% Now that we've enhanced our GBBopen and Common Lisp development environment, let's begin developing the random-walk application in earnest. \fndocrule This exercise shows you how to: \begin{tightitemize} \item Begin working with files for application development \item Compile and load an application file using the SLIME or ELI environments \end{tightitemize} \fndocrule \subsection*{Step 1: Create the tutorial-example directories} Create a directory to hold the random-walk application. I'm calling mine \code{tutorial}. Next, create a subdirectory in that directory named \code{source}. The reason for doing this will become clear in an \reflink{upcoming exercise}{sec:application}. Here are the shell commands that I used to create my directories: % \W\supp \begin{example} \textcolor{darkergray}{% [~]\$ \textcolor{black}{mkdir tutorial} [~]\$ \textcolor{black}{cd tutorial} [~/tutorial]\$ \textcolor{black}{mkdir source} [~/tutorial]\$} \end{example} \subsection*{Step 2: Create the tutorial-example file} Start up a fresh Common Lisp session and load the \nobr{\code{:gbbopen-user}} module, using the REPL command we set up in the last exercise: % \W\supp \begin{smallexample} \textcolor{darkergray}{% cl-user> \textcolor{black}{:gbbopen-user} ;; Loading \var{\/}/startup.lisp \textrm{\ldots{}} ;; Loading \var{\/}/\var{\/}/gbbopen/gbbopen-user.fasl gbbopen-user>} \end{smallexample} Next, begin editing a new file named \nobr{\code{tutorial-example.lisp}} in the \code{source} subdirectory that you just created. Even if you are more comfortable using another editor, use the editing facilities that are provided by your Common Lisp environment. The development features of a quality Common Lisp environment are well worth the price of learning a new editor. In an Emacs-based environment, such as \xsitelink{SLIME}{http://common-lisp.net/project/slime/} or \xsitelink{Allegro CL}{http://franz.com/products/allegrocl/}'s ELI, typing \nobr{\code{C-x C-f}} will prompt you for the name of a file to editor or create. (We will use Emacs key-binding notation, where \nobr{\code{C-x C-f}} means typing \code{\ctrl{c}} followed by {\ctrl{f}}.) Type the following two forms into the \nobr{\code{tutorial-example.lisp}} file buffer: % \W\supp \begin{example} (in-package :gbbopen-user) (define-unit-class location () (x y)) \end{example} The \nobr{\code{in-package}} form specifies the Common Lisp package that is made current when the file is compiled or loaded. The first form in every Common Lisp source file that you create should begin with an \nobr{\code{in-package}} form. This form is also used by most Common Lisp editing environments to set the package associated with development operations. The \nobr{\textbf{define-unit-class}} definition is the same one we used in \texorhtml{Exercise~\ref{sec:unit-instance}.}{the \link{Creating a Unit instance}{sec:unit-instance} exercise.} Now, save the file. \subsection*{Step 3: Compile and load the tutorial-example file} At this point, we have been using Common Lisp's development environment, but we have not loaded the forms in our file into Common Lisp: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(make-instance 'location)} Error: No class named: location. gbbopen-user>> \textcolor{black}{:a} gbbopen-user>} \end{example} We could use Common Lisp's \nobr{\code{compile-file}} to compile the file and then \code{load} to load the resulting compiled file. For example: % \W\supp \begin{smallexample} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(load (compile-file "~/tutorial/source/tutorial-example.lisp"))} ;; Compiling file ~/tutorial/source/tutorial-example.lisp ;; Loading ~/tutorial/source/tutorial-example.fasl t gbbopen-user>} \end{smallexample} % but you should be able to compile and load the file directly from the editor buffer. In SLIME, the command \nobr{\code{C-c C-k}} will compile and load the file currently being edited. Allegro's ELI interface compile-and-load command is \nobr{\code{C-c C-b}}. Identify and use the compile-and-load-file command in your editing environment. Verify that all is well by creating a \nobr{\code{location}} unit instance in the REPL: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(make-instance 'location)} # gbbopen-user>} \end{example} \bfindexit{delete-blackboard-repository}% Then delete the blackboard repository in preparation for the next exercise: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(delete-blackboard-repository)} t gbbopen-user>} \end{example} %% ======================================================================== %% Adding Dimensions \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-adding-dimensions} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \section{Adding Dimensions} \label{sec:dimensions}% A central concept in GBBopen is \textit{dimensionality}. Dimensional abstraction of space instances, unit instances, and proximity-based retrieval patterns is used to provide a semantically meaningful separation of blackboard-repository storage mechanisms from system and application code. This separation provides flexibility in developing and evolving complex blackboard applications and allows GBBopen to change storage and search strategies and optimizations dynamically. Each space instance can be created as a conceptual hyper-dimensional volume. Unit instances occupy multidimensional extent based on their attributes. The location of a unit instance within the space instance is determined by the intersection of the space instance's dimensionality and the unit instance's dimension values. GBBopen supports three types of dimensions: \begin{tightitemize} \item \textit{ordered}: a real-number line \item \textit{boolean}: true and false values \item \textit{enumerated}: a set of named elements (the set can be either closed or infinite) \end{tightitemize} Determining the dimensionality of space and unit instances is an important part of designing a blackboard application. In this exercise, we will redefine the \nobr{\code{location}} unit class to have two ordered dimensions, \code{x} and \code{y}, that represent Euclidean positions on a two-dimensional plane. Then we will create a two-dimensional \nobr{\code{known-world}} space instance, create some \nobr{\code{location}} unit instances on the \nobr{\code{known-world}}, and retrieve the instances based on their two-dimensional positions. \fndocrule This exercise shows you how to: \begin{tightitemize} \item Add dimensional values to a unit-class definition \item Create a multidimensional space instance \item Retrieve unit instances from a space instance based on their dimensional values \item Compile and load individual forms directly from an Emacs file buffer using the SLIME or ELI environments \end{tightitemize} \fndocrule \subsection*{Prerequisites} \begin{tightitemize} \item The \nobr{\code{tutorial-example.lisp}} file created in the last exercise, containing: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% (in-package :gbbopen-user) (define-unit-class location () (x y))} \end{example} \W\\ \item The \nobr{\code{:gbbopen-user}} module is loaded \end{tightitemize} \subsection*{Step 1: Add dimensions to the \code{location} unit class} Edit your \nobr{\code{tutorial-example.lisp}} file, and change the \nobr{\code{location}} unit-class definition as follows: % \W\supp \begin{example} \textcolor{darkergray}{% (define-unit-class location () (x y) \textcolor{black}{(:dimensional-values (x :point x) (y :point y))})} \end{example} % In this tutorial, we will highlight code additions and changes using a black font. The \nobr{\code{:dimensional-values}} unit-class option specifies that \nobr{\code{location}} unit instances have two ordered dimensions, \code{x} and \code{y}, and that the value of each dimension will be a single numeric value obtained from the slots \code{x} and \code{y}, respectively. Because we chose to use the same name for each dimension and its associated slot value, our \nobr{\code{:dimensional-values}} option might appear to be double-talk. We could have defined our class as: % \W\supp \begin{example} \textcolor{darkergray}{% (define-unit-class location () (x-slot y-slot) (:dimensional-values (x :point x-slot) (y :point y-slot)))} \end{example} % which clarifies the semantics of the \nobr{\code{:dimensional-values}} option. Often, however, it is most convenient to use the same name for a slot and the dimension associated with the slot's value, so we will stick with our original definition. \subsection*{Step 2: Compile and load the new definition} We could compile and load the entire \nobr{\code{tutorial-example.lisp}} file just as we did in the last exercise. However, as we develop our application it can be convenient to compile and load (and debug) each form as we write it. Your Common Lisp development environment should provide this capability. In SLIME, the command to compile the current top-level form is \nobr{\code{C-c C-c}}. In Allegro's ELI, the command is \nobr{\code{C-c C-x}}. Try compiling and loading just the new \nobr{\code{location}} unit-class definition. \subsection*{Step 3: Make a \code{location} unit instance} \bfindexit{make-instance}% \codeindexit{defparameter}% Let's test our new \nobr{\code{location}} unit class definition by making an instance. Enter the following form in the REPL: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(defparameter ui (make-instance 'location :x 40 :y 60))} ui gbbopen-user>} \end{example} % \bfindexit{describe-instance}% and display its description: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-instance ui)} Location # Instance name: 1 Space instances: None Dimensional values: x: 40 y: 60 Non-link slots: x: 40 y: 60 Link slots: None gbbopen-user>} \end{example} % Note the dimensional values for the \code{x} and \code{y} dimensions. \subsection*{Step 4: Make the \code{known-world} space instance} \bfindexit{make-space-instance}% Create the \nobr{\code{known-world}} space instance by evaluating: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(defparameter si (make-space-instance '(known-world)))} si gbbopen-user>} \end{example} \subsection*{Step 5: Add the unit instance to the space instance} \bfindexit{add-instance-to-space-instance}% Now, add the \nobr{\code{location}} unit instance to the space instance: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(add-instance-to-space-instance ui si)} Warning: In add-instance-to-space-instance: # does not share any dimensions with space instance #. # gbbopen-user>} \end{example} \bfindexit{describe-blackboard-repository}% GBBopen has warned us that our \nobr{\code{location}} unit instance does not have any dimensions in common with the \nobr{\code{known-world}} space instance (because we didn't specify any dimensions for the \nobr{\code{known-world}}). GBBopen dutifully added the unit instance, as shown by \nobr{\textbf{describe-blackboard-repository}}: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-blackboard-repository)} Space Instance Contents -------------- -------- known-world 1 instance (1 location) Unit Class Instances ---------- --------- location 1 standard-space-instance 1 --------- 2 instances gbbopen-user>} \end{example} % but we cannot perform dimension-based retrieval of our \nobr{\code{location}} unit instance on the \nobr{\code{known-world}}. \subsection*{Step 6: Create a dimensioned \code{known-world}} \bfindexit{delete-space-instance}% \bfindexit{make-space-instance}% % Let's delete the \nobr{\code{known-world}} and create another one---this time with \code{x} and \code{y} dimensions: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(delete-space-instance si)} # gbbopen-user> \textcolor{black}{(setf si (make-space-instance '(known-world) :dimensions '((x :ordered) (y :ordered))))} # gbbopen-user>} \end{example} % We have specified \code{x} and \code{y} as ordered dimensions, making the \nobr{\code{known-world}} a two-dimensional Euclidean plane. (Flatlanders would be proud!) \bfindex{describe-space-instance}% % Verify the dimensionality of the \nobr{\code{known-world}} space instance by evaluating: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-space-instance si)} Standard-space-instance # Allowed unit classes: t Dimensions: (x :ordered) (y :ordered) gbbopen-user>} \end{example} \subsection*{Step 7: Add the \code{location} unit instance to the new \code{known-world}} \bfindexit{add-instance-to-space-instance}% % Add the \nobr{\code{location}} unit instance to the new \nobr{\code{known-world}} space instance: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(add-instance-to-space-instance ui si)} # gbbopen-user>} \end{example} % The dimension warning is gone. \subsection*{Step 8: Adding every \code{location} to the \code{known-world} automatically} Up to this point, we have used \nobr{\textbf{add-instance-to-space-instance}} to add each \nobr{\code{location}} unit instance to the \nobr{\code{known-world}} space instance. We can tell GBBopen to automatically add new unit instances to one or more space instances by using the \nobr{\code{:initial-space-instances}} class option in \nobr{\textbf{define-unit-class}}. \bfindexit{define-unit-class}% % Add the following \nobr{\code{:initial-space-instances}} class option to the \nobr{\code{location}} unit-class definition in your \nobr{\code{tutorial-example.lisp}} file: % \W\supp \begin{example} \textcolor{darkergray}{% (define-unit-class location () (x y) (:dimensional-values (x :point x) (y :point y)) \textcolor{black}{(:initial-space-instances (known-world))})} \end{example} Compile and load the new \nobr{\code{location}} unit-class definition. \subsection*{Step 9: Create more \code{location} unit instances} \bfindexit{make-instance}% \codeindexit{defparameter}% % Let's test our new \nobr{\code{location}} unit class definition by making another instance. Enter the following form in the REPL: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(make-instance 'location :x 70 :y 30)} # gbbopen-user>} \end{example} % \REPLindex{:dsbb}% % and confirm that the new \nobr{\code{location}} is on the \nobr{\code{known-world}}: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{:dsbb} Space Instance Contents -------------- -------- known-world 2 instances (2 location) Unit Class Instances ---------- --------- location 2 standard-space-instance 1 --------- 3 instances gbbopen-user>} \end{example} % Here we used GBBopen's \code{:dsbb} REPL command, which is equivalent to evaluating \nobr{\code{(describe-blackboard-repository)}}. Describing the repository is a useful check that our unit instances and space instances are being created and deleted as intended. \bfindexit{make-instance}% % Now, let's populate the \nobr{\code{known-world}} with a few more locations: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(make-instance 'location :x 20 :y 20)} # gbbopen-user> \textcolor{black}{(make-instance 'location :x 25 :y 25)} # gbbopen-user> \textcolor{black}{(make-instance 'location :x 20 :y 30)} # gbbopen-user>} \end{example} % \REPLindexit{:dsbb}% % and verify that they are all on the \nobr{\code{known-world}}: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{:dsbb} Space Instance Contents -------------- -------- known-world 5 instances (5 location) Unit Class Instances ---------- --------- location 5 standard-space-instance 1 --------- 6 instances gbbopen-user>} \end{example} \subsection*{Step 10: Dimensional retrieval} \bfindexit{find-instances}% % We have seen how we can use \nobr{\textbf{find-instances}} to retrieve all \nobr{\code{location}} unit instances from the \nobr{\code{known-world}}: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) :all)} (# # # # #) gbbopen-user>} \end{example} Now that we have added \code{x} and \code{y} dimensions to \nobr{\code{location}} unit instances and to the \nobr{\code{known-world}}, we can retrieve \nobr{\code{location}} unit instances using dimensional patterns. For example, let's retrieve the unit instance positioned at (20,20): % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) '(and (= x 20) (= y 20)))} (#) gbbopen-user>} \end{example} \bfindexit{describe-instance}% % We can use \nobr{\textbf{describe-instance}} to verify that we found the desired \nobr{\code{location}} unit instance: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) '(and (= x 20) (= y 20)))} (#) gbbopen-user> \textcolor{black}{(describe-instance (first *))} Location # Instance name: 3 Space instances: ((known-world)) Dimensional values: x: 20 y: 20 Non-link slots: x: 20 y: 20 Link slots: None gbbopen-user>} \end{example} % Note that we used Common Lisp's REPL \code{*} variable that is always set to the value returned by evaluating the last REPL expression (in this case, the result of \nobr{\textbf{find-instances}}). \subsection*{Step 11: Customize the display of \code{location} unit instances} \bfindex{print-instance-slots}% % It would be convenient if we could easily see the coordinates of \nobr{\code{location}} unit instances without having to describe them. Fortunately, GBBopen makes this is easy to do by providing the \nobr{\textbf{print-instance-slots}} generic function that allows us to extend how Common Lisp's \nobr{\code{print-object}} displays \nobr{\code{location}} unit instances. Add the following \nobr{\textbf{print-instance-slots}} method \underline{after} the \nobr{\code{location}} unit-class definition in your \nobr{\code{tutorial-example.lisp}} file: % \W\supp \begin{example} \textcolor{darkergray}{% (define-unit-class location () (x y) (:dimensional-values (x :point x) (y :point y)) (:initial-space-instances (known-world))) \textcolor{black}{(defmethod print-instance-slots ((location location) stream) (call-next-method) (when (and (slot-boundp location 'x) (slot-boundp location 'y)) (format stream " (~s ~s)" (x-of location) (y-of location))))}} \end{example} % \bfindexit{print-instance-slot-value}% The method first performs a \nobr{\code{(call-next-method)}} to produce the initial printed representation of the \nobr{\code{location}} unit instance. It then checks that both \code{x} and \code{y} slots are bound and, if so, writes the \nobr{\code{location}}'s coordinates to the output stream. Checking that the slots are bound is necessary to avoid generating an error in the \nobr{\textbf{print-instance-slots}} if the slots have not been given a value. You should always perform this safety check in any \nobr{\textbf{print-instance-slots}} methods. (GBBopen provides the generic function \nobr{\textbf{print-instance-slot-value}} for use in safely displaying a slot value in \nobr{\textbf{print-instance-slots}} methods. We did not use \nobr{\textbf{print-instance-slot-value}} here, as our \nobr{\textbf{print-instance-slots}} method presents two slots, \code{x} and \code{y}, formatted together.) \bfindexit{find-instances}% % Now, compile and load the \nobr{\textbf{print-instance-slots}} method and try it out: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) '(= (x y) (20 20)))} (#) gbbopen-user>} \end{example} Note that this time we used a two-dimensional (\code{x},\code{y}) retrieval pattern rather than the conjunction of two one-dimensional patterns that we used previously. The two patterns are equivalent, but often a higher-dimensional pattern may be more convenient than a conjunction. Also note that we can see immediately that we retrieved the desired \nobr{\code{location}}. \subsection*{Step 12: More dimensional retrievals} \bfindexit{find-instances}% % Let's try some additional dimensional retrievals. First, find all \nobr{\code{location}} unit instances with an \code{x} position of 20: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) '(= x 20))} (# #) gbbopen-user>} \end{example} % Find all \nobr{\code{location}} unit instances whose \code{x} and \code{y} coordinates are less than or equal to 25: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) '(<= (x y) (25 25)))} (# #) gbbopen-user>} \end{example} % Find all \nobr{\code{location}} unit instances whose \code{x} coordinates are between 0 and 40 (inclusive) and whose \code{y} coordinates are between 60 and 100 (inclusive): % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) '(within (x y) ((0 40) (60 100))))} (#) gbbopen-user>} \end{example} % Find all \nobr{\code{location}} unit instances whose coordinates are not within the above region: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) '(not (within (x y) ((0 40) (60 100)))))} (# # # #) gbbopen-user>} \end{example} \subsection*{Step 13: Change a dimensional value} \bfindexit{find-instances}% % Recall that we assigned \nobr{\code{location}} \code{1} to the global variable \code{ui}: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{ui} # gbbopen-user>} \end{example} % and we can retrieve it by its \code{x} and \code{y} coordinates: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) '(= (x y) (40 60)))} (#) gbbopen-user>} \end{example} \bfindexit{find-instances}% % Let's change its \code{x} position to \code{80} and try retrieving it again: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(setf (x-of ui) 80)} 80 gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) '(= (x y) (40 60)))} nil gbbopen-user>} \end{example} % It has moved on the \nobr{\code{known-world}}. As expected, the \nobr{\code{location}} \code{1} unit instance is now at (80, 60): % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(find-instances 'location '(known-world) '(= (x y) (80 60)))} (#) gbbopen-user>} \end{example} % Also note that the textual representation of \nobr{\code{location}} \code{1} shows the new \code{x} slot value. %% ======================================================================== %% Using A Control Shell \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-control-shell} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \section{Using a Control Shell} \label{sec:control-shell}% A control shell is one of the three major components of a blackboard system (along with KSs and the blackboard). The control shell directs the problem-solving process by managing how KSs respond to contributions that are placed on the blackboard by an executing KS and to other events that may be triggered by the application or received from external sources. In this exercise we will use a control shell called the ``Agenda Shell.'' GBBopen's Agenda Shell is a generalization of the priority-based scheduling approach that was used in the original Hearsay-II blackboard architecture. The Agenda Shell manages KS definitions, and it initiates and terminates KS activities by: % \begin{tightitemize} \item Triggering KSs in response to events \item Deciding which triggered KSs should be activated and their priority rating \item Maintaining a rating-based queue of pending KS activations (KSAs) \item Executing the top-rated KSAs, one at a time \end{tightitemize} The Agenda Shell is highly customizable and extensible, and it can be used as the foundation for implementing advanced control mechanisms. We will use only the most basic Agenda Shell capabilities in this Tutorial. \fndocrule This exercise shows you how to: % \begin{tightitemize} \item Load GBBopen's Agenda Shell control shell \item Start the Agenda Shell executing \item Define a KS \item Display control-shell activities (control-shell events) \item Use control-shell stepping \end{tightitemize} \fndocrule \subsection*{Prerequisites} The \nobr{\code{tutorial-example.lisp}} file as modified thus far: % \W\supp \begin{example} (in-package :gbbopen-user) (define-unit-class location () (x y) (:dimensional-values (x :point x) (y :point y)) (:initial-space-instances (known-world))) (defmethod print-instance-slots ((location location) stream) (call-next-method) (when (and (slot-boundp location 'x) (slot-boundp location 'y)) (format stream " (~s ~s)" (x-of location) (y-of location)))) \end{example} \subsection*{Step 1: Load the Agenda Shell} Start up a fresh Common Lisp session and load the \nobr{\code{:agenda-shell-user}} module, using the \nobr{\code{:agenda-shell-user}} REPL command: % \W\supp \begin{smallexample} \textcolor{darkergray}{% cl-user> \textcolor{black}{:agenda-shell-user} ;; Loading \var{\/}/startup.lisp \textrm{\ldots{}} ;; Loading \var{\/}/\var{\/}/gbbopen/ ;; control-shells/agenda-shell-user.fasl gbbopen-user>} \end{smallexample} This loads everything that we've been loading with the \nobr{\code{:gbbopen-user}} module and, additionally, GBBopen's Agenda Shell control shell. As with the \nobr{\code{:gbbopen-user}} REPL command, the current package in the REPL is set to the \nobr{\code{:gbbopen-user}} package. \subsection*{Step 2: Run the control shell} \bfindex{start-control-shell}% % Now, start the Agenda Shell: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(start-control-shell)} ;; Control shell 1 started ;; No executable KSAs remain, exiting control shell ;; Control shell 1 exited: 2 cycles completed ;; Run time: 0 seconds ;; Elapsed time: 0 seconds :quiescence gbbopen-user>} \end{example} \index{quiescence}% \codeindex{quiescence-event}% \index{knowledge~source}% \index{knowledge-source activation}% % What just happened? The Agenda Shell began executing and looked for something to do. However, we have not yet defined any knowledge sources (KSs), so the Agenda Shell indicates that it did not find any executable KSAs in its initial KS-execution cycle. This situation is called \textit{quiescence} and, by default, the Agenda Shell signals that quiescence has occurred and then continues for an additional KS-execution cycle in case any executable KSAs resulted from the quiescence signal. Again, no executable KSAs were found in cycle 2, so the Agenda Shell exits due to \code{:quiescence}. Note that the Agenda Shell requires that the idle-loop process has been started on \xsitelink{CMUCL}{http://www.cons.org/cmucl/} and that multiprocessing has been started on \xsitelink{LispWorks}{http://www.lispworks.com}. (An error message will instruct you on what to do if this is not the case.) \subsection*{Step 3: Define a KS} So let's define a KS! \bfindex{define-ks}% % Edit your \nobr{\code{tutorial-example.lisp}} file and add the following function and KS definition to the end of the \nobr{\code{tutorial-example.lisp}} file: % \W\supp \begin{example} \textcolor{darkergray}{% (define-unit-class location () (x y) (:dimensional-values (x :point x) (y :point y)) (:initial-space-instances (known-world))) (defmethod print-instance-slots ((location location) stream) (call-next-method) (when (and (slot-boundp location 'x) (slot-boundp location 'y)) (format stream " (~s ~s)" (x-of location) (y-of location)))) \textcolor{black}{;;; ==================================================================== ;;; Startup KS (defun startup-ks-function (ksa) (declare (ignore ksa)) ;; Create an initial location unit instance at (0,0): (make-instance 'location :x 0 :y 0)) (define-ks startup-ks :trigger-events ((control-shell-started-event)) :execution-function 'startup-ks-function)}} \end{example} The function \nobr{\code{startup-ks-function}} implements the KS. The Agenda Shell always calls a KS's execution function with a single argument, a \code{ksa} unit instance that represents the activation of the KS. For the present, we will ignore the \code{ksa} argument. Our simple \nobr{\code{startup-ks-function}} creates a \nobr{\code{location}} unit instance at the center of the \nobr{\code{known-world}}, coordinate (0,0). The \nobr{\textbf{define-ks}} form defines a KS named \nobr{\code{startup-ks}} to the Agenda Shell. The \nobr{\code{:trigger-events}} value indicates that the KS should be triggered when an event called \nobr{\code{control-shell-started-event}} is signaled. The Agenda Shell signals \nobr{\code{control-shell-started-event}} once, when the control shell is started. The \nobr{\code{:execution-function}} names the function (that we just defined) that implements the KS. Compile and load the entire \nobr{\code{tutorial-example.lisp}} file directly from the editor buffer (using \nobr{\code{C-c C-k}} in SLIME; \nobr{\code{C-c C-b}} in ELI). The Agenda Shell creates a \code{ks} unit instance for each KS that we define: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(map-instances-of-class #'print 'ks)} # nil gbbopen-user>} \end{example} % Note that the name of the \code{ks} unit instance is the name of the KS. \subsection*{Step 4: Make the \code{known-world}} Before we can run our KS, we must make the \nobr{\code{known-world}} space instance: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(make-space-instance '(known-world) :dimensions '((x :ordered) (y :ordered))))} # gbbopen-user>} \end{example} \subsection*{Step 5: Start the control shell} \bfindexit{start-control-shell}% % Start the Agenda Shell again: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(start-control-shell)} ;; Control shell 1 started ;; No executable KSAs remain, exiting control shell ;; Control shell 1 exited: 3 cycles completed ;; Run time: 0 seconds ;; Elapsed time: 0 seconds :quiescence gbbopen-user>} \end{example} \REPLindexit{:dsbb}% % Did it work? Let's describe the blackboard repository: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{:dsbb} Space Instance Contents -------------- -------- known-world 1 instances (1 location) Unit Class Instances ---------- --------- control-shell 1 * ks 1 + ksa-queue 2 + location 1 ordered-ksa-queue 1 + standard-space-instance 1 --------- 7 instances gbbopen-user>} \end{example} % The initial \nobr{\code{location}} unit instance is there! Note that some other unit instances have been created by the control shell. There is one \code{ks} unit instance, our \nobr{\code{startup-ks}} KS, as well as two \nobr{\code{ksa-queue}} and one \nobr{\code{ordered-ksa-queue}} unit instances. The \nobr{\code{ordered-ksa-queue}} is the rating-based queue of pending KSAs (no pending KSAs remain on it) and the two \nobr{\code{ksa-queue}} queues are the Agenda Shell's executed KSAs and obviated KSAs queues (both empty). We will discuss these queues in a later exercise. The \code{ks}, \nobr{\code{ksa-queue}}, \nobr{\code{ordered-ksa-queue}} unit-instance counts are followed by plus signs (\code{+}). This indicates that these unit classes have been defined to be \textit{retained}, meaning that their instances are not deleted by our call to \nobr{\textbf{delete-blackboard-repository}}. The plus sign indicates that the retention attribute will be propagated to all subclasses of those unit classes; retained, but not propagated, would be shown by an asterisk (\code{*}). \REPLindexit{:dsbb}% % Let's verify this behavior by calling \nobr{\textbf{delete-blackboard-repository}} and then describe the blackboard repository: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(delete-blackboard-repository)} t gbbopen-user> \textcolor{black}{:dsbb} There are no space instances in the blackboard repository. Unit Class Instances ---------- --------- control-shell 1 * ks 1 + ksa-queue 2 + ordered-ksa-queue 1 + gbbopen-user>} \end{example} \subsection*{Step 6: Display control shell activities} With all but the retained unit instances deleted from the blackboard repository, let's rerun the control shell. However, this time we will ask GBBopen to display more of what the control shell is doing. First, enable display of all control-shell and instance-creation events by evaluating: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(enable-event-printing '(control-shell-event :plus-subevents))} nil gbbopen-user> \textcolor{black}{(enable-event-printing 'instance-created-event)} nil gbbopen-user>} \end{example} % or the shorthand equivalent: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(enable-event-printing '(control-shell-event +))} nil gbbopen-user> \textcolor{black}{(enable-event-printing 'instance-created-event)} nil gbbopen-user>} \end{example} We will cover events, event printing, and event functions in greater detail in a later exercise. \bfindexit{start-control-shell}% % Start the Agenda Shell once again: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(start-control-shell)} ;; Control shell 1 started => Control-shell-started-event => Control-shell-cycle-event :cycle 1 => Instance-created-event :instance # => Ksa-activated-event :instance # :cycle 1 => Ksa-executing-event :instance # :cycle 1 => Instance-created-event :instance # => Control-shell-cycle-event :cycle 2 => Quiescence-event => Control-shell-cycle-event :cycle 3 ;; No executable KSAs remain, exiting control shell ;; Control shell 1 exited: 3 cycles completed ;; Run time: 0 seconds ;; Elapsed time: 0 seconds :quiescence gbbopen-user>} \end{example} \subsection*{Step 7: Control-shell stepping} \bfindexit{delete-blackboard-repository}% % Let's rerun the control shell once again, but this time we will enable control-shell stepping. Again, delete the blackboard repository: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(delete-blackboard-repository)} t gbbopen-user>} \end{example} % and disable the event printing that we enabled in the last step: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(disable-event-printing)} nil gbbopen-user>} \end{example} \bfindexit{start-control-shell}% % Now start the Agenda Shell, this time with stepping enabled: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(start-control-shell :stepping 't)} ;; Control shell 1 started >> CS Step (cycle 1): About to process event #... \entered{?} Stepping commands (follow with ): d Disable this kind of stepping (:process-event) e Enable another kind of stepping f Evaluate a form h or ? Help (this text) q Quit (disable all stepping and continue) s Show enabled stepping kinds x Exit control shell = Describe the object of interest (bound to ==) + Enable all stepping - Disable all stepping Continue (resume processing) >> CS Step (cycle 1): About to process event #... \entered{} >> CS Step (cycle 1): About to activate KS startup-ks on control-shell-started-event... \entered{} >> CS Step (cycle 1): About to execute KSA #... \entered{} << KSA 1 returned: (#) >> CS Step (cycle 2): About to signal quiescence... \entered{} >> CS Step (cycle 3): About to signal quiescence... \entered{} ;; No executable KSAs remain, exiting control shell ;; Control shell 1 exited: 3 cycles completed ;; Run time: 0 seconds ;; Elapsed time: 1 minute, 6 seconds :quiescence gbbopen-user>} \end{example} %% ======================================================================== %% Define Another KS \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-application-startup} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \section{Application Startup and Event Functions} \label{sec:application-startup}% In the last exercise, we needed to create the \nobr{\code{known-world}} space instance, with \code{x} and \code{y} dimensions, before we started the Agenda Shell. We also needed to call \nobr{\textbf{delete-blackboard-repository}} before calling \nobr{\textbf{start-control-shell}} to execute another run of our developing application. It is convenient to have these activities performed automatically whenever the control shell is started, so we'll do so in this exercise. \fndocrule This exercise shows you how to: \begin{tightitemize} \item Define a function to perform all application-specific initialization and re-execution activities \item Automatically invoke the initialization/re-execution function at control-shell startup using GBBopen's event-function capabilities \item Restrict the classes of unit instances that can be stored on a space instance \item Specify the dimensionality of a space instance relative to the dimensional specifications of a unit class \end{tightitemize} \fndocrule \subsection*{Prerequisites} \begin{tightitemize} \item The \nobr{\code{tutorial-example.lisp}} file as modified thus far: \end{tightitemize} % \W\supp \begin{example} (in-package :gbbopen-user) (define-unit-class location () (x y) (:dimensional-values (x :point x) (y :point y)) (:initial-space-instances (known-world))) (defmethod print-instance-slots ((location location) stream) (call-next-method) (when (and (slot-boundp location 'x) (slot-boundp location 'y)) (format stream " (~s ~s)" (x-of location) (y-of location)))) ;;; ==================================================================== ;;; Startup KS (defun startup-ks-function (ksa) (declare (ignore ksa)) ;; Create an initial location unit instance at (0,0): (make-instance 'location :x 0 :y 0)) (define-ks startup-ks :trigger-events ((control-shell-started-event)) :execution-function 'startup-ks-function) \end{example} \begin{tightitemize} \item The \nobr{\code{:agenda-shell-user}} module is loaded \end{tightitemize} \subsection*{Step 1: Define an initialization function} Edit your \nobr{\code{tutorial-example.lisp}} file and add the following function definition at the end of the file: % \W\supp \begin{example} (defun initializations (event-name &key &allow-other-keys) (declare (ignore event-name)) ;; Clean up any previous run: (delete-blackboard-repository) ;; Make a new known-world space instance: (make-space-instance '(known-world) :dimensions '((x :ordered) (y :ordered)))) \end{example} The first thing to note about the function definition is the argument signature: \nobr{\code{initializations}} is to be invoked with an event name (which is ignored in our function) and possibly other keyword arguments (indicated by the \code{\&key} and \nobr{\code{\&allow-other-keys}} lambda list keywords). This argument signature conforms to GBBopen's event-function capabilities, which will be introduced in the next step in this exercise. When \nobr{\code{initializations}} executes, it deletes all of our application unit and space instances from the blackboard repository. Only \code{ks}, \nobr{\code{ksa-queue}}, and \nobr{\code{ordered-ksa-queue}} unit instances, which are defined by the control shell as retained, are not deleted by \nobr{\textbf{delete-blackboard-repository}}. Then \nobr{\code{initializations}} creates a new \nobr{\code{(known-world)}} space instance with ordered dimensions \code{x} and \code{y}. \subsection*{Step 2: Add an event function} GBBopen allows you to attach functions, called \textit{event functions}, that are called whenever a specific event is signaled. Each event function must accept the arguments associated with every event class to which it is added. In addition, the function should accept additional arguments that are associated with all subevents of the specified event classes. This is achieved by specifying \nobr{\code{\&allow-other-keys}} in the lambda list of the function. Here are GBBopen's defined event classes when the Agenda Shell has been loaded: % \T\begin{ifhtml} \xml{br} \xml{img align="center" src="agenda-shell-events.png"} \xml{br clear="both"} \T\end{ifhtml} \W\begin{iftex} \begin{center} \includegraphics[scale=0.85]{agenda-shell-events} \end{center} \W\end{iftex} % Event classes shown within rectangles are abstract event classes that cannot be signaled. Nevertheless, abstract event classes are very convenient if we wish to attach an event function to an entire subtree of event classes. We used abstract event classes to advantage earlier when we enabled display of all control-shell events by evaluating: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> (enable-event-printing '(control-shell-event :plus-subevents)) nil gbbopen-user>} \end{example} % or the shorthand equivalent: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> (enable-event-printing '(control-shell-event +)) nil gbbopen-user>} \end{example} Add the following form at the end of your \nobr{\code{tutorial-example.lisp}} file: % \W\supp \begin{example} (add-event-function 'initializations 'control-shell-started-event ;; Initializations should be done first! :priority 100) \end{example} % (We'll place the \nobr{\textbf{add-event-function}} form immediately after the \nobr{\code{initializations}} function definition in our file, but this choice of location is purely a code organizational style preference---the form could be placed anywhere relative to the function definition.) \subsection*{Step 3: Run the application} \bfindexit{start-control-shell}% % Start a fresh Common Lisp session, compile and load the \nobr{\code{tutorial-example.lisp}} file directly from the editor buffer (using \nobr{\code{C-c C-k}} in SLIME; \nobr{\code{C-c C-b}} in ELI) and start the Agenda Shell again: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(start-control-shell)} ;; Control shell 1 started ;; No executable KSAs remain, exiting control shell ;; Control shell 1 exited: 3 cycles completed ;; Run time: 0 seconds ;; Elapsed time: 0 seconds :quiescence gbbopen-user>} \end{example} Note that our developing application performs the same as it did in the last exercise, but now our \nobr{\code{initializations}} event function is taking care of all the details of starting up our application. We no longer have to remember to create the \nobr{\code{known-world}} space instance or to delete the blackboard repository before running the application another time. \subsection*{Step 4: Run it Again} \bfindexit{start-control-shell}% % Let's verify that we can re-run our application. Without doing anything else, start the Agenda Shell again: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(start-control-shell)} ;; Control shell 1 started ;; No executable KSAs remain, exiting control shell ;; Control shell 1 exited: 3 cycles completed ;; Run time: 0 seconds ;; Elapsed time: 0 seconds :quiescence gbbopen-user>} \end{example} As before, our \nobr{\code{initializations}} event function took care of all the details of starting up our application. \subsection*{Step 5: It's a new world$\ldots$} \bfindexit{make-space-instance}% % GBBopen allows us to restrict the classes of unit instances that can be stored on a space instance. For example, we can limit the \nobr{\code{known-world}} to \nobr{\code{location}} unit instances by specifying an \nobr{\code{:allowed-unit-classes}} value to \nobr{\textbf{make-space-instance}}: % \W\supp \begin{example} \textcolor{darkergray}{% (defun initializations (event-name &key &allow-other-keys) (declare (ignore event-name)) ;; Clean up any previous run: (delete-blackboard-repository) ;; Make a new known-world space instance: (make-space-instance '(known-world) \textcolor{black}{:allowed-unit-classes '(location)} :dimensions '((x :ordered) (y :ordered))))} \end{example} % Attempting to add any unit-instance that is not a \nobr{\code{location}} to \nobr{\code{known-world}} will now generate an error. It is often convenient to specify the dimensions of a space-instance relative to those of one or more unit classes. Edit the definition of \nobr{\code{initializations}}, removing the \code{x} and \code{y} dimensions specification: % \W\supp \begin{example} \textcolor{darkergray}{% (defun initializations (event-name &key &allow-other-keys) (declare (ignore event-name)) ;; Clean up any previous run: (delete-blackboard-repository) ;; Make a new known-world space instance: (make-space-instance '(known-world) :allowed-unit-classes '(location) :dimensions \textcolor{red}{'((x :ordered) (y :ordered))}))} \end{example} % and replacing it with a call of \nobr{\textbf{dimensions-of}} to obtain the dimensions associated with instances of the \nobr{\code{location}} unit class: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% (defun initializations (event-name &key &allow-other-keys) (declare (ignore event-name)) ;; Clean up any previous run: (delete-blackboard-repository) ;; Make a new known-world space instance: (make-space-instance '(known-world) :allowed-unit-classes '(location) :dimensions \textcolor{black}{(dimensions-of 'location)}))} \end{example} \subsection*{Step 6: Run the application again} Compile and load the \nobr{\code{tutorial-example.lisp}} file directly from the editor buffer (using \nobr{\code{C-c C-k}} in SLIME; \nobr{\code{C-c C-b}} in ELI) and start the Agenda Shell again: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(start-control-shell)} ;; Control shell 1 started ;; No executable KSAs remain, exiting control shell ;; Control shell 1 exited: 3 cycles completed ;; Run time: 0 seconds ;; Elapsed time: 0 seconds :quiescence gbbopen-user>} \end{example} \bfindexit{describe-space-instance}% Verify the dimensionality of the \nobr{\code{known-world}} space instance by evaluating: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-space-instance '(known-world))} Standard-space-instance # Allowed unit classes: t Dimensions: (x :ordered) (y :ordered) gbbopen-user>} \end{example} %% ======================================================================== %% Define Another KS \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-another-ks} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \section{Add Another KS} \label{sec:another-ks}% The last exercise made it easy to initialize and run our application repeatedly by simply starting the Agenda Shell. We also specified the dimensionality of our \nobr{\code{known-world}} space instance relative to the dimensional specifications of the \nobr{\code{location}} unit class. With these niceties in place, its time to move beyond our initial \nobr{\code{location}} unit instance. \fndocrule This exercise shows you how to: \begin{tightitemize} \item Add an additional dimension to a unit class \item Define a KS that obtains its execution-context information from its triggering unit instance \item Extend the random-walk application to do some walking \item Explore the resulting random walk \end{tightitemize} \fndocrule \subsection*{Prerequisites} \begin{tightitemize} \item The \nobr{\code{tutorial-example.lisp}} file as modified thus far: \end{tightitemize} % \W\supp \begin{example} (in-package :gbbopen-user) (define-unit-class location () (x y) (:dimensional-values (x :point x) (y :point y)) (:initial-space-instances (known-world))) (defmethod print-instance-slots ((location location) stream) (call-next-method) (when (and (slot-boundp location 'x) (slot-boundp location 'y)) (format stream " (~s ~s)" (x-of location) (y-of location)))) ;;; ==================================================================== ;;; Startup KS (defun startup-ks-function (ksa) (declare (ignore ksa)) ;; Create an initial location unit instance at (0,0): (make-instance 'location :x 0 :y 0)) (define-ks startup-ks :trigger-events ((control-shell-started-event)) :execution-function 'startup-ks-function) ;;; ==================================================================== ;;; Initializations (run at Agenda Shell startup) (defun initializations (event-name &key &allow-other-keys) (declare (ignore event-name)) ;; Clean up any previous run: (delete-blackboard-repository) ;; Make a new known-world space instance: (make-space-instance '(known-world) :dimensions (dimensions-of 'location))) (add-event-function 'initializations 'control-shell-started-event ;; Initializations should be done first! :priority 100) \end{example} \begin{tightitemize} \item The \nobr{\code{:agenda-shell-user}} module is loaded \end{tightitemize} \subsection*{Step 1: Add another dimension} It's time we introduce the notion of time to our application. Edit the \nobr{\code{location}} unit-class definition in \nobr{\code{tutorial-example.lisp}}, adding a new slot, \code{time}, to the \nobr{\code{location}} unit class definition and a corresponding \code{time} dimensional value: % \W\supp \begin{example} \textcolor{darkergray}{% (define-unit-class location () (\textcolor{black}{time} x y) (:dimensional-values \textcolor{black}{(time :point time)} (x :point x) (y :point y)) (:initial-space-instances (known-world)))} \end{example} Recall that we specified that the dimensions of the \nobr{\code{known-world}} space instance that is created by our \nobr{\code{initializations}} function relative to the dimensions of the \nobr{\code{location}} unit class: % \W\supp \begin{example} \textcolor{darkergray}{% (make-space-instance '(known-world) :dimensions (dimensions-of 'location)))} \end{example} % Therefore, we don't need to modify our call to \nobr{\code{make-space-instance}} in order to add \code{time} as a dimension of \nobr{\code{known-world}}. Next, modify \nobr{\code{startup-ks-function}} in \nobr{\code{tutorial-example.lisp}} so that it creates the initial \nobr{\code{location}} unit instance at time 0: % \W\supp \begin{example} \textcolor{darkergray}{% (defun startup-ks-function (ksa) (declare (ignore ksa)) ;; Create an initial location unit instance at (0,0) \textcolor{black}{at time 0}: (make-instance 'location \textcolor{black}{:time 0} :x 0 :y 0))} \end{example} \subsection*{Step 2: A test of time} Let's verify our work. Compile and load the \nobr{\code{tutorial-example.lisp}} file directly from the editor buffer (using \nobr{\code{C-c C-k}} in SLIME; \nobr{\code{C-c C-b}} in ELI) and start the Agenda Shell: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(start-control-shell)} ;; Control shell 1 started ;; No executable KSAs remain, exiting control shell ;; Control shell 1 exited: 3 cycles completed ;; Run time: 0 seconds ;; Elapsed time: 0 seconds :quiescence gbbopen-user>} \end{example} \bfindexit{describe-instance}% \bfindexit{find-instance-by-name}% Check that the initial \nobr{\code{location}} unit instance is at \code{time} zero: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-instance (find-instance-by-name 1 'location))} Location # Instance name: 1 Space instances: ((known-world)) Dimensional values: time: 0 x: 40 y: 60 Non-link slots: time: 0 x: 40 y : 60 Link slots: None gbbopen-user>} \end{example} \subsection*{Step 3: Define another KS} Define a KS called \nobr{\code{random-walk-ks}} that: % \begin{tightitemize} \item Is triggered when a \nobr{\code{location}} unit instance is created \item Has a constant KSA rating of 100 \item Has an execution function called \nobr{\code{random-walk-ks-function}}, which: \begin{tightitemize} \item Checks if we've already walked for 75 locations and prints a message if we have. \item Otherwise: \begin{tightitemize} \item Determines a random location for which the x and y values are within 10 of the x and y values of the triggering unit instance (that is, the \nobr{\code{location}} instance whose creation triggered the \nobr{\code{random-walk-ks}} KS) \item If both of the x and y values for the new random location are between -50 and 50, creates a \nobr{\code{location}} unit instance at the random location; otherwise, prints a message indicating that we've walked off the world \end{tightitemize} \end{tightitemize} \end{tightitemize} \subsubsection*{Step 3a: Define a utility function} Begin implementing the \nobr{\code{random-walk-ks}} by adding the following utility function to the end of your \nobr{\code{tutorial-example.lisp}} file: % \W\supp \begin{example} ;;; ==================================================================== ;;; Random-walk KS (defun add-linear-variance (value max-variance) ;;; Returns a new random value in the interval ;;; [(- value max-variance), (+ value max-variance)] (+ value (- (random (1+ (* max-variance 2))) max-variance))) \end{example} % Then compile the definition (using \nobr{\code{C-c C-c}} in SLIME or \nobr{\code{C-c C-x}} in ELI) and evaluate the following test in the REPL: % \bfindex{printv}% % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(dotimes (i 15) (printv (add-linear-variance 0 10)))} ;; (add-linear-variance 0 10) => 8 ;; (add-linear-variance 0 10) => 9 ;; (add-linear-variance 0 10) => 4 ;; (add-linear-variance 0 10) => 3 ;; (add-linear-variance 0 10) => -4 ;; (add-linear-variance 0 10) => -10 ;; (add-linear-variance 0 10) => -1 ;; (add-linear-variance 0 10) => 0 ;; (add-linear-variance 0 10) => 4 ;; (add-linear-variance 0 10) => 5 ;; (add-linear-variance 0 10) => 8 ;; (add-linear-variance 0 10) => -5 ;; (add-linear-variance 0 10) => -3 ;; (add-linear-variance 0 10) => 7 ;; (add-linear-variance 0 10) => 6 nil gbbopen-user>} \end{example} Because \nobr{\code{add-linear-variance}} is stochastic, your results will be similar but not identical. Note that we used GBBopen's \textbf{printv} macro to display the result of each generated value. \textbf{Printv} can greatly assist debugging by printing forms and the results of evaluating them. \textbf{Printv} can be transparently wrapped around any form in a complex function definition, as it evaluates and displays all the forms in its body and returns the values resulting from evaluating the last form: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> (printv "Some multiple values" (values 1 2) "Some more" (values 3 4 5)) ;; Some multiple values ;; (values 1 2) => 1; 2 ;; Some more ;; (values 3 4 5) => 3; 4; 5 4 5 6 gbbopen-user>} \end{example} \subsubsection*{Step 3b: Define the \code{random-walk-ks} execution function} Next add the following KS-execution function to the end of your \nobr{\code{tutorial-example.lisp}} file: % \W\supp \begin{example} (defun random-walk-ks-function (ksa) ;;; Move to the next (random) location in the world (let* ((trigger-instance (sole-trigger-instance-of ksa)) ;; The new time is one greater than the stimulus's time: (time (1+ (time-of trigger-instance)))) (cond ;; If the maximum time value (75) is reached, tell the user we've ;; walked too long: ((>= time 75) (format t "~2&Walked too long.~\%")) (t ;; The new location is +/- 10 of the stimulus's location: (let ((x (add-linear-variance (x-of trigger-instance) 10)) (y (add-linear-variance (y-of trigger-instance) 10))) (cond ;; Check that the new location is within the known-world ;; boundaries. If so, create the new location instance: ((and (<= -50 x 50) (<= -50 y 50)) (make-instance 'location :time time :x x :y y)) ;; Otherwise, tell the user that we've walked too far away: (t (format t "~2\&Walked off the world: (~d, ~d).~\%" x y)))))))) \end{example} \bfindex{sole-trigger-instance-of}% % Unlike the KS-execution functions that we have defined previously, \nobr{\code{random-walk-ks-function}} does not ignore its \code{ksa} argument. Instead, it calls \nobr{\textbf{sole-trigger-instance-of}} with the \code{ksa} unit-instance argument in order to obtain the \nobr{\code{location}} unit instance whose creation triggered the KSA. This pattern of obtaining the unit instance that triggered a KSA and then using that triggering unit instance as the context for the KS execution is typical of many KSs. \subsubsection*{Step 3c: Add the \code{random-walk-ks} definition} Finally, add this \nobr{\code{define-ks}} form to the end of your \nobr{\code{tutorial-example.lisp}} file to complete the \nobr{\code{random-walk-ks}} definition: % \bfindexit{define-ks}% % \W\supp \begin{example} (define-ks random-walk-ks :trigger-events ((instance-created-event location)) :rating 100 :execution-function 'random-walk-ks-function) \end{example} \subsection*{Step 4: Run the application} \bfindexit{start-control-shell}% % Compile and load the \nobr{\code{random-walk-ks}} forms, and then start the Agenda Shell: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(start-control-shell)} ;; Control shell 1 started Walked off the world: (23, 55). ;; No executable KSAs remain, exiting control shell ;; Control shell 1 exited: 64 cycles completed ;; Run time: 0.01 seconds ;; Elapsed time: 0 seconds :quiescence gbbopen-user>} \end{example} \REPLindexit{:dsbb}% % It looks like something happened! (Again, because \nobr{\code{add-linear-variance}} is stochastic, your results will be similar but not identical.) Let's look at the blackboard repository and see how many \nobr{\code{location}} unit instances were created: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{:dsbb} Space Instance Contents -------------- -------- known-world 61 instances (61 location) Unit Class Instances ---------- --------- control-shell 1 * ks 1 + ksa-queue 2 + location 61 ordered-ksa-queue 1 + standard-space-instance 1 --------- 67 instances gbbopen-user>} \end{example} The 61 \nobr{\code{location}} instances makes sense. Previously, it required 3 control-shell cycles to create the initial \nobr{\code{location}} unit instance (one to execute the \nobr{\code{initial-ks}} KSA followed by two additional cycles of quiescence before the Agenda Shell exits). We now create one additional \nobr{\code{location}} unit instance with every execution of \nobr{\code{random-walk-ks}}, so we always create 3 fewer \nobr{\code{location}} instances than the total number of control-shell cycles. \subsection*{Step 5: Where have we been?} \bfindexit{map-instances-of-class}% % It would be interesting to see where our random walk has taken us. We could use GBBopen's \nobr{\textbf{map-instances-of-class}} iterator to print each of the \nobr{\code{location}} unit instances: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(map-instances-of-class #'print 'location)} # # # # # # # # # \textrm{\ldots{}} # # # # # # # # # # # nil gbbopen-user>} \end{example} % Unfortunately, the order that unit instances are supplied to the \code{print} function is not controllable. Our walk would be much clearer if we printed the \nobr{\code{location}} unit instances in time order. \bfindex{map-sorted-instances-of-class}% % We might consider taking advantage of the instance names that GBBopen assigns to unit instances. We could do something like the following: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{red}{(dotimes (i 76) (let ((location (find-instance-by-name i 'location))) (when location (print location))))} # # # # # # # # # # \textrm{\ldots{}} # # # # # # # # # # # # nil gbbopen-user>} \end{example} This is a bad idea for several reasons. First, we are looking up every \nobr{\code{location}} unit instance by its instance name, which is less efficient than operating on \nobr{\code{location}} instances directly. While this isn't an significant issue in expressions that we evaluate in the REPL to investigate our application, we should seek to avoid such inefficiencies in application code. More importantly, however, the \nobr{\code{location}} instance name just happens to mirror the sequencing that we really want to display: the \code{time} value of the locations. We should find a way to sequence \nobr{\code{location}} printing that relies on the \code{time} values directly. \bfindex{map-sorted-instances-of-class}% % GBBopen provides a variant of \nobr{\textbf{map-instances-of-class}}, called \nobr{\textbf{map-sorted-instances-of-class}}, that sorts the unit instances based on a comparison predicate and an optional \code{:key} accessor function that suits our needs: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(map-sorted-instances-of-class #'print 'location #'< :key #'time-of)} # # # # # # # # # # \textrm{\ldots{}} # # # # # # # # # # # # nil gbbopen-user>} \end{example} \bfindex{do-sorted-instances-of-class}% % Using \nobr{\textbf{map-sorted-instances-of-class}} involves a sorting operation, so this approach still has some efficiency concerns for use in application code. However, it suits our REPL-exploration needs just fine. (There is a \nobr{\textbf{do-sorted-instances-of-class}} macro, if an iterative style is preferred over a mapper.) We will explore a more efficient approach to displaying the random walk in the next exercise. \subsection*{Step 6: Run the application a few more times} \bfindexit{start-control-shell}% % If we run the application a few more times, we eventually encounter a case where we create the allotted 75 \nobr{\code{location}} unit instances without walking off the \nobr{\code{known-world}}: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(start-control-shell)} ;; Control shell 1 started Walked too long. ;; No executable KSAs remain, exiting control shell ;; Control shell 1 exited: 78 cycles completed ;; Run time: 0.04 seconds ;; Elapsed time: 0 seconds :quiescence gbbopen-user>} \end{example} \bfindexit{map-sorted-instances-of-class}% Here is one such random walk: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(map-sorted-instances-of-class #'print 'location #'< :key #'time-of)} # # # # # # # # # # \textrm{\ldots{}} # # # # # # nil gbbopen-user>} \end{example} %% ======================================================================== %% Making Connections \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-connections} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \section{Making Connections} \label{sec:connections}% %\begin{center} %\textcolor{caution}{[Construction ahead\ldots{}]} %\end{center} We finally did some walking in the last exercise and learned how to display the \nobr{\code{location}} unit instances in our walk from the REPL. In this exercise, we learn how to use GBBopen's link capabilities to represent relationships among unit instances. Links are an important aspect of almost every GBBopen application, so it's time that we started taking advantage of them. \fndocrule This exercise shows you how to: \begin{tightitemize} \item Add link slots to a unit class \item Use link slots to traverse and display the resulting random walk \end{tightitemize} \fndocrule \subsection*{Prerequisites} \begin{tightitemize} \item The \nobr{\code{tutorial-example.lisp}} file as modified thus far: \end{tightitemize} % \W\supp \begin{example} (in-package :gbbopen-user) (define-unit-class location () (time x y) (:dimensional-values (time :point time) (x :point x) (y :point y)) (:initial-space-instances (known-world))) (defmethod print-instance-slots ((location location) stream) (call-next-method) (when (and (slot-boundp location 'x) (slot-boundp location 'y)) (format stream " (~s ~s)" (x-of location) (y-of location)))) ;;; ==================================================================== ;;; Startup KS (defun startup-ks-function (ksa) (declare (ignore ksa)) ;; Create an initial location unit instance at (0,0): (make-instance 'location :time 0 :x 0 :y 0)) (define-ks startup-ks :trigger-events ((control-shell-started-event)) :execution-function 'startup-ks-function) ;;; ==================================================================== ;;; Initializations (run at Agenda Shell startup) (defun initializations (event-name &key &allow-other-keys) (declare (ignore event-name)) ;; Clean up any previous run: (delete-blackboard-repository) ;; Make a new known-world space instance: (make-space-instance '(known-world) :dimensions (dimensions-of 'location))) (add-event-function 'initializations 'control-shell-started-event ;; Initializations should be done first! :priority 100) ;;; ==================================================================== ;;; Random-walk KS (defun add-linear-variance (value max-variance) ;;; Returns a new random value in the interval ;;; [(- value max-variance), (+ value max-variance)] (+ value (- (random (1+ (* max-variance 2))) max-variance))) (defun random-walk-ks-function (ksa) ;;; Move to the next (random) location in the world (let* ((trigger-instance (sole-trigger-instance-of ksa)) ;; The new time is one greater than the stimulus's time: (time (1+ (time-of trigger-instance)))) (cond ;; If the maximum time value (75) is reached, tell the user we've ;; walked too long: ((>= time 75) (format t "~2&Walked too long.~\%")) (t ;; The new location is +/- 10 of the stimulus's location: (let ((x (add-linear-variance (x-of trigger-instance) 10)) (y (add-linear-variance (y-of trigger-instance) 10))) (cond ;; Check that the new location is within the known-world ;; boundaries. If so, create the new location instance: ((and (<= -50 x 50) (<= -50 y 50)) (make-instance 'location :time time :x x :y y)) ;; Otherwise, tell the user that we've walked too far away: (t (format t "~2&Walked off the world: (~d, ~d).~\%" x y)))))))) (define-ks random-walk-ks :trigger-events ((instance-created-event location)) :rating 100 :execution-function 'random-walk-ks-function) \end{example} \begin{tightitemize} \item The \nobr{\code{:agenda-shell-user}} module is loaded \end{tightitemize} \subsection*{Step 1: Add a link} In the last exercise, we used \nobr{\textbf{map-sorted-instances-of-class}} to display the random walk. Another way that we could represent the walk is by connect each newly created \nobr{\code{location}} unit instance to the \nobr{\code{location}} unit instance that preceded it in the walk. We'll use GBBopen's link capabilities to do this. A \textit{link} is a bidirectional relationship between two unit instances that is implemented by two pointers. From the perspective of a particular unit instance, each link consists of an outgoing, or \textit{direct}, pointer to another unit instance and an incoming, or \textit{inverse}, pointer that is stored in unit instance pointed to by the direct pointer. GBBopen automatically maintains the bidirectional-link consistency of these pointers when creating new links, deleting existing links, or deleting unit instances. Links remove the possibility of ``one-sided'' relationships or ``dangling'' pointers to deleted unit instances. Edit the \nobr{\code{location}} unit-class definition in your \nobr{\code{tutorial-example.lisp}} file, adding two link slots, \nobr{\code{next-location}} and \nobr{\code{previous-location}}, to the \nobr{\code{location}} unit class definition: % \W\supp \begin{example} \textcolor{darkergray}{% (define-unit-class location () (time x y \textcolor{black}{(next-location :link (location previous-location :singular t) :singular t) (previous-location :link (location next-location :singular t) :singular t)}) (:dimensional-values (time :point time) (x :point x) (y :point y)) (:initial-space-instances (known-world)))} \end{example} Each link-slot specification is a list whose first element is the name of the link slot. This is followed by the link slot option \code{:link} and a concise specification of the inverse link slot associated with that link slot. In this case, the \nobr{\code{next-location}/\code{previous-location}} link is between instances of the same (\nobr{\code{location}}) unit class, but often links are between instances of different unit classes. Links can be many-to-many, many-to-one, one-to-many, or one-to-one. In this case, the \nobr{\code{next-location}/\code{previous-location}} link is one-to-one, which is specified by including the \nobr{\code{:singular t}} slot option in the link-slot definition (and the corresponding \nobr{\code{:singular t}} specification in the concise inverse-link-slot specification). To help clarify the specification of link slot arity, let's temporarily assume that we want a \nobr{\code{location}} instance that can have many next locations, but only a single previous location. This link relation would be specified as follows: % \W\supp \begin{example} \textcolor{darkergray}{% \textrm{\ldots{}} \textcolor{blue}{(next-locations :link (location previous-location :singular t))) (previous-location :link (location next-locations) :singular t)} \textrm{\ldots{}}} \end{example} We've followed the natural GBBopen convention of giving singular link slots a singular name (such as \nobr{\code{previous-location}}) and link slots that can contain multiple links a plural name (such as \nobr{\code{next-locations}}). Note that the \code{:singular} option is associated with the \nobr{\code{previous-location}} link slot as both a slot option in the \nobr{\code{previous-location}} link-slot definition and in the concise inverse-link-slot specification for \nobr{\code{previous-location}} in the \nobr{\code{next-locations}} link-slot definition. \subsection*{Step 2: Break some links} The concise inverse-link-slot specification supplied by the \code{:link} slot option provides a ``double entry'' redundancy that is useful when links are between instances of different unit classes, as the link can be understood by viewing either class definition. The redundancy also helps GBBopen recognize inconsistencies in link specifications. The function \nobr{\textbf{check-link-definitions}} asks GBBopen to validate that all link definitions are consistent. Let's try it on our current random-walk application. Compile and load the latest changes in your \nobr{\code{tutorial-example.lisp}} file (including the new \nobr{\code{next-location}} and \nobr{\code{previous-location}} link slots). Then check link consistency: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(check-link-definitions)} ;; All link definitions are consistent. t gbbopen-user>} \end{example} % GBBopen reports that all link definitions are consistent. Suppose that we had forgotten to add the \nobr{\code{previous-location}} end of the link in our \nobr{\code{location}} unit-class definition. Edit the \nobr{\code{location}} unit-class definition in your \nobr{\code{tutorial-example.lisp}} file, adding the line \nobr{\code{\#+ignore}} immediately before the \nobr{\code{previous-location}} link-slot definition: % \W\supp \begin{example} \textcolor{darkergray}{% (define-unit-class location () (time x y (next-location :link (location previous-location :singular t) :singular t) \textcolor{black}{\#+ignore} (previous-location :link (location next-location :singular t) :singular t)) (:dimensional-values (time :point time) (x :point x) (y :point y)) (:initial-space-instances (known-world)))} \end{example} The \nobr{\code{\#+ignore}} read-time conditionalization tells Common Lisp to skip over the next form if \code{ignore} is not an element of the feature list \nobr{\code{*features*}}. By convention, \code{ignore} is never added to \nobr{\code{*features*}}, so nobr{\code{\#+ignore}} is a handy mechanism for temporarily ``commenting out'' a single form. Compile the now-defective definition (using \nobr{\code{C-c C-c}} in SLIME or \nobr{\code{C-c C-x}} in ELI) and then recheck link consistency: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(check-link-definitions)} Warning: The inverse of link slot next-location in unit class location refers to link slot previous-location which is not present in unit class location. nil gbbopen-user>} \end{example} % As expected, GBBopen alerts us to the problem. Remove the \nobr{\code{\#+ignore}} that we just added and comment out the \nobr{\code{:singular t}} portion of the inverse link-slot specification in \nobr{\code{next-location}}: % \W\supp \begin{example} \textcolor{darkergray}{% (define-unit-class location () (time x y (next-location :link (location previous-location\textcolor{black}{) ;} :singular t) :singular t) \textcolor{red}{#+ignore} (previous-location :link (location next-location :singular t) :singular t)) (:dimensional-values (time :point time) (x :point x) (y :point y)) (:initial-space-instances (known-world)))} \end{example} % Compile the again-defective definition (using \nobr{\code{C-c C-c}} in SLIME or \nobr{\code{C-c C-x}} in ELI) and then recheck link consistency: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(check-link-definitions)} Warning: Link slot next-location in unit class location incorrectly declares its inverse link slot previous-location in unit class location as not singular. nil gbbopen-user>} \end{example} % Once again, GBBopen has alerted us to the problem. Restore the \nobr{\code{:singular t}} portion of the inverse link-slot specification in \nobr{\code{next-location}} that we just commented out: % \W\supp \begin{example} \textcolor{darkergray}{% (define-unit-class location () (time x y (next-location :link (location previous-location\textcolor{red}{) ;} :singular t) :singular t) (previous-location :link (location next-location :singular t) :singular t)) (:dimensional-values (time :point time) (x :point x) (y :point y)) (:initial-space-instances (known-world)))} \end{example} % Then recompile and recheck link consistency: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(check-link-definitions)} ;; All link definitions are consistent. t gbbopen-user>} \end{example} \subsection*{Step 3: Create some links} Let's use our newly defined \nobr{\code{next-location}/\code{previous-location}} link to connect our \nobr{\code{location}} unit instances. Edit the \nobr{\code{random-walk-ks-function}} definition in your \nobr{\code{tutorial-example.lisp}} file, adding the trigger instance as a new \nobr{\code{:previous-location}} argument to \nobr{\textbf{make-instance}}: % \W\supp \begin{example} \textcolor{darkergray}{% (defun random-walk-ks-function (ksa) ;;; Move to the next (random) location in the world (let* ((trigger-instance (sole-trigger-instance-of ksa)) ;; The new time is one greater than the stimulus's time: (time (1+ (time-of trigger-instance)))) (cond ;; If the maximum time value (75) is reached, tell the user we've ;; walked too long: ((>= time 75) (format t "~2&Walked too long.~\%")) (t ;; The new location is +/- 10 of the stimulus's location: (let ((x (add-linear-variance (x-of trigger-instance) 10)) (y (add-linear-variance (y-of trigger-instance) 10))) (cond ;; Check that the new location is within the known-world ;; boundaries. If so, create the new location instance: ((and (<= -50 x 50) (<= -50 y 50)) (make-instance 'location :time time :x x :y y \textcolor{black}{:previous-location trigger-instance})) ;; Otherwise, tell the user that we've walked too far away: (t (format t "~2&Walked off the world: (~d, ~d).~\%" x y))))))))} \end{example} Compile the \nobr{\code{random-walk-ks-function}} (using \nobr{\code{C-c C-c}} in SLIME or \nobr{\code{C-c C-x}} in ELI) and then run the application: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(start-control-shell)} ;; Control shell 1 started Walked off the world: (55, 35). ;; No executable KSAs remain, exiting control shell ;; Control shell 1 exited: 66 cycles completed ;; Run time: 0.01 seconds ;; Elapsed time: 0 seconds :quiescence gbbopen-user>} \end{example} Let's describe a couple of \nobr{\code{location}} unit instances to check our work. First, the initial \nobr{\code{location}} unit instance: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-instance (find-instance-by-name 1 'location))} Location # Instance name: 1 Space instances: ((known-world)) Dimensional values: time: 0 x: 40 y: 60 Non-link slots: time: 0 x: 40 y: 60 Link slots: next-location: # previous-location: nil gbbopen-user>} \end{example} % Note that the \nobr{\code{next-location}} link slot points to the next \nobr{\code{location}} unit instance in our random walk. Let's describe that unit instance: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-instance (find-instance-by-name 2 'location))} Location # Instance name: 2 Space instances: ((known-world)) Dimensional values: time: 1 x: -10 y: 10 Non-link slots: time: 1 x: -10 y: 10 Link slots: next-location: # previous-location: # gbbopen-user>} \end{example} % The \nobr{\code{next-location}} link slot in \nobr{\code{location}} \code{2} points to the third \nobr{\code{location}} unit instance in our random walk and its \nobr{\code{previous-location}} link slot points back to the initial \nobr{\code{location}} unit instance. We can now follow the links to display the random walk: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(loop with location = (find-instance-by-name 1 'location) do (print location) while (setf location (next-location-of location)))} # # # # # # # # # # \textrm{\ldots{}} # # # # nil gbbopen-user>} \end{example} \subsection*{Step 4: Define a ``print walk'' KS} \bfindexit{define-ks}% \codeindex{:stop}% \codeindexit{quiescence-event}% \bfindexit{find-instance-by-name}% % Let's add a new KS, \nobr{\code{print-walk-ks}}, that displays the random walk once it is completed. Add the following KS to the end of your \nobr{\code{tutorial-example.lisp}} file: % \W\supp \begin{example} ;;; ==================================================================== ;;; Print-walk KS (defun print-walk-ks-function (ksa) ;;; Starting with the initial location instance, print the instance ;;; name and location of the walk (declare (ignore ksa)) (format t "~2\&The random walk:~\%") (let ((instance (find-instance-by-name 1 'location))) (while instance (format t "~s (~s ~s)~\%" (instance-name-of instance) (x-of instance) (y-of instance)) (setf instance (next-location-of instance)))) ;; Tell the Agenda Shell to exit: ':stop) (define-ks print-walk-ks :trigger-events ((quiescence-event)) :rating 100 :execution-function 'print-walk-ks-function) \end{example} % The \nobr{\code{print-walk-ks}} is triggered by a \nobr{\code{quiescence-event}}. Recall that the Agenda Shell signals that quiescence has occurred when no executable KSAs are available to be executed and then it continues for an additional KS-execution cycle in case any executable KSAs resulted from the quiescence event. So, \nobr{\code{print-walk-ks}} will be triggered once no \nobr{\code{random-walk-ks}} KSAs are triggered by newly created \nobr{\code{location}} unit instances. The \nobr{\code{print-walk-ks-function}} follows the \nobr{\code{next-location}/\code{previous-location}} link to display the walk. More importantly, the function returns the keyword symbol \code{:stop}. The Agenda Shell checks the value returned by a KS execution function for this special indicator and, if it is returned, the control shell is exited. If we did not return \code{:stop}, the \nobr{\code{print-walk-ks}} KS would be triggered and activated on the first \nobr{\code{quiescence-event}}, the KSA would execute, then the Agenda Shell would detect another quiescence condition, signal a new \nobr{\code{quiescence-event}}, and our application would print the random walk over and over again. Let's compile our latest changes and then run our application with the new \nobr{\code{print-walk-ks}} KS in place: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(start-control-shell)} ;; Control shell 1 started Walked off the world: (54, 15). The random walk: 1 (0 0) 2 (-6 9) 3 (-14 8) 4 (-5 6) 5 (-13 5) 6 (-11 13) 7 (-11 4) 8 (-17 8) 9 (-21 15) 10 (-12 14) \textrm{\ldots{}} 35 (40 28) 36 (50 22) 37 (49 12) 38 (47 10) ;; Explicit :stop issued by KS print-walk-ks ;; Control shell 1 exited: 41 cycles completed ;; Run time: 0.01 seconds ;; Elapsed time: 0 seconds :stop gbbopen-user>} \end{example} %% ======================================================================== %% Creating an Application \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-application} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \section{Creating a GBBopen Application} \label{sec:application}% GBBopen's \xreflink{Module Manager}{sec:module-manager} Facility provides mechanisms that make it easy to define and use your own GBBopen applications. \fndocrule This exercise shows you how to: \begin{tightitemize} \item Structure an application using the \xreflink{Module Manager Facility}{sec:module-manager} \item Define a REPL command for your application \item Compile and load your application using your REPL command \item Create and use an application-specific package \item Add an ``autorun'' action \end{tightitemize} \fndocrule \subsection*{Prerequisites} \begin{tightitemize} \item The \nobr{\code{tutorial-example.lisp}} file as modified thus far: % \W\supp\notpretop \begin{example} (in-package :gbbopen-user) (define-unit-class location () (time x y (next-location :link (location previous-location :singular t) :singular t) (previous-location :link (location next-location :singular t) :singular t)) (:dimensional-values (time :point time) (x :point x) (y :point y)) (:initial-space-instances (known-world))) (defmethod print-instance-slots ((location location) stream) (call-next-method) (when (and (slot-boundp location 'x) (slot-boundp location 'y)) (format stream " (~s ~s)" (x-of location) (y-of location)))) ;;; ==================================================================== ;;; Startup KS (defun startup-ks-function (ksa) (declare (ignore ksa)) ;; Create an initial location unit instance at (0,0): (make-instance 'location :time 0 :x 0 :y 0)) (define-ks startup-ks :trigger-events ((control-shell-started-event)) :execution-function 'startup-ks-function) ;;; ==================================================================== ;;; Initializations (run at Agenda Shell startup) (defun initializations (event-name &key &allow-other-keys) (declare (ignore event-name)) ;; Clean up any previous run: (delete-blackboard-repository) ;; Make a new known-world space instance: (make-space-instance '(known-world) :dimensions (dimensions-of 'location))) (add-event-function 'initializations 'control-shell-started-event ;; Initializations should be done first! :priority 100) ;;; ==================================================================== ;;; Random-walk KS (defun add-linear-variance (value max-variance) ;;; Returns a new random value in the interval ;;; [(- value max-variance), (+ value max-variance)] (+ value (- (random (1+ (* max-variance 2))) max-variance))) (defun random-walk-ks-function (ksa) ;;; Move to the next (random) location in the world (let* ((trigger-instance (sole-trigger-instance-of ksa)) ;; The new time is one greater than the stimulus's time: (time (1+ (time-of trigger-instance)))) (cond ;; If the maximum time value (75) is reached, tell the user we've ;; walked too long: ((>= time 75) (format t "~2&Walked too long.~\%")) (t ;; The new location is +/- 10 of the stimulus's location: (let ((x (add-linear-variance (x-of trigger-instance) 10)) (y (add-linear-variance (y-of trigger-instance) 10))) (cond ;; Check that the new location is within the known-world ;; boundaries. If so, create the new location instance: ((and (<= -50 x 50) (<= -50 y 50)) (make-instance 'location :time time :x x :y y :previous-location trigger-instance)) ;; Otherwise, tell the user that we've walked too far away: (t (format t "~2\&Walked off the world: (~d, ~d).~\%" x y)))))))) (define-ks random-walk-ks :trigger-events ((instance-created-event location)) :rating 100 :execution-function 'random-walk-ks-function) ;;; ==================================================================== ;;; Print-walk KS (defun print-walk-ks-function (ksa) ;;; Starting with the initial location instance, print the instance ;;; name and location of the walk (declare (ignore ksa)) (format t "~2\&The random walk:~\%") (let ((instance (find-instance-by-name 1 'location))) (while instance (format t "~s (~s ~s)~\%" (instance-name-of instance) (x-of instance) (y-of instance)) (setf instance (next-location-of instance)))) ;; Tell the Agenda Shell to exit: ':stop) (define-ks print-walk-ks :trigger-events ((quiescence-event)) :rating 100 :execution-function 'print-walk-ks-function) \end{example} \item The GBBopen environment setup using \nobr{\code{\var{\/}/initiate.lisp}} as described in Steps 1 and 2 of the \reflink{Enhancing Your Development Environment exercise}{sec:environment} \end{tightitemize} \subsection*{Step 1: Create your personal \code{gbbopen-modules} directory} Create a directory named \nobr{\code{gbbopen-modules}} in your \reflink{``homedir'' directory}{ref:your-homedir}. For example: % \W\supp \begin{example} \textcolor{darkergray}{% [~]\$ \textcolor{black}{mkdir gbbopen-modules} [~]\$} \end{example} % This is a special directory that is read by used by GBBopen to find applications when GBBopen is started using \nobr{\code{\var{\/}/initiate.lisp}}, as described in Steps 1 and 2 of the \reflink{Enhancing Your Development Environment exercise}{sec:environment}. \subsection*{Step 2: Create a module-definition file for the random-walk application} Recall that you created a directory to hold the random-walk application in Step 1 of \reflink{Working Within a File exercise}{sec:file}. I used these shell commands to create my directories: % \W\supp \begin{example} \textcolor{darkergray}{% [~]\$ mkdir tutorial [~]\$ cd tutorial [~/tutorial]\$ mkdir source [~/tutorial]\$} \end{example} % Then you created the \nobr{\code{tutorial-example.lisp}} file in this \code{source} subdirectory. We said that we would explain why we created the \code{source} directory in a later exercise. Well, later has arrived. Each GBBopen application is packaged in a directory that contains: \begin{tightitemize} \item a \nobr{\code{modules.lisp}} file that contains module definitions (loaded after the personal \nobr{code{gbbopen-init.lisp}} file if there is one in the user's ``homedir'') \item a directory named \code{source} containing all the source files for the module or application \item an optional \nobr{\code{commands.lisp}} file that specifies REPL commands for the module (loaded after the personal \nobr{\code{gbbopen-commands.lisp}} file if there is one in the user's ``homedir'') \item any additional directories or files useful to the application \end{tightitemize} % You already have the \code{source} directory and the \nobr{\code{tutorial-example.lisp}} source file. Next, we create the \nobr{\code{modules.lisp}} file for the application. (We will create a \nobr{\code{commands.lisp}} file for the random-walk application in Step 5.) Use your Common Lisp editor to create a new file named \nobr{\code{modules.lisp}} in the \code{tutorial} directory (just as you created the \nobr{\code{tutorial-example.lisp}} file in Step 2 of \reflink{Working Within a File exercise}{sec:file}. Note that this file is \textit{not\/} in the \code{source} subdirectory, but in the \code{tutorial} directory that contains the \code{source} subdirectory. Type the following two forms into the new \nobr{\code{modules.lisp}} file: % \W\supp \begin{example} (in-package :module-manager-user) (define-module :tutorial (:requires :agenda-shell-user) (:files "tutorial-example")) \end{example} % and then save the file. Recall that the \nobr{\code{in-package}} form specifies the Common Lisp package that is made current when the file is compiled or loaded. A \nobr{\code{modules.lisp}} file should always specify the \nobr{\code{:module-manager-user}} package as the first form in the file. The second form defines our application module, which we will name \code{:tutorial}. The \code{:requires} subform specifies that the \nobr{\code{:agenda-shell-user}} module must be compiled (if necessary) and then loaded before our \code{:tutorial} module. The \code{:files} subform specified the files that comprise the module. In our case, there is one file: \nobr{\code{tutorial-example.lisp}}. We leave off the \code{.lisp} file extension, as the \xreflink{Module Manager}{sec:module-manager} will add the appropriate source or compiled file extension for us. \subsection*{Step 3: Add the random-walk application to your personal \code{gbbopen-modules} directory} The \nobr{\code{gbbopen-modules}} directory in your ``homedir'' is expected to consist of directories each containing an individual GBBopen application. We could place the random-walk application directly in the \nobr{\code{gbbopen-modules}} directory by moving the \code{tutorial} directory there. However, it is generally more convenient to use a symbolic link to point to the actual application directory. For example, an application can be provided to a number of users by creating a symbolic link to the application directory in each user's \nobr{\code{gbbopen-modules}} directory. Unless you are running Windows, add the random-walk application to your \nobr{\code{gbbopen-applications}} by creating a symbolic link. For example: % \W\supp \begin{example} \textcolor{darkergray}{% [~]\$ \textcolor{black}{cd ~/gbbopen-modules/} [~/gbbopen-modules]\$ \textcolor{black}{ln -s ~/tutorial .} [~/gbbopen-modules]\$} \end{example} \subsubsection*{Windows users} Instead of creating a symbolic link, GBBopen also supports a special ``pseudo symbolic-link'' file that can be used with Windows. This is simply a text file with the file extension \code{.sym} that contains the target directory path as the sole line in the file. For example, you could create the file \nobr{\code{tutorial.sym}} in your \nobr{\code{gbbopen-modules}} directory with: % \W\supp \begin{example} C:\bkslash{}tutorial\bkslash \end{example} \T\vskip -12pt % as the sole line in the file. \subsection*{Step 4: Try the \code{:tutorial} module definition} Let's try out our module definition. Exit Common Lisp and start a fresh Common Lisp session. If you have set up your environment according to the \reflink{Enhancing Your Development Environment exercise}{sec:environment}, the following files should be loaded: % \W\supp \begin{smallexample} \textcolor{darkergray}{% \textrm{\ldots{}} ;; Loading \var{\/}/shared-init.lisp ;; Loading \var{\/}/initiate.lisp ;; GBBopen is installed in \var{\/} ;; Your "home" directory is \var{\/} ;; Loading \var{\/}/extended-repl.lisp ;; Loading \var{\/}/commands.lisp ;; Loading \var{\/}/gbbopen-modules-directory.lisp ;; No shared module command definitions were found in \var{\/}/gbbopen-modules/. ;; No personal module command definitions were found in \var{\/}/gbbopen-modules/. cl-user>} \end{smallexample} Note that some basic GBBopen initialization files have been loaded for us as well as GBBopen's command definitions and any command definitions for applications linked from our \nobr{\code{\var{\/}/gbbopen-modules/}} directory. No module definitions have been defined yet, and GBBopen itself (or even the Module Manager Facility) were not loaded by \nobr{\code{\var{\mbox{\/}}/initiate.lisp}.} Now, instead of loading the \nobr{\code{:agenda-shell-user}} module, let's load only the \nobr{\code{:module-manager-user}} module: % \W\supp \begin{smallexample} \textcolor{darkergray}{% cl-user> \textcolor{black}{:module-manager-user} ;; Loading \var{\/}/startup.lisp \textrm{\ldots{}} ;; Loading \var{\/}/\var{\/}/module-manager/module-manager-user.fasl ;; Loading \var{\/}/modules.lisp ;; No shared module definitions were found in \var{\/}/gbbopen-modules/. ;; Loading personal module definitions from \var{\/}/gbbopen-modules/... ;; Loading \var{\/}/tutorial/modules.lisp module-manager-user>} \end{smallexample} % Note that when the Module Manager was loaded as part of loading the \nobr{\code{:module-manager-user}} module, the module definitions for our personal GBBopen modules were loaded automatically. (In this case, the \nobr{\code{\var{\/}/tutorial/modules.lisp}} file.) \REPLindex{:cm}% % Now that we have defined our \code{:tutorial} module, we can use the \nobr{\textbf{compile-module}} REPL command, \code{:cm}, to compile (if needed) and load it. Before doing so, however, let's explore what is happening when \code{:tutorial} is being compiled by instructing the Module Manager not to create new compiled-file directories automatically (its default behavior). Enter the following in the REPL: % \W\supp \begin{example} \textcolor{darkergray}{% module-manager-user> \textcolor{black}{(setf *automatically-create-missing-directories* nil)} nil module-manager-user>>} \end{example} Now compile and load the \code{:tutorial} module: % \W\supp \begin{example} \textcolor{darkergray}{% module-manager-user> \textcolor{black}{:cm :tutorial} ;; Loading \var{\/}/\var{\/}/tools/preamble.fasl \textrm{\ldots{}} ;; Loading .../gbbopen/control-shells/agenda-shell-user.fasl Error: Directory \var{\/}/tutorial/\var{\/}/ in module :tutorial does not exist. Restart actions (select using :c n): 0: Create this directory. 1: Create this directory and any future missing directories. module-manager-user>>} \end{example} % The \code{:requires} in our \code{:tutorial} module definition causes the \nobr{\code{:agenda-shell-user}} module (and before that, all its required modules) to be loaded for us. Then the Module Manager signals a continuable error, telling us that the directory to hold the compiled application files for \code{:tutorial} does not exist. Compiled files are put in a Common Lisp and platform-specific subdirectory, \nobr{\code{<\var{platform-dir>\/}}}, in our \code{tutorial} directory that mirrors the \code{source} directory. This organization makes it easy to use the application with a number of Common Lisp implementations and on a file system shared with a number of different hosts and operating systems. By default, the Module Manager would have created the missing \nobr{\var{\/}/tutorial/\var{\/}/} directory for us automatically (and continued compiling our \code{:tutorial} module), but we disabled automatic directory creation by setting \nobr{/textbf{*automatically-create-missing-directories*}} to \nil. We still could have avoided this continuable error by providing the \nobr{\code{:create-dirs}} option to the \code{:cm} command: % \W\supp \begin{example} \textcolor{darkergray}{% module-manager-user> :cm :tutorial :create-dirs} \end{example} % to allow the Module Manager to create the \nobr{\code{<\var{platform-dir>\/}}} subdirectory automatically for us. Since we did not do this, we can still continue from the error: % \W\supp\notpretop \begin{smallexample} \textcolor{darkergray}{% Restart actions (select using :c n): 0: Create this directory. 1: Create this directory and any future missing directories. module-manager-user>> \textcolor{black}{:c 0} ;; Compiling file \var{\/}/tutorial/source/tutorial-example.lisp ;; Loading \var{\/}/tutorial/\var{\/}/tutorial-example.fasl module-manager-user>} \end{smallexample} At this point, we've compiled and loaded our \code{:tutorial} application module. \subsection*{Step 5: Create a command-definition file for the random-walk application} It is convenient to define a REPL command to compile and load you application (and any required GBBopen modules). Use your Common Lisp editor to create a new file named \nobr{\code{commands.lisp}} in the \code{tutorial} directory. Type the following two forms into the new \nobr{\code{commands.lisp}} file: % \bfindex{define-repl-command}% % \W\supp \begin{example} (in-package :common-lisp-user) (define-repl-command :tutorial (\&rest options) "Compile and load the Random-Walk Tutorial Application Module" (startup-module :tutorial options :gbbopen-user)) \end{example} % and then save the file. A \nobr{\code{commands.lisp}} file should always specify the \nobr{\code{:common-lisp-user}} package as the first form in the file. The \nobr{\textbf{define-repl-command}} form adds a REPL command, named \nobr{\code{:tutorial}}, to the set of handy REPL commands. The \nobr{\textbf{startup-module}} call does all the work associated with executing the command. The first argument to \nobr{\textbf{startup-module}} specifies that the \code{:tutorial} module will be compiled (if necessary) and then loaded by the \xreflink{Module Manager}{sec:module-manager} when the \code:{tutorial} command is issued. The second, \code{options}, argument passes any options given with the command to a \nobr{\textbf{compile-module}} call that is performed by \nobr{\textbf{startup-module}}. The third argument, \nobr{\code{:gbbopen-user}} is optional and specifies that the REPL's current package should be changed to \nobr{\code{:gbbopen-user}} after the \code{:tutorial} module is loaded. \subsection*{Step 6: Try the \code{:tutorial} command} Let's try our command definition. Exit Common Lisp and start a fresh Common Lisp session. If you have set up your environment according to the \reflink{Enhancing Your Development Environment exercise}{sec:environment}, the following files should be loaded: % \W\supp \begin{smallexample} \textcolor{darkergray}{% \textrm{\ldots{}} ;; Loading \var{\/}/shared-init.lisp ;; Loading \var{\/}/initiate.lisp ;; GBBopen is installed in \var{\/} ;; Your "home" directory is \var{\/} ;; Loading \var{\/}/extended-repl.lisp ;; Loading \var{\/}/commands.lisp ;; Loading \var{\/}/gbbopen-modules-directory.lisp ;; No shared module command definitions were found in \var{\/}/gbbopen-modules/. ;; Loading personal module command definitions from \var{\/}/gbbopen-modules/... ;; Loading \var{}/gbbopen-modules/tutorial/commands.lisp cl-user>} \end{smallexample} % Note that the \nobr{\code{commands.lisp}} file from the \code{tutorial} directory has been loaded by \nobr{\code{\var{\/}/initiate.lisp}}. Now, we can compile and load the \code{:tutorial} module by simply issuing the \code{:tutorial} REPL command: % \W\supp \begin{smallexample} \textcolor{darkergray}{% cl-user> \textcolor{black}{:tutorial} ;; Loading \var{\/}/startup.lisp \textrm{\ldots{}} ;; Loading \var{\/}/tutorial/\var{\/}/tutorial-example.fasl gbbopen-user>} \end{smallexample} % With the command definition in place, we are able to compile and load our random-walk application by issuing a single command, \code{:tutorial}. Note a potential continuable error due to a missing \nobr{\code{\var{\/}}} subdirectory can always be avoided by providing the \nobr{\code{:create-dirs}} option to the \code{:tutorial} command: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> :tutorial :create-dirs} \end{example} % to allow the Module Manager to create the \nobr{\code{\var{\/}}} subdirectory automatically for us. However, since we created \nobr{\code{\var{\/}}} in Step 4, we did not need to specify the \nobr{\code{:create-dirs}} option again in this step. \subsection*{Step 7: Controlling automatic creation of missing subdirectories} \bfindex{*automatically-create-missing-directories*} % If you prefer, you can specify that the Module Manager not create missing \nobr{\code{\var{\/}}} directories and subdirectories automatically but, instead, signal an continuable error if a directory is missing (and the \nobr{\code{:create-dirs}} \textbf{compile-module} option was not specified). As we have seen, this behavior is controlled by the value of the symbol \nobr{\textbf{*automatically-create-missing-directories*}}, which is in the \nobr{\code{:common-lisp-user}} package and is set to \code{t} by default. If you would like to turn-off automatic directory creation by the Module Manager, add the following form to your \nobr{\code{shared-init.lisp}} file (in your ``homedir'' directory): % \W\supp \begin{example} (defparameter *automatically-create-missing-directories* nil) \end{example} I prefer to have the Module Manager generate the continuable error if it needs to create a \nobr{\code{\var{\/}}} directory and I didn't specify \nobr{\code{:create-dirs}} when compiling a new module, so I set \nobr{\textbf{*automatically-create-missing-directories*}} to \code{t} in my \nobr{\code{shared-init.lisp}} file. \subsection*{Step 8: Create and use an application-specific package} We have been developing our random-walk application in GBBopen's \nobr{\code{:gbbopen-user}} package. The \nobr{\code{:gbbopen-user}} package is convenient, and we could continue using it. However, if we develop multiple GBBopen applications in the \nobr{\code{:gbbopen-user}} package and load several of them at the same time, symbol-name clashes could occur. To eliminate this possibility, we can create our own package for the random-walk application. First, let's determine what packages are being used by GBBopen's \nobr{\code{:gbbopen-user}} package. Evaluate the following: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(package-use-list :gbbopen-user)} (# # # # # #) gbbopen-user>} \end{example} \subsubsection*{Change \code{tutorial-example.lisp}} % We want our new \code{:tutorial} package to use the same packages that the \nobr{\code{:agenda-shell-user}} package used. Edit your \nobr{\code{tutorial-example.lisp}} file and replace the \nobr{\code{:gbbopen-user}} package specification: % \W\supp \begin{example} \textcolor{red}{(in-package :gbbopen-user)} \end{example} % with the following: % \W\supp\notpretop \begin{example} (eval-when (:compile-toplevel :load-toplevel :execute) (unless (find-package :tutorial) (defpackage :tutorial (:use :common-lisp :module-manager :gbbopen-tools :gbbopen :portable-threads :agenda-shell)))) (in-package :tutorial) \end{example} % and save the file. Note the use of \nobr{\code{eval-when}} in the first form above. Normally, top-level forms in a file are not evaluated at compile time. In this case, however, we want to define the \code{:tutorial} package when needed, whether the file is being compiled or loaded. The \nobr{\code{eval-when}} special operator with the three situations (\nobr{\code{:compile-toplevel}}, \nobr{\code{:load-toplevel}}, and \code{ :execute}) provides this behavior to the forms that it contains. Such \nobr{\code{eval-when}} forms are a standard Common Lisp idiom for compile-time and load-time evaluation. \subsubsection*{An application feature} % In my applications, I also add a feature to Common Lisp's \code{*features*} list to indicate that the application has been fully loaded. To do this, add the following at the end of your \nobr{\code{tutorial-example.lisp}} file: % \W\supp \begin{example} (pushnew :tutorial *features*) \end{example} % and save the file. \subsubsection*{Change \code{commands.lisp}} % Next, edit your \nobr{\code{commands.lisp}} file and delete the \nobr{\code{:gbbopen-user}} package-name argument to \nobr{\textbf{startup-module}}: % \W\supp \begin{example} \textcolor{darkergray}{% (define-repl-command :tutorial (\&rest options) "Compile and load the Random-Walk Tutorial Application Module" (startup-module :tutorial options \textcolor{red}{:gbbopen-user}))} \end{example} % and add the package-name \code{:tutorial} in its place: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% (define-repl-command :tutorial (\&rest options) "Compile and load the Random-Walk Tutorial Application Module" (startup-module :tutorial options \textcolor{black}{:tutorial}))} \end{example} % Save the file. \subsubsection*{Change \code{modules.lisp}} \bfindex{describe-module}% % Finally, we no longer need the \nobr{\code{:gbbopen-user}} package that is created by requiring the \nobr{\code{:agenda-shell-user}} module. Let's take a closer look at the \code{:tutorial} module that we defined: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-module :tutorial)} Module :tutorial (loaded) Requires: (:agenda-shell-user) Fully expanded requires: (:module-manager :module-manager-user :portable-threads :gbbopen-tools :gbbopen-core :polling-functions :queue :agenda-shell :os-interface :gbbopen-user :agenda-shell-user) Source directory: \var{\/}/tutorial/source/ Compiled directory: \var{\/}/\var{\/}/ Forces recompile date: None Files: Mar 24 06:02 tutorial-example gbbopen-user>} \end{example} % Although we only specified that the \nobr{\code{:agenda-shell-user}} module was required, our \code{:tutorial} module implicitly requires a number of packages that are required by the \nobr{\code{:agenda-shell-user}} module and its implicitly required packages. These are shown as the ``Fully expanded requires'' value. If we look at the details of the \nobr{\code{:agenda-shell-user}} module we see: % \W\supp \begin{example} \textcolor{darkergray}{% gbbopen-user> \textcolor{black}{(describe-module :agenda-shell-user)} Module :agenda-shell-user (loaded) Requires: (:agenda-shell :gbbopen-user) Fully expanded requires: (:module-manager :module-manager-user :portable-threads :gbbopen-tools :gbbopen-core :polling-functions :queue :agenda-shell :os-interface :gbbopen-user) Source directory: \var{\/}/source/gbbopen/control-shells/ Compiled directory: \var{\/}/\var{\/}/gbbopen/control-shells/ Forces recompile date: None Files: Mar 23 12:27 agenda-shell-user gbbopen-user>} \end{example} % Note that the \nobr{\code{:agenda-shell-user}} module requires two modules: \nobr{\code{:agenda-shell}} and \nobr{\code{:gbbopen-user}}. We can eliminate the loading of the \nobr{\code{:gbbopen-user}} module by editing our \nobr{\code{modules.lisp}} file and delete \nobr{\code{:agenda-shell-user}} in the \code{:requires} option in our \code{:tutorial} module definition: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% (in-package :module-manager-user) (define-module :tutorial (:requires \textcolor{red}{:agenda-shell-user}) (:files "tutorial-example"))} \end{example} % and replace it with \nobr{\code{:agenda-shell}}: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% (in-package :module-manager-user) (define-module :tutorial (:requires \textcolor{black}{:agenda-shell}) (:files "tutorial-example"))} \end{example} % Save the file. \subsection*{Step 9: Verify your changes} Let's make sure that everything is still working. Exit Common Lisp and start a fresh Common Lisp session. Next enter the \code{:tutorial} REPL command: % \W\supp \begin{smallexample} \textcolor{darkergray}{% cl-user> \textcolor{black}{:tutorial} ;; Loading \var{\/}/startup.lisp \textrm{\ldots{}} ;; Loading \var{\/}/tutorial/\var{\/}/tutorial-example.fasl tutorial>} \end{smallexample} % Note that we are now in our newly defined \code{:tutorial} package. We should still be able to run the random-walk application: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% tutorial> \textcolor{black}{(start-control-shell)} ;; Control shell 1 started Walked off the world: (56, 38). The random walk: 1 (0 0) 2 (-1 -1) 3 (-8 -10) 4 (0 -2) 5 (-5 2) 6 (3 11) 7 (8 5) 8 (12 2) 9 (3 12) 10 (10 4) \textrm{\ldots{}} 55 (50 40) 56 (42 47) 57 (47 41) ;; Explicit :stop issued by KS print-walk-ks ;; Control shell 1 exited: 60 cycles completed ;; Run time: 0.01 seconds ;; Elapsed time: 0 seconds :stop gbbopen-user>} \end{example} \subsubsection*{Installation-wide sharing} % \index{shared-gbbopen-modules@\code{shared-gbbopen-modules}, installation-wide application sharing}% GBBopen also has an \nobr{\code{\var{\/}/shared-gbbopen-modules}} directory. As with our personal \nobr{\code{gbbopen-modules}} directory, this \nobr{\code{shared-gbbopen-modules}} directory is assumed to contain symbolic links (or ``pseudo-symbolic-link'' files on Windows) to individual GBBopen module directory trees. This is the recommended mechanism for installation-wide managing and sharing of modules and applications, and if we wanted to share our random-walk application to everyone using our GBBopen installation, we could create our symbolic link (or ``pseudo-symbolic-link'' file) in the \nobr{\code{shared-gbbopen-modules}} directory. \subsection*{Step 10: Add an ``autorun'' action} Suppose we want the random-walk application to run automatically when it is loaded. You could simply add: % \W\supp \begin{example} \textcolor{darkergray}{% (start-control-shell)} \end{example} % as a top-level form at the end of your \nobr{\code{tutorial-example.lisp}} file. The problem with this is that sometimes you may want to compile and load the application without running it. GBBopen's Module Manager Facility supports a convention that makes it easy to conditionalize load-time action execution via the value of \nobr{\textbf{*autorun-modules*}}. Normally, \nobr{\textbf{*autorun-modules*}} will be true, but it can be set to \nil{} when a module is loaded with the \code{:noautorun} option. Add the following at the end of your \nobr{\code{tutorial-example.lisp}} file: % \W\supp \begin{example} (when *autorun-modules* (format t "~\{~\&~s~\%~\}" (multiple-value-list (start-control-shell)))) \end{example} % and save the file. We could have simply called \nobr{\textbf{start-control-shell}} when \nobr{\code{*autorun-modules*}} is true, but then we would not be able to see what values are returned by the Agenda Shell. The \code{format} form above prints each returned value on a separate output line. By convention, the ``autorun'' form is placed at the very end of the file, immediately after the form to add \code{:tutorial} to Common Lisp's \code{*features*}. This is so that the \code{:tutorial} feature will be present during the ``autorun'' execution and thereafter---even if an error occurs when executing the ``autorun'' form. \subsection*{Step 11: Try it out} Enter the \code{:tutorial} REPL command. The modified \nobr{\code{tutorial-example.lisp}} file should compile and load, followed by a random walk: % \W\supp \begin{smallexample} \textcolor{darkergray}{% tutorial> \textcolor{black}{:tutorial} ;; Compiling \var{\/}/tutorial/source/tutorial-example.lisp ;; Loading \var{\/}/tutorial/\var{\/}/tutorial-example.fasl ;; Control shell 1 started Walked off the world: (11, -51). The random walk: 1 (0 0) 2 (6 7) 3 (6 3) 4 (3 -4) 5 (10 -13) \textrm{\ldots{}} 21 (-5 -46) 22 (-9 -39) 23 (1 -33) 24 (8 -41) ;; Explicit :stop issued by KS print-walk-ks ;; Control shell 1 exited: 27 cycles completed ;; Run time: 0.01 seconds ;; Elapsed time: 0 seconds :stop tutorial>} \end{smallexample} Let's try it again: % \W\supp \begin{smallexample} \textcolor{darkergray}{% tutorial> \textcolor{black}{:tutorial} tutorial>} \end{smallexample} % This time, nothing happened. Why? Since no source files were modified, the Module Manager knows that the latest compiled files for the \code{:tutorial} module and its required modules have all been loaded. So, because the \nobr{\code{tutorial-example}} file is not loaded, its ``autorun'' conditional form is not evaluated. We can tell the Module Manager to always reload the \nobr{\code{tutorial-example}} file by editing our \nobr{\code{modules.lisp}} file and adding the \code{:reload} file option to the \code{:tutorial} module definition: % \W\supp \begin{example} \textcolor{darkergray}{% (define-module :tutorial (:requires :agenda-shell) (:files \textcolor{black}{(}"tutorial-example" \textcolor{black}{:reload})))} \end{example} % Note that once a file has one or more options, the file name and its options are enclosed in parentheses. Save the modified \nobr{\code{modules.lisp}} file. Now, if you try the \code{:tutorial} command, the \nobr{\code{tutorial-example}} file will always be loaded and its ``autorun'' form evaluated: % \W\supp \begin{smallexample} \textcolor{darkergray}{% tutorial> \textcolor{black}{:tutorial} ;; Loading \var{\/}/tutorial/modules.lisp ;; Loading \var{\/}/tutorial/\var{\/}/tutorial-example.fasl ;; Control shell 1 started \textrm{\ldots{}} tutorial>} \end{smallexample} % The Module Manager noticed our updated \nobr{\code{modules.lisp}} file and loaded it, redefining the \code{:tutorial} module definition, and then followed our \code{:reload} specification. Let's try the \code{:tutorial} command one more time, just to be certain that \code{:reload} is happening when no files have been updated: % \W\supp \begin{smallexample} \textcolor{darkergray}{% tutorial> \textcolor{black}{:tutorial} ;; Loading \var{\/}/tutorial/\var{\/}/tutorial-example.fasl ;; Control shell 1 started \textrm{\ldots{}} tutorial>} \end{smallexample} \subsubsection*{Module Manager propagation} The Module Manager's \nobr{\textbf{compile-module}} function also accepts a \code{:reload} option, so we might be tempted to simply add that option when we specify our \code{:tutorial} command: % \W\supp \begin{example} \textcolor{darkergray}{% tutorial> :tutorial :reload ;; Loading \var{\/}/\var{\/}/module-manager/module-manager.fasl ;; Loading \var{\/}/\var{\/}/module-manager/module-manager-user.fasl \textrm{\ldots{}} ;; Control shell 1 started \textrm{\ldots{}} tutorial>} \end{example} % however, this would also reload all the files of very module required by the \code{:tutorial} module, as the \nobr{\textbf{startup-module}} function that we used in defining our \nobr{\code{:tutorial}} REPL command always adds a \nobr{\code{:propagate}} option to the options that we provide to the command. We could override (cancel) this propagation behavior by adding the \nobr{\code{:nopropagate}} option when we specify our \nobr{\code{:tutorial}} command: % \W\supp\notpretop \begin{smallexample} \textcolor{darkergray}{% tutorial> :tutorial :reload :nopropagate ;; Loading \var{\/}/tutorial/\var{\/}/tutorial-example.fasl ;; Control shell 1 started \textrm{\ldots{}} tutorial>} \end{smallexample} % which would eliminate reloading of all but the files in the \code{:tutorial} module. Since we only have one file (at the moment) specified in our \code{:tutorial} module definition, this behavior is equivalent, but less convenient, than specifying the \code{:reload} file option to our \nobr{\code{tutorial-example}} file in the module definition. If we had a number of files, however, we would probably only want the last one reloaded every time. \subsubsection*{Module Manager memories} The Module Manager also remembers the last module (and any provided options, such as \nobr{\code{:create-dirs}}) that was specified to a \code{:cm} (textbf{compile-module}) or \code{:lm} (\nobr{\textbf{load-module}}) command. The \nobr{\textbf{startup-module}} function that we used in defining our \code{:tutorial} REPL command performs an implicit \code{:cm} command for us, so we could have alternatively typed the \code{:cm} REPL command rather than \code{:tutorial} once we have issued the first \code{:tutorial} REPL command: % \W\supp \begin{smallexample} \textcolor{darkergray}{% tutorial> \textcolor{black}{:cm} ;; :cm :tutorial :propagate ;; Loading \var{\/}/tutorial/\var{\/}/tutorial-example.fasl ;; Control shell 1 started \textrm{\ldots{}} tutorial>} \end{smallexample} % Just the thing for lazy typists like me! Note that the \code{:cm} command echos the full (implicitly completed) command with the remembered module name and any remembered options. \subsection*{Step 12: Add a second source file} Reloading the entire \nobr{\code{tutorial-application}} file in order to evaluate our ``autorun'' form is still a bit heavy handed. There are two ways to improve this situation, and both involve placing the ``autorun'' form in a separate file. Use your Common Lisp editor to create a new file named \nobr{\code{autorun.lisp}} in the \code{source} subdirectory of the \code{tutorial} directory. Type (or copy) the following two forms into the new \nobr{\code{autorun.lisp}} file: % \W\supp \begin{example} (in-package :tutorial) (when *autorun-modules* (format t "~\{~\&~s~\%~\}" (multiple-value-list (start-control-shell)))) \end{example} % and save the file. Next, edit the \nobr{\code{tutorial-example.lisp}} file and remove the ``autorun'' form from the end of the file: % \W\supp \begin{example} \textcolor{red}{% (when *autorun-modules* (format t "~\{~\&~s~\%~\}" (multiple-value-list (start-control-shell))))} \end{example} % Save the file. We could define a second random-walk application module, say \nobr{\code{:run-tutorial}}, in our \nobr{\code{modules.lisp}} file that contains the new \nobr{\code{autorun.lisp}} file and requires our current \code{:tutorial} module. This definition would look like: % \W\supp \begin{example} \textcolor{darkergray}{% (define-module :run-tutorial (:requires :tutorial (:files ("autorun" :reload))))} \end{example} % However, we can avoid creating a new module by simply adding the \code{autorun} file to our current \code{:tutorial} module definition. Edit the \nobr{\code{modules.lisp}} file in the \code{tutorial} directory and remove the \code{:reload} file option from the \nobr{\code{:tutorial-example}} file specification: % \W\supp \begin{example} \textcolor{darkergray}{% (define-module :tutorial (:requires :agenda-shell) (:files \textcolor{red}{(}"tutorial-example" \textcolor{red}{:reload)}))} \end{example} % and then add a new line for the \code{autorun} file with the \code{:reload} file option: % \W\supp\notpretop \begin{example} \textcolor{darkergray}{% (define-module :tutorial (:requires :agenda-shell) (:files "tutorial-example" \textcolor{black}{("autorun" :reload)}))} \end{example} % and save the file. \subsection*{Step 13: One last check} Let's double-check that everything is working: % \W\supp \begin{smallexample} \textcolor{darkergray}{% tutorial> \textcolor{black}{:cm} ;; :cm :tutorial :propagate ;; Loading \var{\/}/tutorial/modules.lisp ;; Compiling \var{\/}/tutorial/source/tutorial-example.lisp ;; Loading \var{\/}/tutorial/\var{\/}/tutorial-example.fasl ;; Compiling \var{\/}/tutorial/source/autorun.lisp ;; Loading \var{\/}/tutorial/\var{\/}/autorun.fasl ;; Control shell 1 started \textrm{\ldots{}} tutorial>} \end{smallexample} % The modified files are compiled and loaded and the Agenda Shell is invoked. Let's try it one last time, just to be sure that the application runs when no files have been modified: % \W\supp \begin{smallexample} \textcolor{darkergray}{% tutorial> \textcolor{black}{:cm} ;; :cm :tutorial :propagate ;; Loading \var{\/}/tutorial/\var{\/}/autorun.fasl ;; Control shell 1 started \textrm{\ldots{}} tutorial>} \end{smallexample} Congratulations! It's time to move to the next exercise. %% ======================================================================== %% Multiple Walkers \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-multiple-walkers} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \section{Multiple Walkers} \label{sec:multiple-walkers}% \T\medskip \T\fndocrule\\ \T\begin{center} \textcolor{darkergray}{\textsf{\textbf{Coming later $\ldots$\\~\\ Please continue with the next exercise.}}} \T\end{center} \T\fndocrule %% ======================================================================== %% A Dimensional Detour \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-dimensional-detour} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \section{A Dimensional Detour} \label{sec:detour}% %\begin{center} %\textcolor{caution}{[Construction ahead\textrm{\ldots{}}]} %\end{center} \T\medskip \T\fndocrule\\ \T\begin{center} \textcolor{darkergray}{\textsf{\textbf{Coming later, exploration of unbound dimensional values, intersection of unit instance, space instance, and retrieval dimensionality, $\ldots$\\~\\ Please continue with the next exercise.}}} \T\end{center} \T\fndocrule %\subsection*{Step 1: Unbound dimensional values} %% ======================================================================== %% More to come... \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-more-to-come} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \W\section{More to come\ldots} \T\section{More to come$\ldots$} \T\medskip \T\fndocrule\\ \T\begin{center} \textcolor{darkergray}{\textsf{\textbf{Additional exercises will be added soon.}}} \T\end{center} \T\fndocrule %% ======================================================================== %% The Completed Application \T\markright{}% \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-completed-application} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\renewcommand{\headrulewidth}{0pt} \section{The Completed Application} \label{sec:completed}% The complete, finished code for the random walk application is in the \nobr{\code{tutorial.lisp}} file in the \nobr{\code{source/gbbopen/examples/}} directory in the GBBopen distribution and at \xsitelink{\nobr{\code{http://gbbopen.org/svn/GBBopen/trunk/source/gbbopen/examples/tutorial.lisp}}}{http://gbbopen.org/svn/GBBopen/trunk/source/gbbopen/examples/tutorial.lisp}. %% ======================================================================== %% Index \T\markright{} \T\pagestyle{plain} \T\cleardoublepage \W\xname{tutorial-index} \T\pagestyle{fancy} \T\thispagestyle{fancybottom} \T\fancyhead[er,ol]{} \T\small \T\addcontentsline{toc}{section}{\textbf{Index}}% \T\twocolumn %\printindex standin: \htmlonly{\HlxSection{-5}{}*{\indexname}\label{hlxindex}}% \texonly{\section*{\indexname}}% % Page references are shown in \textbf{bold} when they refer to the definition or main source of information on the entry. A page reference that is given in \textit{\textcolor{verydarkgreen}{green italics}\/} indicates an instructive example of the use of that entity. % \W\\~\\ \T\bigskip % \texorhtml{\input{tutorial.ind}}{\htmlprintindex} %% ======================================================================== \end{document} \code