Basic LISP Techniques - Part4

Download as pdf or txt
Download as pdf or txt
You are on page 1of 13

Chapter 4

Interfaces

One of the strong points of modern commercially-supported CL environments is their flex-


ibility in working with other environments. In this chapter we present a sampling of some
interfaces and packages which come in handy for developing real-world CL applications and
connecting them to the outside environment. Some of the packages covered in this chapter
are commercial extensions to Common Lisp as provided with Franz Inc’s Allegro CL, and
some of them are available as open-source offerings.

4.1 Interfacing with the Operating System


One of the most basic means of interacting with the outside world is simply to invoke
commands at the operating system level, in similar fashion to Perl’s “system” command.
In Allegro CL, one way to accomplish this is with the function excl.osi:command-output1 .
This command takes a string which represents a command (possibly with arguments) to be
executed through an operating system shell, such as the “C” shell on Unix:

(excl.osi:command-output "ls")

When invoked in this fashion, this function returns the the Standard Output from the shell
command as a string. Alternatively, you can specify that the output go to another location,
such as a file, or a variable defined inside the CL session.
When using excl.osi:command-output, you can also specify whether CL should wait
for the shell command to complete, or should simply go about its business and let the shell
command complete asynchronously.

4.2 Foreign Function Interface


Allegro CL’s foreign function interface allows you to load compiled libraries and object
code from C, C++, Fortran, and other languages, and call functions defined in this code
1
You must have a fully patched ACL 6.2 or later ACL version to access the excl.osi functionality (if your
machine can access the internet, you can bring your ACL installation to the current patch level with the
function sys:update-allegro). Previous ACL versions have a function excl:run-shell-command which
operates similarly to excl.osi:command-output.

55
56 CHAPTER 4. INTERFACES

as if they were defined natively in CL. This technique requires a bit more setup work than
using simple shell commands with excl.osi:command-output, but it will provide better
performance and more transparent operation once it is set up.

4.3 Interfacing with Corba


Corba, or the Common Object Request Broker Architecture, is an industry-standard mech-
anism for connecting applications written in different object-oriented languages.
To use Corba, you define a standard interface for your application in a language called
Interface Definition Language (Corba IDL), and each application must compile the standard
interface and implement either a servant or a client for the interface. In this manner, servants
and clients can communicate through a common protocol, regardless of what language they
are written in.
In order to use Corba with CL requires a Corba IDL compiler implemented in CL,
as well as a run-time ORB, or Object Request Broker, to handle the actual client/server
communications. Several such Corba IDL compilers and ORBs are available for CL. An
example of such a system is Orblink, which is available as an optional package for Allegro
CL. Orblink consists of a complete Corba IDL compiler, and a CL-based ORB, which runs
as a CL thread rather than as a separate operating system process.
Using Orblink to integrate a CL application to a Corba client/server environment is a
straightforward process. To start, you simply obtain the Corba IDL files for the desired
interface, and compile them in CL with the command corba:idl. This will automatically
define a set of classes in CL corresponding to all the classes, methods, etc. which make up
the interface.
To use CL as a client, you can now simply make instances of the desired classes, and use
them to call the desired methods. The Orblink ORB will take care of communicating these
method calls across the network, where they will be invoked in the actual process where the
corresponding servant is implemented (which could be on a completely different machine
written in a completely different language).
To use CL as a servant, you must implement the relevant interfaces. This consists of
defining classes in CL which inherit from the “stub” servant classes automatically defined by
the compilation of the Corba IDL, then defining methods which operate on these classes,
and which perform as advertised in the interface. Typically, a Corba interface will only
expose a small piece of an entire application, so implementing the servant classes would
usually represent a small chore relative to the application itself.

4.4 Custom Socket Connections


Just about every CL implementation provides a mechanism to program directly with net-
work sockets, in order to implement “listeners” and network client applications. As a
practical example, Figure 4.1 shows a Telnet server, written by John Foderaro of Franz
Inc., in 22 lines of code in Allegro CL. This Telnet server will allow a running CL process to
accept Telnet logins on port 4000 on the machine on which it is running, and will present
4.4. CUSTOM SOCKET CONNECTIONS 57

(defun start-telnet (&optional (port 4000))


(let ((passive (socket:make-socket :connect :passive
:local-host "127.1"
:local-port port
:reuse-address t)))
(mp:process-run-function
"telnet-listener"
#’(lambda (pass)
(let ((count 0))
(loop
(let ((con (socket:accept-connection pass)))
(mp:process-run-function
(format nil "tel~d" (incf count))
#’(lambda (con)
(unwind-protect
(tpl::start-interactive-top-level
con
#’tpl::top-level-read-eval-print-loop
nil)
(ignore-errors (close con :abort t))))
con)))))
passive)))

Figure 4.1: Telnet Server in 22 Lines


58 CHAPTER 4. INTERFACES

the client with a normal CL Command Prompt (for security, it will by default only accept
connections from the local machine).

4.5 Interfacing with Windows (COM, DLL, DDE)


Most Commercial CL implementations for the Microsoft Windows platform will allow CL
applications to use various Microsoft-specific means for integrating with the Microsoft plat-
form. For example, an Allegro CL application can be set up to run as a COM server, or
compiled into a DLL to be used by other applications.

4.6 Code Generation into Other Languages


CL excels at reading and generating CL code. It is also a powerful tool for analyzing and
generating code in other languages. An example of this can be found in Franz Inc’s htmlgen
product, which provides a convenient CL macro to generate HTML for a web page.
Another example is MSC’s AutoSim product, which uses a CL program to model an
automobile, then generates optimized C programs for solving specialized systems of linear
equations relating to vehicle dynamics.
CL also played a crucial role in preventing the potential Y2K disaster by automatically
analyzing and repairing millions of lines of COBOL!

4.7 Multiprocessing
Multiprocessing allows your application to carry on separate tasks virtually simultaneously.
Virtually, because on a single-processor machine, only one operation can physically be hap-
pening at any one time. However, there are situations when you want your program to
appear to be executing separate execution threads, or processes, simultaneously. Web serv-
ing is a good example. As described in Section 4.9, you may be running your CL application
as a webserver. This means that multiple users, on different machines in different parts of
your company or even around the world, may be sending requests to your application in
the form of web browser (client) HTTP commands. If one user’s request takes an especially
long time to process, you probably do not want all your other users to have to wait for a
response while that one request is being processed.
Multiprocessing addresses such problems by allowing your CL application to continue
servicing other requests, while “simultaneously” completing that long computation.
Allegro Multiprocessing is covered in the document multiprocessing.htm which ships
with all editions of Allegro CL.

4.7.1 Starting a Background Process


Starting another thread of execution in Allegro CL can be as simple as calling the function
mp:process-run-function. This function spawns a separate thread of execution, while
the execution of code from whence you call it continues. Here is a simple example which
you can run from the command line:
4.7. MULTIPROCESSING 59

(defvar *count* 0)
(mp:process-run-function
(list :name "counting to a million in background"
:priority -10)
#’(lambda() (dotimes (n 1000000) (setq *count* n))))

This starts a thread named “counting to a million in background” with a relative priority
of -10. Notice that after you call this function, your toplevel prompt returns immediately.
The toplevel Read-Eval-Print loop is continuing on its merry way, while your new process is
running in the background, counting to a million (actually, 999999). While the background
process is running, you can sample the value of count at any time:

CL-USER(16): *count*
424120
CL-USER(17): *count*
593783
CL-USER(18): *count*
679401
CL-USER(19): *count*
765012

Note that on a fast processor, you may need to increase the count value in this example to
be able to take samples before the code completes.

4.7.2 Concurrency Control


Sometimes you want to prevent two threads from modifying a value in memory at the same
time. For this purpose, you can use process locks. A process lock is an object which can be
seized by at most one running process at a time. Using process locks, you can ensure that
multiple threads of execution will interact as you expect.
In our previous counting example, let’s say you want to prevent another thread from
modifying the count variable until the count is complete. First, you would establish a
process lock specific for this purpose:

CL-USER(20): (setq count-lock (mp:make-process-lock))


#<MULTIPROCESSING:PROCESS-LOCK @ #x5a02fba>

Now, run the background function within the context of seizing this lock:
60 CHAPTER 4. INTERFACES

CL-USER(21): (mp:process-run-function
(list :name "counting to a million in background"
:priority -10)
#’(lambda()
(mp:with-process-lock (count-lock)
(dotimes (n 1000000) (setq count n)))))
#<MULTIPROCESSING:PROCESS counting to a million in background @ #x5a0332a>

You now can adopt a policy that any piece of code wishing to modify the value of count must
first seize the count-lock. This will ensure that nothing will interfere with the counting
process. If you do the following while the counting process is running:

CL-USER(22): (mp:with-process-lock (count-lock) (setq count 10))

you will notice that execution will block until the counting process is finished. This is
because the count-lock is already seized, and execution is waiting for this lock to be
released before proceeding.
It is usually a good idea to make your process locks as fine-grained as possible, to avoid
making processes wait unecessarily.
From your toplevel prompt, you can monitor which processes are running in the current
CL image with the toplevel :proc command:

CL-USER(28): :proc
P Bix Dis Sec dSec Priority State Process Name, Whostate, Arrest
* 7 1 2 2.0 -10 runnable counting to a million in background
* 1 8 28 0.1 0 runnable Initial Lisp Listener
* 3 0 0 0.0 0 waiting Connect to Emacs daemon, waiting for input
* 4 0 0 0.0 0 inactive Run Bar Process
* 6 0 3 0.0 0 waiting Editor Server, waiting for input

See the multiprocessing.htm for details on each column in this report, but essentially
this is showing the state of each process currently in the system. This example shows that
Allegro CL uses separate threads for maintaining the connection to the Emacs text editor.
As we will see in Section 4.9, the AllegroServe webserver automatically creates and
manages separate threads to deal with simultaneous web connections.

4.8 Database Interfaces


CL in general, and Allegro CL specifically, provides a myriad of mechanisms for connecting
to databases. Most of these mechanisms differ only slightly in their operation, so you can
generally switch among the different mechanisms without major rework in your application.
The two mechanisms we will cover here are both included with the Enterprise and Platinum
editions of Allegro CL.
4.8. DATABASE INTERFACES 61

4.8.1 ODBC
ODBC, or Open Database Connectivity, is a standard means of connecting to a relational
database based on SQL (Structured Query Language, the industry standard Data Definition
and Data Manipulation language for relational databases). Virtually all popular database
systems can be connected to using ODBC. On Windows these facilities are usually free
of charge; on Unix they may or may not be free. In any case, ODBC is a good solution
to consider if you want to connect your application to several different relational database
systems without writing and maintaining separate pieces of code specific to each.
ODBC uses the concept of data sources, each with a name. There are operating system
dependent ways to set up data sources on your machine to point to actual physical databases,
which may be housed on the local machine or remote. Assuming we have a data source
named “login” with a table named “USER,” connecting to and querying the database can
be as simple as the following:

CL-USER(40): (setq handle (dbi:connect :data-source-name "login"))


#<DBI::ODBC-DB "DSN=login;DB=login;...[truncated]" @ #x4b2c3b2>
CL-USER(41): (dbi:sql "select * from USER" :db handle)
(("1" "dcooper8" "guessme" "Dave" NIL "Cooper" NIL NIL NIL NIL ...)
("7" "awolven" NIL NIL NIL NIL NIL NIL NIL NIL ...))
("GEN_ID" "LOGIN" "PASSWORD" "FIRST_NAME" "MIDDLE_NAME" "LAST_NAME"
"COMPANY" "ADDRESS_1" "ADDRESS_2" "CITY" ...)

As with most CL database interfaces, the ODBC interface returns all SQL queries with
multiple values: The first (and main) return value is a list of lists, corresponding to the
rows in the returned query. The second return value is a list of field names, corresponding
to the values in each of the first lists.
Because ODBC by definition must retain portability among database systems, it is
generally unable to take full advantage of database-specific features and performance opti-
mizations.

4.8.2 MySQL
MySQL is a popular relational database which is available both in commercially supported
and free, open-source form. While you can connect to a MySQL database using ODBC,
Allegro CL also provides a direct connection to MySQL, which performs faster and is easier
to set up than the ODBC connection. From the application level, its use is simple enough
that an application using the MySQL Direct interface could be converted to use ODBC
without major rework, should that become necessary. Therefore, if you know your Allegro
CL application will be using MySQL for the immediate future, we recommend using the
MySQL Direct interface rather than ODBC.
Here is the same example from above, using the MySQL Direct interface instead of the
ODBC one:

CL-USER(50): (setq handle (dbi.mysql:connect :user "dcooper8"


:password "guessme"
62 CHAPTER 4. INTERFACES

:database "login"))
#<DBI.MYSQL:MYSQL connected to localhost/3306 @ #x5bd114a>
CL-USER(51): (dbi.mysql:sql "select * from USER" :db handle)
((1 "dcooper8" "bhaga<<" "Dave" #:|null| "Cooper" #:|null| ...)
(7 "awolven" #:|null| #:|null| #:|null| #:|null| #:|null| ...))
("GEN_ID" "LOGIN" "PASSWORD" "FIRST_NAME" "MIDDLE_NAME"
"LAST_NAME" "COMPANY" "ADDRESS_1" "ADDRESS_2" "CITY" ...)

A few differences from ODBC to note:

• The functions are in the dbi.mysql package rather than the dbi package.

• Since there is no need to set up separate data sources for MySQL Direct, we connect
to a MySQL instance on the local machine directly by specifying a user, password,
and database name.

• NULL values in the return query are represented with the special symbol #:|null|
rather than with NIL as in ODBC.

4.9 World Wide Web


4.9.1 Server and HTML Generation
The dynamic and multi-threaded nature of AllegroCL makes it a natural fit for powering
webserver applications. Franz provides a popular open-source webserver called AllegroServe
which is designed just for this purpose.
With a standard Allegro CL 6.2 installation you can load AllegroServe and access its
exported symbols as follows:

(require :aserve)
(use-package :net.aserve)
(use-package :net.html.generator)
(use-package :net.aserve.client)

With AllegroServe, you can map website addresses to actual functions which will run in
response to a request. Here is a simple example:

(defun hello (req ent)


(with-http-response (req ent)
(with-http-body (req ent)
(html "Hello World"))))

(net.aserve:publish :path "/hello.html"


:function #’hello)
4.9. WORLD WIDE WEB 63

Now, whenever a web visitor goes to the /hello.html URI on our server, she will receive
in response a page with the simple text “Hello World.” More dynamic examples would of
course be more interesting, and we present some in the next chapter.
Note the use of the html operator in the previous example. This is a macro from the
HTMLgen package which ships with Allegroserve. HTMLgen allows convenient generation
of dynamic, well-structured HTML from a lisp-like source format. Please see the Alle-
groServe and HTMLgen documentation at http://opensource.franz.com/aserve/index.html
for details on both of these.

4.9.2 Client and HTML Parsing


Where there is a server there must be a client. In the World Wide Web, we typically think
of clients as being interactive web browsers such as Netscape or Internet Explorer. However,
web clients can also be autonomous programs. This is how “agents” and “crawlers” work –
they are actually programmatic web browsers which continually send requests to webservers
and parse the results, gathering information.
Allegroserve includes a web client for such purposes. Using it can be as simple as a
call to net.aserve.client:do-http-request. For example, to retrieve the URI from the
above example programmatically:

CL-USER(1): (do-http-request "localhost:9000/hello.html")


"Hello World"
200
((:TRANSFER-ENCODING . "chunked") (:SERVER . "AllegroServe/1.2.25")
(:DATE . "Tue, 04 Mar 2003 21:36:20 GMT") (:CONNECTION . "Close"))
#<URI http://localhost:9000/hello.html>

As you can see, the do-http-request function returns four values:

1. the actual contents of the response, as a string

2. the response code (e.g. 200 for a normal response, 404 for a “Page Not Found,” etc.)

3. an assoc-list containing the HTTP headers

4. a URI object, an internal CL object containing information about the URI

For more complex client requests which return structured HTML, an HTML parser is
available. For example, you might create a news agent by sending an HTTP request to a
news site, then parsing the HTML to create convenient list structures from which you can
elicit the text of the news stories. Please see http://opensource.franz.com/xmlutils/index.html
for details on this parser.
64 CHAPTER 4. INTERFACES

4.10 Regular Expressions


Many applications have to work with strings of characters. Doing this in an efficient manner
can be a key to the efficiency and performance of your entire application. Allegro CL
provides a Regular Expression module for this purpose. The Regular Expression module
allows you to search, replace, and manipulate patterns in character strings in an efficient
and optimized manner.
As a simple example, let us replace one character in a string with another. Assume we
have some field names as variables in CL, which can legally contain dash (“-”) characters.
Say we want to use these names as a basis for field names in an SQL database, where dashes
are not allowed as part of a name. We will replace all dashes with underscores:
(defun replace-dashes (word)
(excl:replace-regexp word "-" "_"))

CL-USER(59): (replace-dashes "top-o-the-morning")


"top_o_the_morning"
Please see the document regexp.htm, which ships with all editions of Allegro CL, for a full
Regular Expression reference.

4.11 Email
Email was the first “killer app” of the Internet, and arguably continues in that role to this
day. Despite threats from viruses and unsolicited commercial email, many people in the
modern world still depend on email for their daily communication. CL provides an excellent
environment for automating email processing. Not coincidentally, in the next chapter we
present an example of using CL to perform text classification on email, an effective means
for filtering unwanted and unsolicited commercial bulk email.

4.11.1 Sending Mail


Franz provides a convenient open-source package for sending messages to an SMTP (Simple
Mail Transfer Protocol) server.
With a standard Allegro CL 6.2 installation you can load this package and access its
exported symbols as follows:
(require :smtp)
(use-package :net.post-office)
This package provides the send-letter function. This function takes the name of an
SMTP server along with the recipient, the text of an email, and other optional arguments,
and actually delivers the mail to the specified SMTP host. Here is an example:
(send-letter "smtp.myisp.com" "[email protected]" "[email protected]"
"See you at the game tonight." :subject "Tonight’s Game"
That’s all it takes to have your application shoot off an email.
4.11. EMAIL 65

4.11.2 Retrieving Mail


The two main Internet standards for retrieving mail are POP and IMAP. Although IMAP
is newer and more feature-filled than POP, many ISPs still support only POP, so we will
cover the POP interface here. Franz provides a single file, imap.cl which contains client
interfaces both for POP and IMAP.
With a standard Allegro CL 6.2 installation you can load this package and access its
exported symbols as follows:

(require :imap)
(use-package :net.post-office)

Here is an example of using the POP client interface. First, you must create a connection
handle to the POP server:

CL-USER(4): (setq mailbox (net.post-office:make-pop-connection "pop.myisp.com"


:user "joe"
:password "guessme"))
#<NET.POST-OFFICE::POP-MAILBOX @ #x5dcc33a>

Now we can get a count of messages in the Inbox:

GDL-USER(5): (net.post-office:mailbox-message-count mailbox)


1

and fetch the one message:

GDL-USER(9): (net.post-office:fetch-letter mailbox 1)


"Content-type: text/plain; charset=US-ASCII
MIME-Version: 1.0 ...
..."

Finally, we can delete this message and close our connection:

GDL-USER(10): (net.post-office:delete-letter mailbox 1)


NIL
GDL-USER(12): (net.post-office:close-connection mailbox)
T

Please see the documentation at http://opensource.franz.com/postoffice/index.html


for complete coverage of the POP and IMAP facilities.
In the next chapter, we will combine most of what we have learned in this chapter to
put together a web-based program for browsing and classifying email. Because we make
use of pre-built libraries and interfaces such as the ones described in this chapter, we can
create a functioning multi-user application with less than 500 lines of code.
66 CHAPTER 4. INTERFACES
92 INDEX

tables
hash, 45
test-expression forms
for cond, 37
threads, 1
toplevel, 19
turning off evaluation, 22
Typing
Dynamic, 22

union, 34
Unix, 44

variables
global, 25
special, 25

webserver, 62
With-open-file, 45

You might also like