land of lisp - barski m.d., conrad

574

Upload: lee-an

Post on 21-Dec-2014

166 views

Category:

Documents


21 download

TRANSCRIPT

Page 1: Land of Lisp - Barski M.D., Conrad
Page 2: Land of Lisp - Barski M.D., Conrad

Land of Lisp

T able of ContentsAcknowledgm entsIntroduc tion

W hat Makes L isp So Cool and Unusua l?If L isp Is So Grea t , W hy Don't More People Use It?W here Did L isp Com e From ?W here Does L isp Get Its Power?

I. L isp is Power1. Ge tt ing Sta rted with L isp

L isp Dia lec tsA T a le of T wo L ispsUp-and-Com ing L ispsL isp Dia lec ts Used for Script ingANSI Com m on L isp

Gett ing Sta rted with CL ISPInsta l l ing CL ISPStart ing Up CL ISP

W hat You've L earned2. Crea ting Your First L isp Program

T he Guess-My-Num ber Gam eDefining Globa l Variables in L isp

Defining the sm all and big VariablesAn Alte rna tive Globa l Variable Definit ion Func tion

Basic L isp E tique tteDefining Globa l Func tions in L isp

Defining the guess-m y-num ber Func tionDefining the sm alle r and bigger Func tionsDefining the sta rt-over Func tion

Defining L oca l Variables in L ispDefining L oca l Func tions in L ispW hat You've L earned

3. E xploring the Syntax of L isp CodeSyntax and Sem anticsT he Building Blocks of L isp Syntax

Sym bolsNum bersStrings

How L isp Dist inguishes Be tween Code and DataCode ModeData Mode

L ists in L ispCons Ce llsL ist Func tionsNested L ists

W hat You've L earnedII. L isp is Sym m etry

4. Making Dec isions with Condit ionsT he Sym m etry of ni l and ()

E m pty E qua ls Fa lseT he Four Disguises of ()

T he Condit iona ls: i f and BeyondOne T hing a t a T im e with ifGoing Beyond if: T he when and unless Alte rna tivesT he Com m and T ha t Does It All : condBranching with case

Cool T ricks with Condit ionsUsing the Stea lth Condit iona ls and and orUsing Func tions T ha t Re turn More than Just the T ruth

Com paring Stuff: eq, equa l , and MoreW hat You've L earned

5. Building a T ext Gam e E ngineT he W izard's Adventure Gam e

Our Gam e W orldBasic Requirem ents

Describing the Scenery with an Assoc ia t ion L istDescribing the L oca tionDescribing the Pa ths

How Quasiquoting W orksDescribing Mult iple Pa ths a t Once

Describing Objec ts a t a Spec ific L oca tionL ist ing Visible Objec tsDescribing Visible Objec ts

Page 3: Land of Lisp - Barski M.D., Conrad

Describing It AllW alking Around in Our W orldPicking Up Objec tsChecking Our InventoryW hat You've L earned

6. Inte rac ting with the W orld: Reading and Print ing in L ispPrinting and Reading T ext

Print ing to the ScreenSaying Hello to the UserSta rt ing with print and readReading and Print ing Stuff the W ay Hum ans L ike It

T he Sym m etry Be tween Code and Data in L ispAdding a Custom Inte rface to Our Gam e E ngine

Se tt ing Up a Custom RE PLW rit ing a Custom read Func tionW rit ing a gam e-eva l Func tionW rit ing a gam e-print Func tion

T rying Out Our Fancy New Gam e Inte rfaceT he Dangers of read and eva lW hat You've L earned

6.5. lam bda: A Func tion So Im portant It Deserves Its Own Chapte rW hat lam bda DoesW hy lam bda Is So Im portantW hat You've L earned

7. Going Beyond Basic L istsE xotic L ists

Dotted L istsPa irsCircula r L istsAssoc ia t ion L ists

Coping with Com plica ted DataVisua liz ing T ree-l ike DataVisua liz ing Graphs

Crea ting a GraphGenera ting the DOT Inform ationT urning the DOT File into a Pic tureCrea ting a Pic ture of Our Graph

Crea ting Undirec ted GraphsW hat You've L earned

8. T his Ain't Your Daddy's W um pusT he Grand T heft W um pus Gam eDefining the E dges of Congestion City

Genera ting Random E dgesL ooping with the loop Com m andPreventing IslandsBuilding the Fina l E dges for Congestion City

Building the Nodes for Congestion CityInit ia l iz ing a New Gam e of Grand T heft W um pusDrawing a Map of Our City

Drawing a City from Part ia l KnowledgeW alking Around T own

L et 's Hunt Som e W um pus!W hat You've L earned

9. Advanced Data types and Generic Program m ingArrays

W orking with ArraysUsing a Generic Se tte rArrays vs. L ists

Hash T ablesW orking with Hash T ablesReturning Mult iple ValuesHash T able Perform anceA Faste r Grand T heft W um pus Using Hash T ables

Com m on L isp Struc turesW orking with Struc turesW hen to Use Struc tures

Handling Data in a Generic W ayW orking with SequencesCrea ting Your Own Generic Func tions with T ype Predica tes

T he Orc Ba tt le Gam eGloba l Variables for the Player and Monste rsMain Gam e Func tionsPlayer Managem ent Func tionsHelper Func tions for Player Attacks

Page 4: Land of Lisp - Barski M.D., Conrad

Monste r Managem ent Func tionsT he Monste rsT o Batt le !

W hat You've L earnedIII. L isp is Hacking

10. L ooping with the loop Com m andT he loop Macro

Som e loop T ricksE verything You E ver W anted to Know About loop

Using loop to E volve!Growing Plants in Our W orldCrea ting Anim alsSim ula ting a Day in Our W orldDrawing Our W orldCrea ting a User Inte rfaceL et 's W atch Som e E volution!E xpla ining the E volution

W hat You've L earned11. Print ing T ext with the form at Func tion

Anatom y of the form at Func tionT he Destina tion Param ete rT he Control String Param ete rValue Param ete rs

Control Sequences for Print ing L isp ValuesControl Sequences for Form att ing Num bers

Control Sequences for Form att ing IntegersControl Sequences for Form att ing Floa ting-Point Num bers

Print ing Mult iple L ines of OutputJust ifying OutputIte ra t ing T hrough L ists Using Control SequencesA Crazy Form att ing T rick for Crea ting Pre t ty T ables of Da taAttack of the Robots!W hat You've L earned

12. W orking with Stream sT ypes of Stream s

Stream s by T ype of ResourceStream s by Direc tion

W orking with FilesW orking with Socke ts

Socke t AddressesSocke t Connec tionsSending a Message over a Socke tT idying Up Afte r Ourse lves

String Stream s: T he Oddba ll T ypeSending Stream s to Func tionsW orking with L ong StringsReading and Debugging

W hat You've L earned13. L e t 's Crea te a W eb Server!

E rror Handling in Com m on L ispSigna ling a Condit ionCrea ting Custom Condit ionsInte rcepting Condit ionsProtec ting Resources Aga inst Unexpec ted Condit ions

W rit ing a W eb Server from Scra tchHow a W eb Server W orksRequest Param ete rsParsing the Request HeaderT esting ge t-header with a String StreamParsing the Request BodyOur Grand Fina le : T he se rve Func tion!

Building a Dynam ic W ebsiteT esting the Request Handle rL aunching the W ebsite

W hat You've L earned13.5. Func tiona l Program m ing Is Beautiful

IV. L isp is Sc ience14. Ram ping L isp Up a Notch with Func tiona l Program m ing

W hat Is Func tiona l Program m ing?Anatom y of a Program W rit ten in the Func tiona l StyleHigher-Order Program m ing

Code Com posit ion with Im pera tive CodeUsing the Func tiona l StyleHigher-Order Program m ing to the Rescue

Page 5: Land of Lisp - Barski M.D., Conrad

W hy Func tiona l Program m ing Is CrazyW hy Func tiona l Program m ing Is Fantast ic

Func tiona l Program m ing Reduces BugsFunc tiona l Program s Are More Com pac tFunc tiona l Code Is More E legant

W hat You've L earned15. Dice of Doom , a Gam e W rit ten in the Func tiona l Style

T he Rules of Dice of DoomA Sam ple Gam e of Dice of DoomIm plem enting Dice of Doom , Version 1

Defining Som e Globa l VariablesRepresenting the Gam e BoardDecoupling Dice of Doom 's Rules from the Rest of the Gam eGenera ting a Gam e T reeCalcula t ing Passing MovesCalcula t ing Attacking MovesFinding the NeighborsAttackingReinforcem entsT rying Out Our New gam e-tree Func tionPlaying Dice of Doom Against Another Hum an

Crea ting an Inte l l igent Com pute r OpponentT he Minim ax Algori thmT urning Minim ax into Actua l CodeCrea ting a Gam e L oop with an AI PlayerPlaying Our First Hum an vs. Com pute r Gam e

Making Dice of Doom Faste rClosuresMem oiza tionT ail Ca ll Optim iza tionA Sam ple Gam e on the 3-by-3 Board

W hat You've L earned16. T he Magic of L isp Macros

A Sim ple L isp MacroMacro E xpansionHow Macros Are T ransform edUsing the Sim ple Macro

More Com plex MacrosA Macro for Spli t t ing L istsAvoiding Repea ted E xecution in MacrosAvoiding Variable CaptureA Recursion Macro

Macros: Dangers and Alte rna tivesW hat You've L earned

17. Dom ain-Spec ific L anguagesW hat Is a Dom ain?W rit ing SVG Files

Crea ting XML and HT ML with the tag MacroCrea ting SVG-Spec ific Macros and Func tionsBuilding a More Com plica ted SVG E xam ple

Crea ting Custom Gam e Com m ands for W izard's Adventure Gam eCrea ting New Gam e Com m ands by HandL et 's T ry the Com ple ted W izard's Adventure Gam e!

W hat You've L earned18. L azy Program m ing

Adding L azy E va lua tion to L ispCrea ting the lazy and force Com m andsCrea ting a L azy L ists L ibra ryConvert ing Be tween Regula r L ists and L azy L istsMapping and Searching Across L azy L ists

Dice of Doom , Version 2Making Our AI W ork on L arger Gam e Boards

T rim m ing the Gam e T reeApplying Heurist icsW inning by a L ot vs. W inning by a L it t leAlpha Be ta Pruning

W hat You've L earned19. Crea ting a Graphica l , W eb-Based Version of Dice of Doom

Drawing the Gam e Board Using the SVG Form atDrawing a DieDrawing a T ileDrawing the Board

Building the W eb Server Inte rfaceW rit ing Our W eb Request Handle r

Page 6: Land of Lisp - Barski M.D., Conrad

L im ita t ions of Our Gam e W eb ServerInit ia l iz ing a New Gam eAnnounc ing a W innerHandling the Hum an PlayerHandling the Com pute r PlayerDrawing the SVG Gam e Board from W ithin the HT ML

Playing Version 3 of Dice of DoomW hat You've L earned

20. Making Dice of Doom More FunIncreasing the Num ber of PlayersRoll ing the Dice

Building Chance NodesDoing the Actua l Dice Roll ingCall ing the Dice Roll ing Code from Our Gam e E ngineUpdating the AI

Im proving the Dice of Doom Reinforcem ent RulesConc lusion

A. E pilogueFunc tiona l Guild Cruise r

SynopsisHow It Kil ls BugsE xplana tionW eakness

Macro Guild Melee Fighte rsSynopsisHow It Kil ls BugsE xplana tionW eakness

Resta rt Guild Arm ored Fighte rSynopsisHow It Kil ls BugsE xplana tionW eakness

Generic Se tte r Guild Supply ShipSynopsisHow It Kil ls BugsE xplana tionW eakness

DSL Guild Hot RodsSynopsisE xplana tionW eakness

CL OS Guild Ba tt leshipSynopsisHow It Kil ls BugsE xplana tionE xplana tionW eakness

T he Continua tion Guild Rocke t PodsSynopsisHow It Kil ls BugsE xplana tionW eakness

Brevity Guild Micro Fighte rSynopsisHow It Kil ls BugsE xplana tionW eakness

Multicore Guild Form ation Fighte rsSynopsisHow It Fights BugsE xplana tionW eakness

T he L azy Guild Friga teL isp Dia lec tSynopsisHow It Kil ls BugsE xplana tionW eakness

Index

Page 7: Land of Lisp - Barski M.D., Conrad

Land of Lisp

Conrad Barski M.D.

Copyright © 2010All rights rese rved. No part of this work m ay be reproduced or transm itted in any form or by any m eans, e lec tronic or

m echanica l , inc luding photocopying, recording, or by any inform ation storage or re trieva l system , without the prior writ tenperm ission of the copyright owner and the publisher.

No Sta rch Press and the No Sta rch Press logo a re registe red tradem arks of No Sta rch Press, Inc . Other produc t and com pany nam esm entioned here in m ay be the tradem arks of the ir respec tive owners. Ra ther than use a tradem ark sym bol with every occurrence ofa tradem arked nam e, we a re using the nam es only in an editoria l fashion and to the benefi t of the tradem ark owner, with nointention of infringem ent of the tradem ark.

T he inform ation in this book is distributed on an “As Is” basis, without warranty. W hile every precaution has been taken in theprepara t ion of this work, ne ithe r the author nor No Sta rch Press, Inc . sha ll have any l iabil i ty to any person or enti ty with respec t toany loss or dam age caused or a l leged to be caused direc t ly or indirec t ly by the inform ation conta ined in i t .

No Sta rch Press

Page 8: Land of Lisp - Barski M.D., Conrad

Dedication

For L auren

Page 9: Land of Lisp - Barski M.D., Conrad

Acknowledgments

First of a l l , I’d l ike to thank m y wife L auren for le t t ing m e spend so m any weekends on this book projec t . I am a lso part icula rlygra te ful to Phil ip Fom inykh, the m ain technica l reviewer for this book, whose extensive experience with proper Com m on L isp formand style he lped to re ign in m any (but not too m any) quirks in the source code and in the discussions of L isp philosophy. I a lso owea grea t dea l to Heow E ide-Goodm an, who reviewed the fina l chapte rs and he lped com ple te this projec t .

Many folks a t No Sta rch Press had to wrest le with the idiosyncra t ic prose and struc ture of this book to bring i t into publishableform . First and forem ost , I want to thank Keith Fancher, m y prim ary editor for m ost of this projec t . Grea t e ffort was a lso put in byBil l Pollock, Serena Yang, Anse l Sta ton, Riley Hoffm an, Alison Pe te rsen, Magnolia Molcan, Ka thleen Mish, Don Marti , T yle rOrtm an, and Adam W right . I’d a lso l ike to thank Aaron Feng for ea rly feedback on the style of the book.

T his book projec t origina lly began as an expansion of m y “Casting SPE L s in L isp” web tutoria l . I want to thank everyone whoem ailed or ta lked with m e about this tutoria l and he lped m e expand m y understanding of L isp a long the way. rm s (RichardSta l lm an), in part icula r, gave m e a lot of feedback on L isp style and he lped m e put toge ther the E m acs L isp version of thetutoria l . Please consider dona ting to the Free Software Founda tion (http: / /www. fsf. org/), a nonprofi t organiza tion he founded tosupport the deve lopm ent of open source and free software , which inc ludes m any grea t L isp tools. Jam es W ebb a lso he lped grea tlywith the E m acs L isp version. And am ong the countless folks who gave feedback and/or correc tions on “Casting SPE L s in L isp, ” I’despec ia l ly l ike to thank Kenny T il ton, Marsha ll Quander, W ei-Ju W u, Christopher Brown, Stephen Gravrock, Paul Schulz , AndyCowell , and Johan Bockgård.

Page 10: Land of Lisp - Barski M.D., Conrad

Introduction

So, you’ve dec ided to pick up a book on L isp and read the introduc tion. Perhaps you were surprised to see som ething tha t lookslike a com ic book m ixed in with the other com pute r program m ing books on the she lf. W ho would bother writ ing a com ic bookabout a weird academ ic program m ing language l ike L isp? Or m aybe you’ve heard other people raving about the L isp language andthought, “Boy, L isp sure sounds diffe rent from other languages people ta lk about. Maybe I should pick up a L isp book som etim e .”E ither way, you’re now holding a book about a program m ing language tha t is ve ry cool but a lso very unusua l .

Page 11: Land of Lisp - Barski M.D., Conrad

What M akes Lisp So Cool and Unusual?

L isp is a ve ry expressive language . L isp is designed to le t you take the m ost com plica ted program m ing ideas and express them ina c lea r and appropria te way. L ispers have the freedom to write a program in exac tly the way tha t is m ost he lpful for solving anyproblem a t hand.

T he power a t your fingert ips when writ ing L isp code is wha t m akes i t so diffe rent . Once you “ge t” L isp, you’l l be foreverchanged as a program m er. E ven if you end up never writ ing L isp code aga in for the rest of your l i fe , lea rning L isp wil lfundam enta l ly change you as a coder.

In a way, lea rning a typica l program m ing language is sim ila r to lea rning a fore ign language as an adult . Suppose you go outtom orrow and dec ide you’re going to lea rn French. You m ay take every course on French tha t you can find, read m ate ria ls tha t a reonly in French, and even m ove to France . But no m atte r wha t you do, your understanding of French wil l a lways rem ain a l i t t leim perfec t . And no m atte r how good of a French speaker you eventua lly becom e, in your dream s you probably wil l st i l l be speakingin your na tive language .

L isp is diffe rent . It ’s not just l ike lea rning any fore ign language . Once you’ve lea rned L isp, you’l l even dream in L isp. L isp issuch a powerful idea tha t i t wil l c rowd out your previous program m ing experience and becom e your new m other tongue! W heneveryou encounte r a new program m ing idea in any language , you’l l a lways say to yourse lf, “T ha t’s kind of how I’d do i t in L isp, except. . . . ” T ha t’s the kind of power only L isp wil l give you.

At this point , a l l you m ay know about L isp is tha t a t least one person (m e) is extrem ely exc ited about i t . But your t im e isva luable , and lea rning som ething new is bound to require som e e ffort .

T he good news is L isp isn’t rea l ly as difficult a s i t m ay seem a t first glance . For instance , the fol lowing is a va lid L ispexpression:(+ 3 (* 2 4))Can you guess wha t the va lue of this expression is? If you answered 11, then you’ve a lready figured out how to read basic L isp

code . It is wri t ten just l ike m ath, except tha t the func tions—in this case , addit ion and m ult ipl ica t ion—com e before the num bers,and everything is in parentheses.

Page 12: Land of Lisp - Barski M.D., Conrad

If Lisp Is So G reat, Why Don't M ore P eople Use It?

Actua lly, a fa ir num ber of la rge com panies do use L isp for som e se rious work (you’l l find a long l ist of industria l L isp projec ts a tht tp: / /snipurl . com /e3lv9/). Other program m ing languages a re constantly “borrowing” fea tures of L isp and presenting them as thela test and grea test ideas. Also, the Sem antic W eb, which m any be lieve wil l play a big role in the future of the W eb, uses m anytools writ ten in L isp.

Note

T he idea behind the Sem antic W eb is to c rea te a se t of protocols for websites to fol low so tha t a com pute r can de te rm ine the“m eaning” of inform ation on a web page . T his is done by annota t ing web pages with spec ia l m etada ta (usua lly in a form at ca l ledResource Descript ion Fram ework, or RDF) tha t l inks to com m on vocabula ries, which diffe rent websites m ay share . Many of thetools used for working with descript ion logics and RDF da ta a re writ ten in L isp (for exam ple , RacerPro and AllegroGraph).

So, L isp ce rta inly has a prom ising future . But som e m ay think tha t lea rning L isp is not worth the e ffort .How did L isp ge t this undeserved reputa t ion?I think tha t people use a rule of thum b when dec iding what things in l i fe a re worth lea rning. Most people seek knowledge in one

of the fol lowing three ca tegories:

W hat m any other people lea rn (ca lculus, C++, and so on)W hat is easy to lea rn (hula -hooping, Ruby, and so on)W hat has va lue tha t is easy to apprec ia te (the rm onuc lear physics, for exam ple , or tha t ridiculously loud whist le

where you st ick your fingers in your m outh)

L isp doesn’t fa l l into any of these ca tegories. It ’s not as popula r as ca lculus, pa rt icula rly easy to lea rn, or as obviously va luableas tha t loud whist le . If we were to fol low these (usua lly very sensible ) rules of thum b, we would conc lude tha t a reasonable personshould stay away from L isp. However, in the case of L isp, we’re going to throw out these rules. As you’l l see from reading thisbook, L isp gives you insights into com pute r program m ing tha t a re so profound tha t every se rious program m er should have som eexperience with this unusua l language , even if i t requires a l i t t le e ffort .

If you’re st i l l not convinced, you m ight want to take a peek a t the com ic book epilogue way a t the end of the book. You m ightnot be able to understand everything in the re right now, but i t wil l give you a fee l for the advanced fea tures ava ilable within L ispand what m akes L isp program m ing diffe rent from other types of program m ing.

Page 13: Land of Lisp - Barski M.D., Conrad

Where Did Lisp Come F rom?

T he L isp fam ily of languages is t ruly anc ient , with a history tha t diffe rs from other languages. W e’ll need to trave l fa r back int im e to ge t to the beginning of i t a l l .

A long t im e ago (way back in the 1940s), the E arth was covered by a giant ocean ca lled the Pantha lassic Ocean, a long with asingle barren land m ass nam ed Pangaea . In this unforgiving environm ent, the first com pute r program s evolved, writ ten in purem achine language (or “ones and ze ros, ” as they say).

T hese protolanguages were t ightly bound to spec ific com pute r system s, such as the E NIAC, the Z use Z 3, and other ea rly vacuum -tube contraptions. Often, these ea rly com pute rs were so prim it ive tha t “program m ing” them involved sim ply fl ipping switches orpa tching cables to physica lly encode each opera t ion.

T he dark days of these protolanguages saw a lot of experim enta t ion with diffe rent com pute r a rchitec tures and an explosion ofdiffe rent com pute r instruc tion se ts. Com peti t ion was fie rce . W hile m ost of these prim it ive language experim ents ul t im ate lydisappeared—vic tim s of anc ient ba tt les for surviva l—others thrived.

At a ce rta in point , com pute rs acquired the ir own in m em ory to store program s, a long with prim it ive assemblers tha t a l lowedprogram s to be writ ten in text , instead of with just pure num bers. T hese assembly languages inc luded Short Code , ARC assem bly,and E DSAC Init ia l Orders.

Assem bly languages m ade software deve lopm ent m uch m ore e ffic ient , enabling anc ient assem blers to evade the m any preda torsin this prim ordia l ocean. But assem bly languages st i l l had significant l im ita t ions. T hey were a lways designed a round the instruc tionse t of a spec ific processor and so they were not portable ac ross diffe rent m achine a rchitec tures. Program m ing languages needed toevolve to survive beyond the confines of a spec ific m achine instruc tion se t .

T he 1950s saw the a rriva l of the first m achine-independent program m ing languages. L anguages l ike Autocode and Inform ationProcessing L anguage accom plished this independence not only through lungs and legs, but a lso through new types of software , suchas com pile rs and inte rpre te rs.

W ith com pile rs and inte rpre te rs, com pute r program s could now be writ ten in a hum an-friendly syntax. A compiler can take ahum an-writ ten com pute r program and convert i t autom atica l ly into a m achine-friendly binary form at tha t the com pute r canexecute . An interpre ter, on the other hand, pe rform s the ac t ions described in a hum an-writ ten program direc t ly, without convert ingthem a ll the way down to a m achine-friendly binary form at.

For the first t im e , program m ers could use languages tha t were designed to m ake com pute r program m ing a pleasant ac t ivi ty,without needing to opera te a t the prim it ive leve l of the com pute r ha rdware . T hese inte rpre ted and com piled program m inglanguages a re wha t we now think of as the first “ true” program m ing languages. One of the m ost im posing of these ea rly languages,FORT RAN (deve loped in 1957), was wide ly supported on diffe rent a rchitec tures and is st i l l used heavily to this day.

Up unti l this point , the m ost successful languages had been designed a round one centra l idea : to offe r a genera l design and syntaxtha t would m ake program m ing as easy as possible for novices. However, designing a good program m ing language turns out to bevery difficult . Hence , m ost of these languages, l ike FORT RAN, BASIC, and C, were rea l ly just a m ishm ash of older ideas, copiedfrom one another and thrown toge ther in a way tha t lacked any rea l beauty. T hey were usua lly easy to use in only superfic ia l ways.

Page 14: Land of Lisp - Barski M.D., Conrad

Nonethe less, these fie rce languages roam ed the jungles for decades in sea rch of easy prey.In the shadows of these fea rsom e beasts lurked a sm all , hum ble , and entire ly diffe rent sort of c rea ture—m ostly hidden from view,

but present a lm ost since the very first m achine-independent languages c rawled onto land. T hese were languages tha t usedm athem atica l syntax, such as the lam bda ca lculus, deve loped by m athem atic ians in the 1930s.

Not the least bi t concerned with be ing pragm atic or easy for novices to lea rn, these languages were highly inte l l igent and wantedto push the l im its of language design. T hey posed questions about program nota t ion, language sem antics, and the sim plest possiblelanguage syntax.

From these highly inte l l igent m athem atica l syntaxes evolved one m ost notable c rea ture : the origina l L isp program m inglanguage . Unlike m ost other program m ing languages, i t did not evolve from FORT RAN or other languages tha t were concernedwith pragm atism or ease of use . Its l ineage is a com ple te ly separa te one , drawn stra ight from m athem atics. But where did L ispcom e from ?

Som e people c la im tha t the story behind L isp’s origins has been forever lost in the fog of t im e . Others (who a re probably m orecorrec t) say L isp’s c rea tion was the work of John McCarthy in 1959. One day, i t is sa id, he ga thered toge ther his t ribe a t MIT andpresented an ingenious idea . McCarthy envisioned a com ple te ly theore t ica l program m ing language , which would have m inim alsyntax and sem antics but , a t the sam e t im e , c rea te inc redibly e legant program s. T hese program s were so e legant tha t even writ ingan inte rpre te r for L isp in L isp i tse lf would take only a round 50 l ines of com pute r code!

Note

John McCarthy published the paper “Recursive Func tions of Sym bolic E xpressions and T he ir Com puta tion by Machine , Part I, ”Com m unica tions of the ACM (April 1960): 184-195. You can read i t a t ht tp: / /www-form al. stanford. edu/jm c/recursive .pdf.

W hen McCarthy first published his idea , i t was intended only as an inte l lec tua l explora t ion of m athem atica l syntax. But soon,the L isp language evolved and could work with com pile rs and inte rpre te rs. It now ran on rea l com pute rs, just l ike FORT RAN andthe other program m ing languages! But unlike these other languages, L isp re ta ined a beauty derived from its m athem atica lancestry.

Page 15: Land of Lisp - Barski M.D., Conrad

Soon a fte r the first L isps appeared, the first L isp program m ers appeared, capturing these doc ile c rea tures and transform ing theminto ever-m ore-re fined program m ing languages. Over t im e , these program m ers turned the prim al L isps into dia lec ts such asMACL ISP and Inte rl isp.

Although the hunting of ea rly L isps was a successful avoca tion for ea rly L isp program m ers, i t soon becam e c lea r tha t thesehunte rs had a com peti tor: Cro-Magnon m an. T he Cro-Magnons were m ore aggressive than the peaceful L isp program m ers, a t tackingever-bigger software deve lopm ent projec ts using fea rsom e languages such as COBOL . Deve loped for business applica t ions, COBOLwas an ugly and vile behem oth tha t none the less m ade lucra t ive prey for the Cro-Magnons. L isp program m ers, on the other hand,were m ore content contem pla ting e legant program m ing and hunting the occasiona l L isp.

Now, while L isp was an inc redibly powerful idea , other program m ing languages a lready had a head sta rt in m ind share and m orem ature deve lopm ent tools. T his m ade i t a cha llenge for L isps, and the L isp program m ers dependent on them , to ge t the trac t ionthey needed for m ainstream success. However, the gentle L ispers were not concerned with such pe tty things. Despite the ir diffe ringdisposit ions, the L ispers and the Cro-Magnons l ived side by side in re la t ive harm ony.

In the ir own way, the L ispers were thriving. At tha t t im e , they benefi ted heavily from highly academ ic research in a reas such asim age recognit ion, com pute rized da ta c lassifica t ion, and other problem s tha t fa l l under the genera l um bre lla of arti f ic ialinte l l igence (A I). T he highly m athem atica l na ture of these problem s lent the ir invest iga tion to a L ispy approach, and L ispprogram m ers buil t new dia lec ts of L isp into ever-m ore-advanced com pute r system s to a t tack them . Many consider this the GoldenAge of L isp.

Unfortuna te ly, a fte r this brie f golden period, the winds unexpec tedly turned on the poor L ispers. In the m id-1980s, a sudden t i l tin the axis of the E arth a l te red the c l im ate , causing shortages in the food sources tha t the L isp languages needed to survive .Disappointm ents in the progress of AI research caused m any grants for academ ic research to dry up, and m uch of the hardwarefavored by the L isps (such as L isp m achines from Sym bolics, L isp Machine , Inc . , and T exas Instrum ents) fe l l behind thecapabil i t ie s of m ore tradit iona l com plex instruc tion se t com pute r (CISC) and reduced instruc tion se t com pute r (RISC) hardwarearchitec tures. T he world had becom e an unwelcom ing place for L isps and the L isp program m ers tha t depended on them forsurviva l . T he “AI winte r” had a rrived, and L isp was doom ed.

T his fina lly gave the Cro-Magnons the defini te advantage in the language race . T he new craze of m ega li thic , FORT RAN-derived, objec t-oriented languages—such as C++, deve loped in 1983—had slowly conquered com m erc ia l software deve lopm ent.T his gave the Cro-Magnons com ple te im m unity from the AI winte r, which was a ffl ic t ing the L ispers. Furtherm ore , the wily Cro-Magnons borrowed som e of the ideas pioneered by the L ispers to pa tch up the problem s of m ainstream languages. T hus, ga rbagecollec t ion and param etric polym orphism , origina lly found in the L isps, becam e com m on in the languages used by m ainstreamprogram m ers.

E ventua lly, through im m ense e ffort , the language behem oths of olden days had been tam ed by the Cro-Magnons into C#, Java ,and sim ila r languages. T he be lie f a rose tha t these languages were m ore pleasant to use as tools than anything ava ilable in the past ,with the Golden Age of L isp long forgotten. More recently, languages such as Python and Ruby have further re fined these Cro-Magnon languages into m ore m odern direc t ions.

But wha t has happened to the L isp program m ers during a l l this t im e? Have they com ple te ly succum bed to the AI winte r? Arethey once aga in lurking in the shadows, wa it ing for another day in the sun? No one knows for sure . But if you look hard enough,m aybe in the highest m ounta ins, in the deepest jungles, or on the lowest basem ent leve ls of MIT , you m ay ca tch a gl im pse of anodd sort of c rea ture . Som e ca ll i t the W indigo; others re fe r to i t a s a ye ti , Sasqua tch, or rm s. But those who rea lly know think i tjust m ight be—tha t i t could only be—a L isp program m er.

Page 16: Land of Lisp - Barski M.D., Conrad
Page 17: Land of Lisp - Barski M.D., Conrad

Where Does Lisp G et Its P ower?

I’ve sa id tha t L isp is a pa rt icula rly powerful language . So what were the key insights tha t John McCarthy (and the other, la te rinnova tors of L isp) had tha t m ade this power possible?

T o m ake a program m ing language powerful , you need to m ake i t expressive . Having an expressive language m eans tha t you cando a lot of stuff with very l i t t le ac tua l code . But wha t tra i ts does a language need to m ake this possible? I think the re a re two tha ta re m ost im portant .

One tra i t is a lot of fea tures buil t into the language . T ha t way, for m ost things you need to ge t done , som eone has a lreadyperform ed som e of the work for you, and you can leverage tha t work to m ake your own code look pithy. Many m odern languageshave this t ra i t . T he Java language , for instance , is renowned for powerful l ibra ries tha t , for exam ple , le t you acquire da ta fromanother PC over a socke t with ease .

T he second tra i t tha t gives a language power is le t t ing you m uck a round inside i t a s deeply as possible to m ake i t do yourbidding. T ha t way, even if the designers of the language never conce ived of wha t you’re trying to do, you can m ake your ownchanges to the language unti l i t does exac tly wha t you need to solve your problem s e legantly. T his tra i t is m uch m ore difficult toprovide in a language . Suppose you wanted to add som ething l ike nested func tion defini t ion support to Java . If you know Java well ,thinking about how to add such support is in the rea lm of nightm ares.

T he reason m ost languages a ren’t good a t support ing both of these tra i ts sim ultaneously is tha t they confl ic t with each other. T hericher a language is a t the sta rt , the m ore com plica ted i t is. And the m ore com plica ted the language , the m ore pa inful i t is tom uck with tha t language . T ha t’s why m aking your own changes to the m ost m ature program m ing languages is c lose to im possible .

Of course , i f you try hard enough, you can a lways m ake fundam enta l changes to any language . For instance , when C++ wasdeve loped, i t origina lly took the form of a C preprocessor. A spec ia l C program was writ ten tha t could take code writ ten in the newC++ dia lec t and convert i t into pla in-old C, which you could then just run through a standard C com pile r. T his is how BjarneStroustrup, the inventor of C++, was able to tweak the C language and add fea tures to turn i t into his own. However, wri t ing atransla tor such as this is an extrem ely difficult and tedious process tha t you would consider only as a last resort .

In contrast , L isp languages m ake i t extrem ely easy for an experienced L isper to a l te r the com pile r/ inte rpre te r tha t runs aprogram , while st i l l support ing rich language fea tures with extensive l ibra ries. In fac t , m essing a round with the language withinL isp is easie r than in any other language ever c rea ted!

For exam ple , wri t ing a func tion in L isp to ca lcula te the distance be tween two points would be sim ple , a s in m ost otherlanguages. But an experienced L isper would find i t equa lly easy to invent a new way to nest func tion defini t ions or devise a funkyif-then com m and. E ven writ ing your own objec t-oriented program m ing support inside L isp is not com plica ted (and m ost L ispershave probably done so a t som e point). In L isp, everyone ge ts to be a m ini-Stroustrup!

How does L isp m ake this nea t fea t possible? One of L isp’s core charac te rist ics is tha t writ ing a L isp direc t ly in L isp is, i tse lf,unbe lievably sim ple . It turns out tha t this is the key property tha t a l lows L isp to break the paradox of the two tra i ts. By sta rt ing outas a language tha t could perform a cool m athem atica l t rick of e legantly writ ing i tse lf, i t ended up possessing the very propertyneeded to be both fea ture -rich and tweakable . T ha t , in turn, m akes i t the perfec t tool for ac tua lly writ ing just about any kind ofprogram a t a l l!

T hink of i t this way: Give a program m er a fish com m and in his program m ing language , and he wil l ea t Chinese takeout anddrink Jolt for a day. Give a program m er a program m ing language tha t a l lows him to write his own fish com m and, and he’l l ea tChinese takeout and drink Jolt for a l i fe t im e (which, adm ittedly, would probably be cut short by nutri t iona l de fic ienc ies, and le t’snot even discuss the probable heart a rrhythm ias).

So, now you have an idea of why L isp is a ve ry cool and very unusua l program m ing language . It has a long and a typica l historycom pared with m ost program m ing languages. Most languages cam e from the world of engineering, whereas L isp origina ted from am ore m athem atica l background. It has a lot to offe r to those wil l ing to spend a l i t t le t im e lea rning som ething new.

Page 18: Land of Lisp - Barski M.D., Conrad

P art I. Lisp is P ower

Page 19: Land of Lisp - Barski M.D., Conrad

Chapter 1. G etting Started with Lisp

T his chapte r begins with an introduc tion to the various dia lec ts of L isp. T hen we’l l ta lk a bi t about ANSI Com m on L isp, thedia lec t tha t we’l l be using in this book. Fina lly, you’l l ge t sta rted by insta l l ing and test ing CL ISP, the im plem enta t ion of ANSICom m on L isp tha t wil l le t you run a l l the L isp gam es you’re going to be c rea ting!

Page 20: Land of Lisp - Barski M.D., Conrad

Lisp Dialec ts

Any language tha t obeys the centra l princ iples of L isp is considered a L isp dia lec t . Since these princ iples a re so sim ple , i t ’s notsurprising tha t l i te ra l ly hundreds of dia lec ts of L isp have been c rea ted. In fac t , since so m any budding L ispers c rea te the ir own L ispdia lec t as an exerc ise , the re m ay be thousands of pa rt ia l ly com ple ted L isps slum bering in long-abandoned direc tories on hard drivesacross the plane t . However, the vast m ajori ty of the L isp com m unity uses two L isps: ANSI Com m on L isp (often abbrevia ted CL )and Schem e.

In this book, we’l l be ta lking exc lusive ly about the ANSI Com m on L isp dia lec t , the sl ightly m ore popula r of the two.Neverthe less, m ost of the knowledge you’l l ga in from reading this book wil l a lso be re levant to Schem e (a l though the nam es offunc tions tend to diffe r som ewhat be tween the dia lec ts).

Page 21: Land of Lisp - Barski M.D., Conrad

A Tale of Two Lisps

Som e deep philosophica l diffe rences exist be tween ANSI Com m on L isp and Schem e, and they appea l to diffe rent program m erpersona li t ie s. Once you lea rn m ore about L isp languages, you can dec ide which dia lec t you pre fe r. T here is no right or wrongchoice .

T o a id you in your dec ision, I have c rea ted the fol lowing persona li ty test for you:

If you chose A, you l ike raw power in your language . You don’t m ind if your language is a bi t ugly, due to a lot of pragm aticcom prom ises, a s long as you can st i l l wri te t ight code . ANSI Com m on L isp is the best language for you! ANSI Com m on L isp tracesi ts ancestry m ost direc t ly from the anc ient L isp dia lec ts, buil t on top of m il l ions of program m er hours, giving i t inc redibly richfunc tiona li ty. Sure , i t has som e baroque func tion nam es due to countless historica l acc idents, but this L isp can rea l ly fly in theright hacker’s hands.

If you chose B, you l ike languages tha t a re c lean and e legant . You a re m ore inte rested in fundam enta l program m ing problem sand a re happy to while away on a beautiful m eadow, contem pla ting the beauty of your code , occasiona lly writ ing a research paperon theore t ica l com puting problem s. Schem e is the language for you! It was c rea ted in the m id-1970s by Guy L . Stee le and Gera ldJay Sussm an and involved som e soul-sea rching about the idea l L isp. Code in Schem e tends to be sl ightly m ore verbose , sinceSchem ers ca re m ore about m athem atica l puri ty in the ir code than c rea ting the shortest program s possible .

If you chose C, you’re som eone who wants i t a l l : the power of ANSI CL and the m athem atica l beauty of Schem e. At this t im e ,no L isp dia lec t com ple te ly fi ts the bi l l , but tha t could change in the future . One language tha t m ight work for you (a l though i t issac ri lege to m ake this c la im in a L isp book) is Haske ll . It is not considered a L isp dia lec t , but i ts fol lowers obey paradigm s popula ram ong L ispers, such as keeping the syntax uniform , support ing na tive l ists, and re lying heavily on higher-order func tions. Moreim portant , i t has an extrem e m athem atica l rigor (even m ore so than Schem e) tha t a l lows i t to hide very powerful func tiona li tyunder a squeaky c lean surface . It’s essentia l ly a wolf in sheep’s c lothing. L ike L isp, Haske ll is a language tha t any program m erwould benefi t from investiga ting further.

Page 22: Land of Lisp - Barski M.D., Conrad

Up-and-Coming Lisps

As just m entioned, the re rea l ly isn’t a t rue L isp dia lec t ava ilable ye t tha t possesses both the power and flexibil i ty of ANSICom m on L isp and the e legance of Schem e. However, som e new contenders on the horizon m ay a t ta in the best-of-both-worlds c rownin the near future .

One new L isp tha t is showing prom ise is Clojure , a dia lec t deve loped by Rich Hickey. Clojure is buil t on the Java pla tform ,a llowing i t to leverage a lot of m ature Java l ibra ries right out of the box. Also, Clojure conta ins som e c lever and well-thought-outfea tures to ease m ult i threaded program m ing, which m akes i t a use ful tool for program m ing seem ingly ubiquitous m ult icore CPUs.

Another inte rest ing cha llenger is Arc . It is a t rue L isp language be ing princ ipa lly deve loped by Paul Graham , a well-knownL isper. Arc is st i l l in an ea rly stage of deve lopm ent, and opinion varies wide ly on how m uch of an im provem ent i t is over otherL isps. Also, i ts deve lopm ent has been progressing a t a glac ia l ly slow pace . It wil l be a while before anyone can say if Arc m ight bea m eaningful contender.

W e’ll be dipping our toes in som e Arc and Clojure in the epilogue .

Page 23: Land of Lisp - Barski M.D., Conrad

Lisp Dialec ts Used for Scr ipting

Som e L isp dia lec ts a re used for sc ript ing, inc luding these :

E m acs L isp is used for sc ript ing inside the popula r (and overa l l awesom e) E m acs text editor.Guile Schem e is used as a sc ript ing language in severa l open source applica t ions.Script-Fu Schem e is used with the GIMP im age editor.

T hese dia lec ts a re forks from older versions of the m ain L isp branches and a re not typica lly used for c rea ting stand-a loneapplica t ions. However, they a re st i l l pe rfec tly respec table dia lec ts of L isp.

Page 24: Land of Lisp - Barski M.D., Conrad

ANSI Common Lisp

In 1981, in order to cope with the dizzying num ber of dia lec ts of the language , m em bers of the varying L isp com m unit iesdra fted a spec ifica t ion for a new dia lec t nam ed Com m on L isp. In 1986, this language , a fte r further adjustm ents, was turned intothe ANSI Com m on L isp standard. Many of the deve lopers of older ve rsions of L isp m odified the ir inte rpre te rs and com pile rs toconform to this new standard, which becam e the m ost popula r ve rsion of L isp and rem ains so to this day.

Note

T hroughout this book, the te rm Common Lisp re fe rs to the version of Com m on L isp defined by the ANSI standard.

A key design goa l with Com m on L isp was to c rea te a multiparadigm language , m eaning i t inc ludes support for m any diffe rentstyles of program m ing. You’ve probably heard of objec t-oriented programming, which can be done quite nice ly in Com m on L isp.Other program m ing styles you m ay not have heard of be fore inc lude func tional programming, generic programming, and domain-spec if ic language programming. T hese a re a l l we ll supported within Com m on L isp. You’l l be lea rning about each of these styles,a long with others, a s we progress through this book.

Page 25: Land of Lisp - Barski M.D., Conrad

G etting Started with CLISP

Many grea t L isp com pile rs a re ava ilable , but one in part icula r is easiest to ge t sta rted with: CL ISP, an open source Com m onL isp. CL ISP is sim ple to insta l l and runs on any opera t ing system .

Other popula r L isps inc lude Stee l Bank Com m on L isp (SBCL ), a fast Com m on L isp tha t’s considered a bi t m ore heavy-duty thanCL ISP and a lso open source ; Allegro Com m on L isp, a powerful com m erc ia l L isp by Franz , Inc ; L ispW orks; Clozure CL ; andCMUCL . Mac users m ay want to consider L ispW orks or Clozure CL , which wil l be easie r to ge t running on the ir m achines.However, for our purposes, CL ISP is the best choice .

Note

Start ing with Chapte r 12, we’l l be using som e CL ISP-spec ific com m ands tha t a re considered nonstandard. However, up unti l tha tpoint , any im plem enta t ion of Com m on L isp wil l work for running the exam ples in this book.

Page 26: Land of Lisp - Barski M.D., Conrad

Install ing CLISP

You can download a CL ISP insta l le r from http: / /c l isp. cons.org/ . It wil l run on W indows PCs, Macs, and L inux variants. On aW indows PC, you sim ply run an insta l l program . On a Mac , the re a re som e addit iona l steps, which a re de ta i led on the website .

On a Debian-based L inux m achine , you should find tha t CL ISP a lready exists in your standard sources. Just type apt-get installclisp a t the com m and l ine , and you’l l have CL ISP insta l led autom atica l ly.

For other L inux distributions (Fedora , SUSE , and so on), you can use standard packages l isted under “L inux packages” on theCL ISP website . And experienced L inux users can com pile CL ISP from source .

Page 27: Land of Lisp - Barski M.D., Conrad

Starting Up CLISP

T o run CL ISP, type clisp from your com m and l ine . If a l l goes according to plan, you’l l see the fol lowing prom pt:$ clisp

i i i i i i i ooooo o ooooooo ooooo oooooI I I I I I I 8 8 8 8 8 o 8 8I \ `+' / I 8 8 8 8 8 8\ `-+-' / 8 8 8 ooooo 8oooo`-__|__-' 8 8 8 8 8| 8 o 8 8 o 8 8------+------ ooooo 8oooooo ooo8ooo ooooo 8

Copyright (c) Bruno Haible, Michael Stoll 1992, 1993Copyright (c) Bruno Haible, Marcus Daniels 1994-1997Copyright (c) Bruno Haible, Pierpaolo Bernardi, Sam Steingold 1998Copyright (c) Bruno Haible, Sam Steingold 1999-2000Copyright (c) Sam Steingold, Bruno Haible 2001-2006

[1]>L ike a l l Com m on L isp environm ents, CL ISP wil l autom atica l ly place you into a read-eval-print loop (R E P L) a fte r you sta rt i t

up. T his m eans you can im m edia te ly sta rt typing in L isp code .T ry i t out by typing (+ 3 (* 2 4)). You’l l see the result printed be low the expression:[1]> (+ 3 (* 2 4))

11T his shows how the RE PL works. You type in an expression, and then the L isp wil l im m edia te ly eva lua te i t and re turn the

result ing va lue . W hen you want to shut down CL ISP, just type (quit).Now tha t you have CL ISP working on your com pute r, you’re ready to write a L isp gam e!

Page 28: Land of Lisp - Barski M.D., Conrad

What You've Learned

In this chapte r, we discussed the diffe rent dia lec ts of L isp and insta l l ing CL ISP. You lea rned the fol lowing a long the way:T here a re two m ain dia lec ts of L isp: Com m on L isp and Schem e. Both have a lot to offe r, but we’l l focus on Com m on L isp in

this book.Com m on L isp is a m ult ipa radigm language , m eaning tha t i t supports m any diffe rent program m ing styles.CL ISP is a Com m on L isp im plem enta t ion tha t is easy to se t up, m aking i t a grea t choice for a L isp novice .You can type in L isp com m ands direc t ly from the CL ISP REPL.

Page 29: Land of Lisp - Barski M.D., Conrad

Chapter 2. Creating Your F irst Lisp P rogram

Now tha t we’ve discussed som e of the philosophy of L isp and have a running CL ISP environm ent, we’re ready to write som eac tua l L isp code in the form of a sim ple gam e.

Page 30: Land of Lisp - Barski M.D., Conrad

The G uess-M y-Number G ame

T his first gam e we’l l wri te is pre t ty m uch the sim plest gam e im aginable . It ’s the c lassic guess-m y-num ber gam e.In this gam e, you pick a num ber from 1 to 100, and the com pute r has to guess i t .T he following shows what gam e play m ight look l ike if you pick the num ber 23. T he com pute r sta rts by guessing 50, and with

each successive guess, you ente r (smaller) or (bigger) unti l the com pute r guesses your num ber.> (guess-my-number)

50> (smaller)25> (smaller)12> (bigger)18> (bigger)21> (bigger)23

T o c rea te this gam e, we need to write three func tions: guess-my-number, smaller, and bigger. T he player sim ply ca l ls thesefunc tions from the RE PL . As you saw in the previous chapte r, when you sta rt CL ISP (or any other L isp), you a re presented with theRE PL , from which the com m ands you type wil l be read, then evaluated, and fina lly printed. In this case , we’re running thecom m ands guess-my-number, smaller, and bigger.

T o ca ll a func tion in L isp, you put pa rentheses a round i t , a long with any param ete rs you wish to give the func tion. Since thesepart icula r func tions don’t require any param ete rs, we sim ply surround the ir nam es in parentheses when we ente r them .

L e t’s think about the stra tegy behind this sim ple gam e. Afte r a l i t t le thought, we com e up with the fol lowing steps:

1. De te rm ine the upper and lower (big and sm all) l im it of the player’s num ber. Since the range is be tween 1 and100, the sm allest possible num ber would be 1 and the biggest would be 100.

2. Guess a num ber in be tween these two num bers.3. If the player says the num ber is sm alle r, lower the big l im it .4. If the player says the num ber is bigger, ra ise the sm all l im it .

By following these sim ple steps, cutt ing the range of possible num bers in ha lf with every guess, the com pute r can quickly hone inon the player’s num ber.

T his type of sea rch is ca l led a binary search. As you m ay know, binary sea rches l ike this a re used a l l the t im e in com pute rprogram m ing. You could fol low these sam e steps, for instance , to e ffic iently find a spec ific num ber given a sorted table of va lues.In tha t case , you would sim ply track the sm allest and la rgest row in tha t table , and then quickly hone in on the correc t row in anana logous m anner.

Page 31: Land of Lisp - Barski M.D., Conrad

Defining G lobal Var iables in Lisp

As the player ca l ls the func tions tha t m ake up our gam e, the program will need to track the sm all and big l im its. In order to dothis, we’l l need to c rea te two globa l va riables ca l led *small* and *big*.

Page 32: Land of Lisp - Barski M.D., Conrad

Defining the small and big Var iables

A variable tha t is de fined globa lly in L isp is ca l led a top-leve l de fini t ion. W e can c rea te new top-leve l de fini t ions with thedefparameter func tion:> (defparameter *small* 1)

*SMALL*> (defparameter *big* 100)*BIG*

T he func tion nam e defparameter is a bi t confusing, since i t doesn’t rea l ly have anything to do with param ete rs. W hat i t does isle t you define a global variable .

T he first a rgum ent we send to defparameter is the nam e of the new variable . T he aste risks surrounding the nam es *big* and*small*—affec tiona te ly ca l led earmuffs—are com ple te ly a rbitra ry and optiona l . L isp sees the aste risks as part of the variablenam es and ignores them . L ispers l ike to m ark a l l the ir globa l va riables in this way as a convention, to m ake them easy todist inguish from loca l va riables, which a re discussed la te r in this chapte r.

Note

Although earm uffs m ay be “optiona l” in a stric t ly technica l sense , I suggest tha t you use them . I cannot vouch for your sa fe ty ifyou ever post any code to a Com m on L isp newsgroup and your globa l va riables a re m issing the ir ea rm uffs.

Page 33: Land of Lisp - Barski M.D., Conrad

An Alternative G lobal Var iable Definition F unction

W hen you se t the va lue of a globa l va riable using defparameter, any va lue previously stored in the variable wil l be overwrit ten:> (defparameter *foo* 5)

FOO> *foo*5> (defparameter *foo* 6)FOO> *foo*6

As you can see , when we redefine the variable *foo*, i ts va lue changes.Another com m and tha t you can use for dec la ring globa l va riables, ca l led defvar, won’t overwrite previous va lues of a globa l

variable :> (defvar *foo* 5)

FOO> *foo*5> (defvar *foo* 6)FOO> *foo*5

Som e L ispers pre fe r to use defvar instead of defparameter when defining globa l va riables. In this book, however, we’l l be usingdefparameter exc lusive ly.

Note

W hen you read about L isp in other places, you m ay a lso see program m ers using the te rm dynamic variable or spec ial variablewhen re fe rring to a globa l va riable in Com m on L isp. T his is because globa l va riables in Com m on L isp have som e spec ia l abil i t ie s,which we’l l be discussing in future chapte rs.

Page 34: Land of Lisp - Barski M.D., Conrad

Basic Lisp Etiquette

T he way com m ands a re ca l led and the way code is form atted in L isp is som ewhat strange com pared with other languages. Firstof a l l , you need to surround the com m and (and i ts a rgum ents) with parentheses, a s with the defparameter func tion:> (defparameter *small* 1)

*SMALL*W ithout the parentheses, a com m and wil l not be ca l led.Also, spaces and l ine breaks a re com ple te ly ignored when L isp reads in your code . T ha t m eans you could ca l l this com m and in

any c razy way, with the sam e result :> ( defparameter

*small* 1)*SMALL*

Because L isp code can be form atted in such flexible ways, L ispers have a lot of conventions for form att ing com m ands, inc ludingwhen to use m ult iple l ines and indenta t ion. W e’ll loose ly fol low som e of the com m on indenta t ion conventions in the codeexam ples in this book. However, we’re m ore inte rested in writ ing gam es than in discussing source code indenta t ion rules, so we’renot going to be spending too m uch t im e on code layout rules in this book.

Page 35: Land of Lisp - Barski M.D., Conrad

Defining G lobal F unctions in Lisp

Our guess-m y-num ber gam e has the com pute r respond to the player’s request to sta rt the gam e, and then to requests for e i the rsm alle r or bigger guesses. For these , we need to define three globa l func tions: guess-my-number, smaller, and bigger. W e’ll a lsodefine a func tion to sta rt over with a diffe rent num ber, ca l led start-over. In Com m on L isp, func tions a re defined with defun, l ikethis:(defun function_name (arguments)

...)First , we spec ify the nam e and a rgum ents for the func tion. T hen we follow i t up with the code tha t com poses the func tion’s

logic .

Page 36: Land of Lisp - Barski M.D., Conrad

Defining the guess-my-number F unction

T he first func tion we’l l de fine is guess-my-number. T his func tion uses the va lues of the *big* and *small* va riables to genera tea guess of the player’s num ber. T he defini t ion looks l ike this:> (defun guess-my-number ()

(ash (+ *small* *big*) −1))

GUESS-MY-NUMBERT he em pty parentheses, (), a fte r the func tion nam e guess-my-number indica te tha t this func tion doesn’t require any param ete rs.Although you don’t need to worry about indenta t ion or l ine breaks when ente ring code snippe ts a t the RE PL , you m ust be sure to

place parentheses correc tly. If you forge t a pa renthesis or put one in the wrong place , you’l l m ost l ike ly ge t an e rror.W henever we run a piece of code l ike this in the RE PL , the result ing va lue of the ente red expression wil l be printed. E very

com m and in Com m on L isp genera tes a re turn va lue . T he defun com m and, for instance , sim ply re turns the nam e of the newly

crea ted func tion. T his is why we see the nam e of the func tion parroted back to us in the RE PL afte r we ca ll defun .W hat does this func tion do? As discussed ea rl ie r, the com pute r’s best guess in this gam e wil l be a num ber in be tween the two

lim its. T o accom plish this, we choose the average of the two l im its. However, i f the average num ber ends up be ing a frac t ion,we’l l want to use a near-average num ber, since we’re guessing only whole num bers.

W e im plem ent this in the guess-my-number func tion by first adding the num bers tha t represent the high and low l im its, thenusing the a ri thm etic shift func tion, ash, to ha lve the sum of the l im its and shorten the result . T he code (+ *small* *big*) adds

toge ther those two variables. Because the addit ion happens within another func tion ca ll , , the result ing sum is then passed tothe ash func tion.

T he parentheses surrounding the ash func tion and the addit ion (+) func tion a re m anda tory in L isp. T hese parentheses a re wha tte l l L isp, “ I want you to ca l l this func tion. ”

T he buil t-in L isp func tion ash looks a t a num ber in binary form , and then shifts i ts binary bits to the le ft or right , dropping anybits lost in the process. For exam ple , the num ber 11 writ ten in binary is 1011. W e can m ove the bi ts in this num ber to the le ft withash by using 1 a s the second a rgum ent:> (ash 11 1)

22T his produces 22, which is 10110 in binary. W e can m ove the bi ts to the right (and lop off the bi t on the end) by passing in −1 a s

the second a rgum ent:> (ash 11 −1)

5T his produces 5, which is 101 in binary.By using the ash func tion in guess-my-number, we a re continua lly ha lving our sea rch space of possible num bers to quickly

narrow down to the fina l correc t num ber. As a lready m entioned, this ha lving process is ca l led a binary search, a use ful technique incom pute r program m ing. T he ash func tion is com m only used for such binary sea rches in L isp.

L e t’s see wha t happens when we ca ll our new func tion:> (guess-my-number)

50Since this is our first guess, the output we see when ca ll ing this func tion te l ls us tha t everything is working as planned: T he

program picked the num ber 50, right in be tween 1 and 100.W hen program m ing in L isp, you’l l wri te m any func tions tha t won’t explic i t ly print va lues on the sc reen. Instead, they’l l sim ply

re turn the va lue ca lcula ted in the body of the func tion. For instance , le t’s say we wanted a func tion tha t just re turns the num ber 5.Here’s how we could write this:> (defun return-five ()

(+ 2 3))

Because the va lue ca lcula ted in the body of the func tion eva lua tes to 5, ca l l ing (return-five) wil l just re turn 5.T his is how guess-my-number is designed. W e see this ca lcula ted result on the sc reen (the num ber 50) not because the func tion

causes the num ber to display, but because this is a fea ture of the RE PL .

Note

If you’ve used other program m ing languages before , you m ay rem em ber having to write som ething l ike return... to cause ava lue to be re turned. In L isp, this is not necessa ry. T he fina l va lue ca lcula ted in the body of the func tion is re turnedautom atica l ly.

Page 37: Land of Lisp - Barski M.D., Conrad

Defining the smaller and bigger F unctions

Now we’ll wri te our smaller and bigger func tions. L ike guess-my-number, these a re globa l func tions defined with defun:

> (defun smaller ()

(setf *big* (1- (guess-my-number)))

(guess-my-number))SMALLER> (defun bigger ()

(setf *small* (1+ (guess-my-number)))(guess-my-number))BIGGER

First , we use defun to sta rt the defini t ion of a new globa l func tion smaller. Because this func tion takes no param ete rs, the

parentheses a re em pty .

Next, we use the setf func tion to change the va lue of our globa l va riable *big* . Since we know the num ber m ust besm alle r than the last guess, the biggest i t can now be is one less than tha t guess. T he code (1- (guess-my-number)) ca lcula tes this:It fi rst ca l ls our guess-my-number func tion to ge t the m ost recent guess, and then i t uses the func tion 1-, which subtrac ts 1 from theresult .

Fina lly, we want our smaller func tion to show us a new guess. W e do this by putt ing a ca l l to guess-my-number a s the fina l l ine

in the func tion body . T his t im e , guess-my-number wil l use the upda ted va lue of *big*, causing i t to ca lcula te the nextguess. T he fina l va lue of our func tion wil l be re turned autom atica l ly, causing our new guess (genera ted by guess-my-number) to bere turned by the smaller func tion.

T he bigger func tion works in exac tly the sam e m anner, except tha t i t ra ises the *small* va lue instead. Afte r a l l , i f you ca ll thebigger func tion, you a re saying your num ber is bigger than the previous guess, so the sm allest i t can now be (which is wha t the*small* va riable represents) is one more than the previous guess. T he func tion 1+ sim ply adds 1 to the va lue re turned by guess-my-

number .Here we see our func tions in ac t ion, with the num ber 56 as our guess:> (bigger)

75> (smaller)62> (smaller)56

Page 38: Land of Lisp - Barski M.D., Conrad

Defining the start-over F unction

T o com ple te our gam e, we’l l add the func tion start-over to rese t our globa l va riables:(defun start-over ()

(defparameter *small* 1)(defparameter *big* 100)(guess-my-number))

As you can see , the start-over func tion rese ts the va lues of *small* and *big* and then ca lls guess-my-number aga in to re turna new sta rt ing guess. W henever you want to sta rt a brand-new gam e with a diffe rent num ber, you can ca ll this func tion to rese t thegam e.

Page 39: Land of Lisp - Barski M.D., Conrad

Defining Local Var iables in Lisp

For our sim ple gam e, we’ve defined globa l va riables and func tions. However, in m ost cases you’l l want to l im it your defini t ionsto a single func tion or a block of code . T hese a re ca l led local va riables and func tions.

T o define a loca l va riable , use the com m and let. A let com m and has the fol lowing struc ture :

(let (variable declarations)

...body...)

T he first thing inside the let com m and is a l ist of va riable dec la ra t ions . T his is where we can dec la re one or m ore loca l

variables. T hen, in the body of the com m and (and only within this body), we can use these variables . Here is an exam ple ofthe let com m and:

> (let ((a 5)

(b 6))

(+ a b))11

In this exam ple , we’ve dec la red the va lues 5 and 6 for the variables a and b , re spec tive ly. T hese a re our variable

dec la ra t ions. T hen, in the body of the let com m and, we added them toge ther , result ing in the displayed va lue of 11.W hen using a le t expression, you m ust surround the entire l ist of dec la red variables with parentheses. Also, you m ust surround

each pa ir of va riable nam es and ini t ia l va riables with another se t of pa rentheses.

Note

Although the indenta t ion and l ine breaks a re com ple te ly a rbitra ry, because the nam es of the variables and the ir va lues in a letexpression form a kind of sim ple table , com m on prac tice is to a l ign the dec la red variables vert ica l ly. T his is why the b is placeddirec tly undernea th the a in the preceding exam ple .

Page 40: Land of Lisp - Barski M.D., Conrad

Defining Local F unctions in Lisp

W e define loca l func tions using the flet com m and. T he flet com m and has the fol lowing struc ture :

(flet ((function_name (arguments)

...function body...))

...body...)

At the top of the flet, we dec la re a func tion (in the first two l ines). T his func tion wil l then be ava ilable to us in the body

. A func tion dec la ra t ion consists of a func tion nam e, the a rgum ents to tha t func tion , and the func tion body , where weput the func tion’s code .

Here is an exam ple :

> (flet ((f (n)

(+ n 10)))

(f 5))15

In this exam ple , we define a single func tion, f, which takes a single a rgum ent, n . T he func tion f then adds 10 to this

variable n , which has been passed in i t . T hen we ca ll this func tion with the num ber 5 a s the a rgum ent, causing the va lue 15

to be re turned .As with let, you can define one or m ore func tions within the scope of the flet.A single flet com m and can be used to dec la re m ult iple loca l func tions a t once . Sim ply add m ult iple func tion dec la ra t ions in

the first pa rt of the com m and:

> (flet ((f (n)(+ n 10))

(g (n)(- n 3)))(g (f 5)))12

Here , we have dec la red two func tions: one nam ed f and one nam ed g . In the body of the flet, we can then re fe r toboth func tions. In this exam ple , the body first ca l ls f with 5 to yie ld 15, then ca lls g to subtrac t 3, leading to 12 a s a fina l result .

T o m ake func tion nam es ava ilable in defined func tions, we can use the labels com m and. It’s identica l in i ts basic struc ture tothe flet com m and. Here ’s an exam ple :

> (labels ((a (n)(+ n 5))

(b (n)

(+ (a n) 6)))

(b 10))21

In this exam ple , the loca l func tion a adds 5 to a num ber . Next, the func tion b is dec la red . It ca l ls the func tion a,

and then adds 6 to the result . Fina lly, the func tion b is ca l led with the va lue 10 . Since 10 plus 6 plus 5 equa ls 21, thenum ber 21 becom es the fina l va lue of the entire expression. T he spec ia l step tha t requires us to use labels instead of flet is where

the func tion b ca l ls the func tion a . If we had used flet, the func tion b would not have “known” about the func tion a.T he labels com m and le ts you ca ll one loca l func tion from another, and i t a l lows you to have a func tion ca ll i tse lf. T his is

com m only done in L isp code and is ca l led recursion. (You wil l see m any exam ples of recursion in future chapte rs. )

Page 41: Land of Lisp - Barski M.D., Conrad

What You've Learned

In this chapte r, we discussed the basic Com m on L isp com m ands for de fining variables and func tions. Along the way, you lea rnedthe fol lowing:

T o define a globa l va riable , use the defparameter com m and.T o define a globa l func tion, use the defun com m and.Use the let and flet com m ands to define loca l va riables and func tions, respec tive ly.T he func tion labels is l ike flet, but i t le ts func tions ca l l them se lves. Func tions tha t ca l l them se lves a re ca l led recursive

func tions.

Page 42: Land of Lisp - Barski M.D., Conrad

Chapter 3. Explor ing the Syntax of Lisp Code

As you’ve lea rned so fa r, L isp com m ands m ust be ente red in a ra ther unorthodox way, with parentheses surrounding eachcom m and. In this chapte r, we’l l explore why L isp works this way.

T o understand why any language—whether i t ’s a program m ing language or a hum an language—looks a ce rta in way, we need tobegin with two concepts from the fie ld of l inguist ics: syntax and sem antics.

Page 43: Land of Lisp - Barski M.D., Conrad

Syntax and Semantics

Here is a typica l sentence in the E nglish language :

My dog ate my homework.T his sentence has the correc t syntax for a sentence in E nglish. T he syntax of a piece of text represents the basic rules tha t i t

needs to fol low to be a va lid sentence . Here a re som e of the rules of sentences in the E nglish language tha t this text obeys:

T he sentence ends in a punc tua tion m ark.T he sentence conta ins a subjec t and a verb.T he sentence is m ade up of le t te rs in the E nglish a lphabe t (as opposed to E gyptian hie roglyphics or Sum erian

cune iform ).

However, the re is m ore to a sentence than just i ts syntax. W e a lso ca re about wha t the sentence ac tua lly m eans. W hen we ta lkabout the semantics of a sentence , we’re re fe rring to i ts m eaning.

For instance , he re a re som e sentences tha t a l l roughly have identica l sem antics:

My dog ate my homework.The canine, which I possess, has consumed my schoolassignment.Der Hund hat meine Hausarbeit gefressen.

T he first two a re just diffe rent ways of saying the sam e thing in E nglish. T he third sentence is in Germ an, but i t st i l l has thesam e m eaning and, hence , sem antics as the first two.

T he sam e dist inc tion be tween these two ideas exists in program m ing languages. For instance , he re is a va lid l ine of code writ tenin C++:((foo<bar>)*(g++)).baz(!&qux::zip->ding());T his l ine of code obeys the rules of C++ syntax. T o m ake m y point , I put in a lot of we ird syntax tha t is unique to C++, which

diffe rentia tes i t from other languages. If you were to place this l ine of code in a program of another program m ing language with adiffe rent syntax, i t would probably be inva lid and cause an e rror.

Of course , this C++ program m ing code a lso m eans som ething. If we were to put this l ine of code in a C++ program (in the propercontext), i t would cause your com pute r to do som ething. T he ac tions tha t a program perform s a re the semantics of the program . Itis usua lly possible to write a program tha t has the sam e sem antics in diffe rent program m ing languages; tha t is, the program will dothe sam e thing in both languages.

W hat a l l this boils down to is tha t m ost program m ing languages have sim ila r sem antic powers. However, basic L isp code isdiffe rent from code in any other m ajor program m ing language in tha t i t has a fa r sim ple r syntax. Having a simple syntax is ade fining feature of the Lisp language .

Page 44: Land of Lisp - Barski M.D., Conrad

The Building Blocks of Lisp Syntax

From the c razy l ine of C++ code in the previous sec tion, you can ge t the idea tha t C++ has a lot of we ird syntax—for indica tingnam espaces, de re fe renc ing pointe rs, pe rform ing casts, re fe renc ing m em ber func tions, pe rform ing Boolean opera t ions, and so on.

If you were to write a C++ com pile r, you would need to do a lot of ha rd work so tha t the com pile r could read this code and obeythe m any C++ syntax rules, be fore you could m ake any sense of the code .

W rit ing a L isp com pile r or inte rpre te r is m uch easie r. T he part of a L isp com pile r or inte rpre te r tha t reads in the code (whichL ispers ac tua lly ca l l the reader) is sim ple r than in C++ or any other m ajor program m ing language . T ake a random piece of L ispcode :(defun square (n)

(* n n))T his func tion dec la ra t ion, which c rea tes a func tion tha t sim ply squares a num ber, consists of nothing m ore than parentheses and

sym bols. In fac t , you can view i t a s just a bunch of nested l ists, de l im ited by parentheses.L isp only has one way of organiz ing bits of code : It uses parentheses to organize the code into l ists.All basic L isp code uses this sim ple l ist-l ike syntax:

But wha t sorts of things can we put into these l ists? W ell , besides other l ists, we can a lso put sym bols, num bers, and strings intoour code . Here , we’l l look a t these basic building blocks, or da ta types, you’l l use in L isp. (W e’ll discuss m any other Com m on L ispda ta types in la te r chapte rs. )

Page 45: Land of Lisp - Barski M.D., Conrad

Symbols

Symbols a re a fundam enta l type of da ta in L isp and a re used extensive ly. A sym bol in L isp is a stand-a lone word. Com m on L ispsym bols a re typica lly m ade up of le t te rs, num bers, and charac te rs l ike + - / * = < > ? ! _. Som e exam ples of va lid L ispsym bols a re foo, ice9, my-killer-app27, and even —<<==>>—.

Sym bols in Com m on L isp a re case-insensit ive (a l though m ost L ispers avoid using uppercase). T o i l lustra te this, we’l l use afunc tion ca lled eq, which le ts us see if two sym bols a re identica l :> (eq 'fooo 'FoOo)

TAs you can see , this func tion re turned T, which te l ls us tha t L isp considers these two sym bols to be identica l . (For now, ignore the

quota t ion m ark in front of the sym bols. T his wil l be expla ined short ly, when we discuss data mode . )

Page 46: Land of Lisp - Barski M.D., Conrad

Numbers

L isp supports both floa ting-point num bers and integers. W hen you write a num ber, the presence of a dec im al point de te rm ineswhether your num ber is seen as a floa ting-point num ber or an integer. T he num bers 1 and 1.0 a re two diffe rent enti t ie s in Com m onL isp.

For instance , i f you use m ost m ath func tions with both an integer and a floa ting-point num ber, the integer wil l becom e“poisoned, ” and a floa ting-point num ber wil l be re turned. Here ’s a case in point:> (+ 1 1.0)

2.0Note tha t the dec im al point in the re turned num ber, 2.0, indica tes tha t i t is a floa ting-point num ber.L isp can perform som e am azing fea ts with num bers, e spec ia l ly when com pared with m ost other languages. For instance , he re

we’re using the func tion expt to ca lcula te the fifty-third power of 53:> (expt 53 53)

24356848165022712132477606520104725518533453128685640844505130879576720609150223301256150373

Isn’t tha t cool? Most languages would choke on a ca lcula t ion involving such a la rge num ber.Fina lly, you should know tha t som ething weird could happen if you divide two integers:> (/ 4 6)

2/3T he division func tion is dividing the 4 by 6. But instead of re turning a frac t ion (0.66666. . . ) a s you m ight expec t , i t re turns a

rational number, represented as two integers with a division sym bol be tween them . So the 2/3 re sult represents a single ra t iona lnum ber, which is the m athem atica l ly idea l way to encode a frac t ion such as this.

Note tha t we ge t a diffe rent answer if the re is a floa ting-point num ber in our ca lcula t ion:> (/ 4.0 6)

0.6666667As in the previous exam ple , the num ber with the dec im al point (4.0) has poisoned our num bers to give us a frac t ion as a result .If you’re not a m ath geek, this m ight not be of m uch use to you, but a t least you now know what’s happening if you see this sort

of thing while you’re coding. You can a lso rest a ssured tha t L isp wil l do the right thing with this num ber when you use i t la te r on inanother ca lcula t ion. L isp is sm art .

Page 47: Land of Lisp - Barski M.D., Conrad

Str ings

T he last basic building block in L isp is the string. Although strings a ren’t rea l ly tha t fundam enta l to L isp from a theore t ica lstandpoint , any program tha t com m unica tes with a hum an wil l usua lly need strings, because hum ans l ike to com m unica te withtext .

T o indica te a string in L isp, surround charac te rs with double quotes. For exam ple , "Tutti Frutti" is a va lid string.W e can display a string using a func tion ca lled princ:> (princ "Tutti Frutti")

Tutti Frutti

"Tutti Frutti"Notice tha t print ing our text a t the RE PL [1] wil l cause the text to appear twice . First , we see the ac tua l print ing caused by the

princ com m and . However, since the RE PL will a lways show the result of eva lua ting the ente red expression, we see our

string parroted back to us . T his is because the princ func tion a lso re turns a va lue , which happens to be the source string.A string can a lso conta in so-ca lled escaped charac ters. If you want a string to inc lude double quotes or a backslash, you’l l need

to pre fix these charac te rs with a backslash. For exam ple , this string has two escaped quotes:> (princ "He yelled \"Stop that thief!\" from the busy street.")

He yelled "Stop that thief!" from the busy street.As you can see , the backslashes in front of the two quotes te l l L isp tha t these a re l i te ra l quota t ion m arks in the string, shown in

the displayed string just l ike any other charac te r.

[1] As discussed in Chapte r 2, in a read-eva l-print loop (or RE PL ), the func tions we ente r wil l be read, then eva lua ted, andfina lly printed.

Page 48: Land of Lisp - Barski M.D., Conrad

H ow Lisp Distinguishes Between Code and Data

W hen we write our L isp program , how does L isp dec ide which parts of our program consist of code (stuff to be executed) andwhich parts a re just da ta? T he syntax of L isp has a spec ia l way of dist inguishing be tween the two.

Com m on L isp uses two m odes when i t reads your code : a code mode and a data mode . You can switch be tween these two m odeswhen writ ing L isp code .

Page 49: Land of Lisp - Barski M.D., Conrad

Code M ode

W henever you type som ething into the L isp RE PL , the com pile r assum es tha t you’re ente ring a com m and you want to execute .In other words, L isp a lways assum es tha t you’re writ ing code and defaults to code m ode .

As we’ve a lready discussed, L isp wil l expec t L isp code to be ente red as a l ist . However, the code should be in a spec ia l type ofl ist : a form. So when you’re in code m ode , as you a re when you sta rt typing into the RE PL , the com m ands you ente r need to bestruc tured as form s:

A form is sim ply a l ist with a spec ia l com m and a t the beginning—typica lly the nam e of a func tion.W hen reading a form , L isp sends a l l other i tem s in the l ist to the func tion as param ete rs. For exam ple , ente r the fol lowing into

your RE PL :> (expt 2 3)

8T his ca lcula tes 2^3 = 8. It does this by ca l l ing expt, which com putes an exponent. T his com m and was ente red in the standard

way for L isp: as a form with the func tion nam e a t the beginning.W hen L isp reads the text for the param ete rs of such a com m and, i t usua lly assum es tha t these param ete rs a re also in code m ode .

Here’s an exam ple :> (expt 2 (+ 3 4))

128T his exam ple has two nested form s. L isp first looks a t the entire expression in code m ode . It de te rm ines tha t we’ve ente red a

form for the expt com m and. T hen L isp looks a t the a rgum ents to this com m and, a lso in code m ode . One of these a rgum ents (+ 34) is a form in i ts own right . T his form is then executed, yie lding 7. Afte rward, this result is passed to the oute r expt form , whichis then executed.

Page 50: Land of Lisp - Barski M.D., Conrad

Data M ode

As you m ight im agine , any stuff writ ten in da ta m ode is t rea ted as da ta . T his m eans the com pute r wil l not t ry to “execute” i t ,which a l lows us to have inform ation in our code tha t’s just pla in old da ta .

L e t’s take a look a t da ta m ode in ac t ion. W e’ll ente r the sam e form tha t we ente red in code m ode in the previous exam ple ,with one diffe rence :> '(expt 2 3)

(expt 2 3)T his t im e , we put a single quote in front of the l ist . Instead of responding with the sum of the num bers 1 and 2, L isp sim ply

parrots our expression to us. T he single quote te l ls L isp to trea t the subsequent form as a chunk of da ta—sim ply a l ist of i tem s. L ispthen prints the result of eva lua ting what we ente red, which is the l ist i tse lf. It ignores any func tions or va riables in our l ist , t rea t ingeverything as da ta .

Plac ing a quote in front of l ists so tha t they won’t be eva lua ted as a com m and is ca l led quoting. By using quoting, you can te l lL isp, “T his next pa rt isn’t a com m and. It’s just a chunk of da ta for m y program .”

Page 51: Land of Lisp - Barski M.D., Conrad

Lists in Lisp

L ists a re a c ruc ia l fea ture in L isp. T hey a re wha t hold a l l your L isp code (as well a s da ta ) toge ther. T ake any basic piece ofL isp code , such as the fol lowing:(expt 2 3)T his piece of code conta ins a sym bol (expt) and two num bers, a l l t ied toge ther as a l ist , indica ted by the parentheses.

You can think of a L isp program as a house . If you were to build a house in L isp, your walls would be m ade out of l ists. T hebricks would be m ade out of sym bols, num bers, and strings. However, a wa ll needs m orta r to hold i t toge ther. In the sam e way, l istsin L isp a re he ld toge ther by struc tures ca l led cons ce l ls.

Page 52: Land of Lisp - Barski M.D., Conrad

Cons Cells

L ists in L isp a re he ld toge ther with cons ce l ls. Understanding the re la t ionship be tween cons ce l ls and l ists wil l give you a be tte ridea of how L isp works.

A cons ce l l looks l ike this:

It’s m ade of two l i t t le connec ted boxes, both of which can point a t other things. A cons ce l l can point to another cons ce l l oranother type of L isp da ta . By be ing able to point to two diffe rent things, i t ’s possible to l ink cons ce l ls toge ther into l ists. In fac t ,l ists in L isp a re just an abstrac t i l lusion—all of them are ac tua lly com posed of cons ce l ls.

For instance , suppose we c rea te the l ist '(1 2 3). Here ’s how this l ist is represented in com pute r m em ory:

It’s c rea ted using three cons ce l ls. E ach ce ll points to a num ber, a s well a s the next cons ce l l for the l ist . T he fina l cons ce l l

Page 53: Land of Lisp - Barski M.D., Conrad

then points a t nil, to te rm ina te the l ist . (If you’ve ever used a l inked l ist in another program m ing language , this is the sam e basicidea . ) You can think of this a rrangem ent l ike a ca l l ing cha in for your friends: “W hen I know about a pa rty this weekend, I’l l ca l lBob, and then Bob wil l ca l l L isa , who wil l ca l l . . . ” E ach person in a ca l l ing cha in is responsible for only one phone ca ll , whichac tiva tes the next ca l l in the l ist .

Page 54: Land of Lisp - Barski M.D., Conrad

List F unctions

Manipula t ing l ists is extrem ely im portant in L isp program m ing. T here a re three basic func tions for m anipula t ing cons ce l ls (andhence l ists) in L isp: cons, car, and cdr.

The cons F unction

If you want to l ink any two pieces of da ta in your L isp program (regardless of type), the usua l way to do tha t is with the consfunc tion. W hen you ca ll cons, the L isp com pile r typica lly a l loca tes a sm all chunk of m em ory, the cons ce l l , tha t can hold tworefe rences to the objec ts be ing l inked. (Usua lly, the second of the two i tem s be ing l inked wil l be a l ist . ) For exam ple , le t’s l ink thesym bol chicken to the sym bol cat:> (cons 'chicken 'cat)

(CHICKEN . CAT)As you can see , cons re turns a single objec t , the cons ce l l , represented by parentheses and a dot be tween the two connec ted

item s. Don’t confuse this with a regula r l ist . T he dot in the m iddle m akes this a cons ce l l , just l inking those two i tem s toge ther.Notice how we pre fix our two pieces of da ta with a single quote to m ake sure tha t L isp sees them as just da ta and doesn’t t ry to

eva lua te them as code .If instead of another piece of da ta , we a t tach the sym bol nil on the right side of the l ist , som ething spec ia l happens:> (cons 'chicken 'nil)

(CHICKEN)Unlike with our cat, the nil does not show in the output this t im e . T here ’s a sim ple reason for this: nil is a spec ia l sym bol tha t

is used to te rm ina te a l ist in L isp. T ha t sa id, the L isp RE PL is taking a shortcut and just saying tha t we c rea ted a l ist with oneitem , our chicken. It could have displayed the result by explic i t ly showing our cons ce l l and print ing (CHICKEN . NIL). However,because this result is coinc identa l ly a lso a l ist , i t instead wil l show the l ist nota t ion.

T he lesson here is tha t L isp wil l a lways go out of i ts way to “hide” the cons ce l ls from you. W hen i t can, i t wil l show yourresults using l ists. It wil l show a cons ce l l (with the dot be tween the objec ts) only if the re isn’t a way to show your result using l ists.

T he previous exam ple can a lso be writ ten l ike this:> (cons 'chicken ())

(CHICKEN)T he empty l ist , (), can be used inte rchangeably with the nil sym bol in Com m on L isp. T hinking of the te rm ina tor of a l ist a s an

em pty l ist m akes sense . W hat do you ge t when you add a chicken to an em pty l ist? Just a l ist with a chicken in i t . T he consfunc tion a lso can add a new i tem to the front of the l ist . For exam ple , to add pork to the front of a l ist conta ining (beefchicken), use cons l ike so:> (cons 'pork '(beef chicken))

(PORK BEEF CHICKEN)W hen L ispers ta lk about using cons, they say they a re consing som ething. In this exam ple , we consed pork to a l ist conta ining

beef and chicken.Since a l l l ists a re m ade of cons ce l ls, our (beef chicken) l ist m ust have been c rea ted from its own two cons ce l ls, pe rhaps l ike

this:> (cons 'beef (cons 'chicken ()))

(BEEF CHICKEN)Com bining the previous two exam ples, we can see wha t a l l the l ists look l ike when viewed as conses. T his is wha t is really

happening:> (cons 'pork (cons 'beef (cons 'chicken ())))

(PORK BEEF CHICKEN)Basica lly, this is te l l ing us tha t when we cons toge ther a l ist of three i tem s, we ge t a l ist of three i tem s. No wholesa le copying or

de le t ing of da ta ever needs to take place .T he RE PL echoed back to us our ente red i tem s as a l ist , (pork beef chicken), but i t could just a s easi ly (though a l i t t le le ss

conveniently) have reported back the i tem s exac tly as we ente red them : (cons 'pork (cons 'beef (cons 'chicken ()))). E ithe rresponse would have been perfec tly correc t . In Lisp, a chain of cons ce l ls and a l ist are exac tly the same thing.

The car and cdr F unctions

L ists a re just long cha ins of two-i tem ce lls.T he car func tion is used for ge tt ing the thing out of the f irst slot of a ce l l :> (car '(pork beef chicken))

PORKT he cdr func tion is used to grab the va lue out of the second slot , or the rem ainder of a l ist :> (cdr '(pork beef chicken))

(BEEF CHICKEN)You can string toge ther car and cdr into new func tions l ike cadr, cdar, or cadadr. T his le ts you succ inc tly extrac t spec ific

pieces of da ta out of com plex l ists. E nte ring cadr is the sam e as using car and cdr toge ther—it re turns the second i tem from a l ist .(T he first slot of the second cons ce l l would conta in tha t i tem . ) T ake a look a t this exam ple :

> (cdr '(pork beef chicken))(BEEF CHICKEN)

> (car '(beef chicken))BEEF

> (car (cdr '(pork beef chicken)))BEEF

> (cadr '(pork beef chicken))BEEF

Page 55: Land of Lisp - Barski M.D., Conrad

W e know tha t cdr wil l take away the first i tem in a l ist . If we then take tha t shortened l ist and use car, we’l l ge t the first

i tem in the new l ist . T hen, i f we use these two com m ands toge ther, we’l l ge t the second i tem in the origina l l ist .

Fina lly, i f we use the cadr com m and, i t gives us the sam e result a s using car and cdr toge ther . E ssentia l ly, using the cadrcom m and is the sam e as saying tha t you want the second i tem in the l ist .

The l ist F unction

For convenience , Com m on L isp has m any func tions buil t on top of the basic three—cons, car, and cdr. A useful one is the listfunc tion, which does the dirty work of c rea ting a l l the cons ce l ls and builds our l ist a l l a t once :> (list 'pork 'beef 'chicken)

(PORK BEEF CHICKEN)Rem em ber tha t the re is no diffe rence be tween a l ist c rea ted with the list func tion, one c rea ted by spec ifying individua l cons

ce lls, or one c rea ted in da ta m ode using the single quote . T hey’re a l l the sam e anim al.

Page 56: Land of Lisp - Barski M.D., Conrad

Nested Lists

L ists can conta in other l ists. Here ’s an exam ple :'(cat (duck bat) ant)T his is a l ist conta ining three i tem s. T he second i tem of this l ist is (duck bat), which is a l ist i tse lf. T his is an exam ple of a

nested l ist .However, under the hood, these nested l ists a re st i l l just m ade out of cons ce l ls. L e t’s look a t an exam ple where we pull i tem s

out of nested l ists. Here , the first i tem is (peas carrots tomatoes) and the second i tem is (pork beef chicken):

> (car '((peas carrots tomatoes) (pork beef chicken)))(PEAS CARROTS TOMATOES)

> (cdr '(peas carrots tomatoes))(CARROTS TOMATOES)

> (cdr (car '((peas carrots tomatoes) (pork beef chicken))))(CARROTS TOMATOES)

> (cdar '((peas carrots tomatoes) (pork beef chicken)))(CARROTS TOMATOES)

T he car func tion gives us the first i tem in the l ist , which is a l ist in this case . Next, we use the cdr com m and to chop off

the first i tem from this inner l ist , leaving us with (CARROTS TOMATOES) . Using these com m ands toge ther gives this sam e result

. Fina lly, using cdar gives the sam e result a s using cdr and car separa te ly .As dem onstra ted in this exam ple , cons ce l ls a l low us to c rea te com plex struc tures, and we use them here to build a nested l ist .

T o prove tha t our nested l ist consists sole ly of cons ce l ls, he re is how we could c rea te this nested l ist using only the cons com m and:> (cons (cons 'peas (cons 'carrots (cons 'tomatoes ())))

(cons (cons 'pork (cons 'beef (cons 'chicken ()))) ()))((PEAS CARROTS TOMATOES) (PORK BEEF CHICKEN))

Here a re som e m ore exam ples of func tions based on car and cdr tha t we could use on our da ta struc ture :> (cddr '((peas carrots tomatoes) (pork beef chicken) duck))

(DUCK)> (caddr '((peas carrots tomatoes) (pork beef chicken) duck))DUCK> (cddar '((peas carrots tomatoes) (pork beef chicken) duck))(TOMATOES)> (cadadr '((peas carrots tomatoes) (pork beef chicken) duck))BEEF

Com m on L isp a lready defines a l l these func tions for you. You can use any func tion with the nam e c*r right out of the box, up tofour leve ls deep. In other words, cadadr wil l a lready exist for you to use , whereas cadadar (which is five leve ls deep) does not (youwould need to write tha t func tion yourse lf). T hese func tions m ake i t easy to m anipula te cons ce l ls-based struc tures in L isp, nom atte r how com plica ted they m ight be .

Page 57: Land of Lisp - Barski M.D., Conrad
Page 58: Land of Lisp - Barski M.D., Conrad
Page 59: Land of Lisp - Barski M.D., Conrad

What You've Learned

In this chapte r, we discussed the basic L isp syntax. Along the way, you lea rned the fol lowing:Parentheses in L isp a re the re to keep the am ount of syntax to a m inim um .L ists a re c rea ted from cons ce l ls.You can c rea te l ists by m aking cons ce l ls with the cons com m and.You can inspec t the pieces of a l ist with car and cdr.

Page 60: Land of Lisp - Barski M.D., Conrad

P art II. Lisp is Symmetry

Page 61: Land of Lisp - Barski M.D., Conrad

Chapter 4. M aking Dec isions with Conditions

In the previous chapte rs, you lea rned som e basic L isp com m ands, as well a s som e of the philosophy behind L isp. In this chapte r,we’l l be looking in de ta i l a t com m ands for handling condit ions. T he e legance of these com m ands shows tha t the unusua l philosophyand design of L isp has rea l prac tica l benefi ts.

Page 62: Land of Lisp - Barski M.D., Conrad

The Symmetry of nil and ()

One thing is pa rt icula rly striking when we look a t how L isp com m ands and da ta struc tures work: T hey a re im bued with sym m etryin every conce ivable way. T his sym m etry can give your L isp code a ce rta in e legance tha t other languages cannot have , and L isp’ssim ple syntax is an im portant fac tor in m aking this sym m etry possible .

Page 63: Land of Lisp - Barski M.D., Conrad

Empty Equals F alse

Since the L isp philosophy strongly em phasizes the use of l ists to store and m anipula te inform ation, i t wil l com e as no surprisetha t the design of Com m on L isp favors behaviors tha t m ake i t easy to sl ice and dice such l ists. T he m ost profound design dec isionm ade in Com m on L isp, with regard to l ists, is tha t i t autom atica l ly trea ts an em pty l ist a s a fa lse va lue when eva lua ting acondit ion:> (if '()

'i-am-true'i-am-false)I-AM-FALSE

> (if '(1)'i-am-true'i-am-false)I-AM-TRUE

T his exam ple shows tha t when we pass the em pty l ist () into an if form , i t eva lua tes as a fa lse va lue , whereas a l ist tha tconta ins an i tem eva lua tes as true .

Because we can easi ly de tec t an em pty l ist , we can process l ists using recursion. W ith this technique , we can take i tem s fromthe front of a l ist and send the rest of the l ist back to the sam e func tion unti l the l ist is em pty. (It’s a good thing tha t de tec t ingem pty l ists is so easy, because so m any func tions in L isp end up be ing l ist-ea te rs. )

L e t’s look a t a com m on l ist-ea t ing func tion, which ca lcula tes the length of a l ist .> (defun my-length (list)

(if list(1+ (my-length (cdr list)))0))> (my-length '(list with four symbols))4

T his func tion is writ ten in c lassic L isp style . It ca l ls i tse lf recursive ly as i t chom ps i tem s off the front of the l ist . Ca ll ing yourse lfin this way is not only a l lowed in L isp, but is often strongly encouraged. L ists in L isp a re recursive (conses of conses of conses . .. ), so the ac t of consum ing a l ist m aps na tura l ly onto func tions tha t a re recursive .

Note

Call ing yourse lf recursive ly can som etim es m ake for slow code . In Chapte r 14, we’l l rewrite the my-length func tion using aspec ia l , potentia l ly faste r, type of recursion.

Page 64: Land of Lisp - Barski M.D., Conrad

The F our Disguises of ()

Not only does the em pty l ist eva lua te to fa lse , but i t is the only fa lse va lue in Com m on L isp. A ny value not equivalent to anempty l ist wil l be considered a true va lue . T his expla ins why the expression '(1) in the ea rl ie r exam ple was trea ted as true .However, the re a re som e other expressions in L isp tha t a re disguises for the one and only em pty l ist :

W e can see tha t the expressions in this table a re equiva lent by com paring them with one another:

(eq '() nil) ==> T

(eq '() ()) ==> T

(eq '() 'nil) ==> TNotice tha t the only va lue in the table tha t seem s norm al is the quoted l ist on the le ft side of the com parisons. T he other three

a ll seem to break the rules of L isp form s tha t we ta lked about in the previous chapte r.T he first two exam ples a re part icula rly puzz ling. T hey a re m issing the quota t ion m ark tha t te l ls the L isp environm ent, “Hey,

this i tem is a piece of da ta , not code!” In the case of nil, you would expec t tha t this would ac tua lly be the nam e of a va riabletha t could have som e kind of a rbitra ry va lue . In the case of the unquoted (), the re ’s no way you could te l l wha t would happen. T heparentheses look l ike a form of code tha t needs to be eva lua ted, but a L isp form a lways has a sym bol a t the beginning, te l l ing i twha t to do. W hat do we do when there ’s nothing inside the form a t a l l?

T he bottom line is tha t Com m on L isp is a rchitec ted behind the scenes to m ake sure a l l four of these va lues look l ike an em ptylist when you use them in your program , a l lowing m ost L isp condit iona ls to be writ ten with an e legant brevity. For instance , the re

is a constant nam ed nil tha t eva lua tes to i tse lf and a l lows you to om it the quota t ion m ark in the first case . T he second case

is a na tura l by-produc t of how Com m on L isp parses an em pty form . T he third case is due to a requirem ent in theCom m on L isp spec tha t says tha t () and nil should be trea ted the sam e.

Although there ’s a ce rta in beauty to having a l l of these va lues be the sam e, not every L isper agrees with this sentim ent. Afte ra l l , a re fa lse and em pty l ist rea l ly the sam e kind of thing? T he c rea tors of the other popula r dia lec t of L isp, Schem e, fe l tdiffe rently about this issue , and pre fe rred to keep the concepts of fa lsi ty and em pty l ist com ple te ly separa te , a t a sl ight cost tocode brevity.

Page 65: Land of Lisp - Barski M.D., Conrad

The Conditionals: i f and Beyond

Now tha t you understand how L isp handles true and fa lse , le t’s look a t if and som e of the other condit iona l com m ands.

Page 66: Land of Lisp - Barski M.D., Conrad

O ne Thing at a Time with if

T he if com m and can be used to m ake diffe rent things happen when things a re true (such as when 1 + 2 = 3) or fa lse (such aswhen 1 + 2 = 4).> (if (= (+ 1 2) 3)

'yup'nope)YUP

> (if (= (+ 1 2) 4)'yup'nope)NOPE

T he if com m and can a lso be used to check whether a l ist is em pty:> (if '(1)

'the-list-has-stuff-in-it'the-list-is-empty)THE-LIST-HAS-STUFF-IN-IT

> (if '()'the-list-has-stuff-in-it'the-list-is-empty)THE-LIST-IS-EMPTY

So fa r, the only way to branch on a condit ion tha t we’ve looked a t has been the if com m and:> (if (oddp 5)

'odd-number'even-number)ODD-NUMBER

All we’re doing here is checking whether the num ber 5 is odd, then, depending on the result , eva lua ting one of the two followingexpressions in the if form . Since 5 is odd, i t eva lua tes the first such expression, and the form as a whole re turns odd-number.

T here ’s a lot happening in this ha rm less-looking l i t t le com m and—stuff tha t’s im portant to understanding L isp. Here a re twoim portant observa tions:

Only one of the expressions a fte r the if is ac tua lly eva lua ted.W e can only do one thing in an if sta tem ent.

Usua lly, when a func tion is executed in L isp, a l l the expressions a fte r the func tion nam e a re eva lua ted, be fore the func tion i tse lfis eva lua ted. However, if does not fol low these rules. T o see this, consider the fol lowing exam ple :> (if (oddp 5)

'odd-number(/ 1 0))ODD-NUMBER

Any se lf-respec ting, law-abiding L isp func tion would kick your butt to the curb if you tried to run this code , because you’redividing by ze ro.

But if is not just a func tion. It’s a spec ial form, which gives i t spec ia l privi leges, such as the right to not eva lua te a l l i tsparam ete rs in the norm al way. T his m akes sense , since the whole point of a condit ion is to run som e stuff but not other stuff. In thiscase , i t just m erri ly ignores the division by ze ro, since i t ’s in the part of the branch tha t applies only to even num bers. Condit iona lcom m ands in L isp a re typica lly spec ia l form s.

Page 67: Land of Lisp - Barski M.D., Conrad

Note

Som e of the condit iona l com m ands m ay be m acros, which a re som ething l ike use r-c rea ted spec ia l form s. Be ing a spec ia l formusua lly im plies tha t a com m and is direc t ly “baked in” to the language . In Chapte r 16, you’l l lea rn how to write such m acrosyourse lf.

Since only one expression inside an if is ever eva lua ted, i t ’s im possible to do two or m ore separa te things inside your branch.T here is ac tua lly a c lever style of program m ing (ca lled func tional programming, a s we’l l discuss in Chapte r 14), which considers

this a Good T hing. However, for cases when you rea l ly want to do m ore than one thing, you can use a spec ia l com m and, progn, towedge in extra com m ands in a single expression. W ith progn, only the last eva lua tion is re turned as the va lue of the ful lexpression. In this next exam ple , for instance , we use the com m and to se t a spec ia l globa l va riable direc t ly inside our condit iona lbranch.> (defvar *number-was-odd* nil)

> (if (oddp 5)(progn (setf *number-was-odd* t)'odd-number)'even-number)ODD-NUMBER

> *number-was-odd*T

Page 68: Land of Lisp - Barski M.D., Conrad
Page 69: Land of Lisp - Barski M.D., Conrad

G oing Beyond if: The when and unless Alternatives

Since i t’s kind of a pa in to use progn every t im e you want to do m ult iple things inside an if, L isp has severa l other com m andstha t inc lude an implic i t progn. T he m ost basic of these a re when and unless:> (defvar *number-is-odd* nil)

> (when (oddp 5)(setf *number-is-odd* t)'odd-number)ODD-NUMBER

> *number-is-odd*T

> (unless (oddp 4)(setf *number-is-odd* nil)'even-number)EVEN-NUMBER

> *number-is-odd*NIL

W ith when, a l l the enc losed expressions a re eva lua ted when the condit ion is t rue . W ith unless, a l l the enc losed expressions a reeva lua ted when the condit ion is fa lse . T he trade-off is tha t these com m ands can’t do anything when the condit ion eva lua tes in theopposite way; they just re turn nil and do nothing.

Page 70: Land of Lisp - Barski M.D., Conrad

The Command That Does It All: cond

But wha t do you do if you’re the kind of coder who wants i t a l l? Maybe you just a in’t in a com prom isin’ m ood and want afunc tion tha t wil l do everything! W ell , L isp has you covered.

T he cond form is the c lassic way to do branching in L isp. T hrough the l ibe ra l use of pa rentheses, i t a l lows for an im plic i t progn,can handle m ore than one branch, and can even eva lua te severa l condit ions in succession. Since cond has been a round since theL isp Stone Age , and i t’s com prehensive in i ts abil i t ie s, m any L isp program m ers consider i t to be the one true L isp condit iona l .

Here ’s an exam ple :> (defvar *arch-enemy* nil)

> (defun pudding-eater (person)(cond ((eq person 'henry) (setf *arch-enemy* 'stupid-lisp-alien)'(curse you lisp alien - you ate my pudding))

((eq person 'johnny) (setf *arch-enemy* 'useless-old-johnny)'(i hope you choked on my pudding johnny))

(t '(why you eat my pudding stranger ?))))

> (pudding-eater 'johnny)(I HOPE YOU CHOKED ON MY PUDDING JOHNNY)> *arch-enemy*JOHNNY> (pudding-eater 'george-clooney)(WHY YOU EAT MY PUDDING STRANGER ?)

Page 71: Land of Lisp - Barski M.D., Conrad

As you can see , the body of a cond uses a layer of pa rentheses to separa te the diffe rent branches of the condit ion. T hen the firstexpression of each parenthesized part conta ins the condit ion for m aking tha t branch ac tive . In our exam ple , we have diffe rent

branches for each type of pudding thie f: one for Henry , one for Johnny , and one for everyone e lse . W e use eq tocom pare the supplied person’s nam e with each potentia l pe rpe tra tor.

T he condit ions in a cond form are a lways checked from the top down, so the first successful m atch drives the behavior. In this

exam ple , the last branch has a condit ion of t (for t rue ), guarantee ing tha t a t least the last branch wil l a lways be eva lua ted.T his is a com m on cond idiom .

As with when and unless, the triggered branch m ay conta in m ore than one com m and, since the re is an im plic i t progn. In this

case , the first two branches se t an extra *arch-enemy* va riable , besides supplying a re turn variable .

Page 72: Land of Lisp - Barski M.D., Conrad

Branching with case

L et’s look a t one fina l L isp com m and: the case form . It is com m on to use the eq func tion for condit iona ls, and case le ts yousupply a va lue to com pare aga inst . Using case, we can rewrite the previous exam ple as fol lows:> (defun pudding-eater (person)

(case person((henry) (setf *arch-enemy* 'stupid-lisp-alien)'(curse you lisp alien - you ate my pudding))((johnny) (setf *arch-enemy* 'useless-old-johnny)'(i hope you choked on my pudding johnny))(otherwise '(why you eat my pudding stranger ?))))

T his version of the code is a lot easie r on the eyes. T he nam e of the person handled by each part of the case sta tem ent is c lea rlyvisible—it’s not hidden inside an equa li ty check. Depending on which version of L isp you use , a case sta tem ent l ike this m ay a lsobe m ore e ffic ient , e spec ia l ly with longer sta tem ents, where la rger num bers of cases a re handled.

Warning

Because the case com m and uses eq for com parisons, i t is usua lly used only for branching on sym bol va lues. It cannot be used tobranch on string va lues, am ong other things. See Com paring Stuff: eq, equa l , and More in Com paring Stuff: eq, equa l , and More forde ta i ls.

Page 73: Land of Lisp - Barski M.D., Conrad

Cool Tr icks with Conditions

T he fundam enta l design of L isp le ts you ge t a lot of m ileage out of a few sim ple com m ands. Spec ifica l ly, a couple ofcounte rintuit ive tricks involving condit ions in L isp can he lp you write c leaner code . T he first involves two new condit iona lcom m ands. T he second takes advantage of L isp’s sim ple conception of true and fa lse .

Page 74: Land of Lisp - Barski M.D., Conrad

Using the Stealth Conditionals and and or

T he condit iona ls and and or a re sim ple m athem atica l opera tors, which a l low you to m anipula te Boolean va lues in the sam e wayyou m ight m anipula te num bers using addit ion and subtrac tion.

For exam ple , he re ’s how we could use and to see if three num bers a re odd:> (and (oddp 5) (oddp 7) (oddp 9))

TBecause 5, 7, and 9 a re odd, the entire expression eva lua tes as true .Sim ila rly, we can use or to see whe ther a t least one of a se t of num bers is odd:> (or (oddp 4) (oddp 7) (oddp 8))

TBecause 7 is odd, the or com m and st i l l eva lua tes as true , despite the fac t tha t 4 and 8 a re even.But the re ’s som ething a bi t m ore inte rest ing about and and or tha t you m ight not notice just by looking a t these first two

exam ples. So fa r, these two com m ands look l ike com ple te ly ordinary m athem atica l opera tors; they do not look l ike condit iona lcom m ands, such as if or cond. However, they can be used for condit iona l behavior.

For instance , he re ’s how we could use these condit iona ls to se t a globa l va riable to true only when a num ber is even:> (defparameter *is-it-even* nil)

*IS-IT-EVEN*

> (or (oddp 4) (setf *is-it-even* t))T

> *is-it-even*T

If we do the sam e thing using an odd num ber, the variable rem ains unchanged:> (defparameter *is-it-even* nil)

*IS-IT-EVEN

> (or (oddp 5) (setf *is-it-even* t))T

> *is-it-even*NIL

T his exam ple i l lustra tes tha t L isp uses shortcut B oolean evaluation. T his m eans tha t once L isp de te rm ines tha t an ea rl ie rsta tem ent in a l ist of or va lues is t rue , i t sim ply re turns true and doesn’t bother eva lua ting the rem aining sta tem ents. Sim ila rly,once i t de te rm ines tha t an ea rl ie r sta tem ent in a l ist of and va lues is fa lse , i t stops without bothering to eva lua te the rest of thesta tem ents.

W hile this m ay seem like a m inor esote ric observa tion, i t can ac tua lly be very use ful in m any si tua tions. For instance , im agineif you want to save a fi le to disk, but only if the fi le was m odified, and only when the use r wants i t to be saved. T he basic struc turecould be writ ten as fol lows:(if *file-modified*

(if (ask-user-about-saving)(save-file)))

Here , the func tion ask-user-about-saving would ask the use r about the fi le , and then re turn true or fa lse based on the use r’swishes. However, since shortcut Boolean eva lua tion is guaranteed to be used for Boolean opera t ions under Com m on L isp and m ostother L isp dia lec ts, we could write this instead:(and *file-modified* (ask-user-about-saving) (save-file))Using this c leaner style for eva lua ting condit iona l code is possible only if you think beyond the typica l use of the Boolean

opera tors as sim ply m athem atica l opera tors. T his form has an e legant sym m etry be tween the three expressions, which som e L ispersm ay l ike . However, others would a rgue tha t a reader of your code m ay easi ly m iss the fac t tha t (save-file) does som ethingbeyond re turning a Boolean va lue . A bit of t im e is required to wrap your head a round this m ore-genera l conception of wha t and andor ac tua lly m ean.

A third way to write this code , which is a com prom ise be tween the previous approaches, is a s fol lows:(if (and *file-modified*

(ask-user-about-saving))(save-file)))

Many experienced L ispers wil l consider this ve rsion a bi t c lea re r than the previous two versions, because only expressions tha t a reexpressly designed to re turn a Boolean va lue a re trea ted as part of the condit ion.

Page 75: Land of Lisp - Barski M.D., Conrad

Using F unctions That Return M ore than Just the Truth

Now le t’s look a t another benefi t of L isp’s sim ple way of thinking about true and fa lse . As we’ve a lready discussed, any va lue inCom m on L isp (except for the diffe rent va ria t ions on nil) is t rue . T his m eans tha t func tions tha t a re com m only used in condit ionshave the option of re turning more than just the truth.

For instance , the L isp com m and member can be used to check for l ist m em bership for an i tem :> (if (member 1 '(3 4 1 5))

'one-is-in-the-list'one-is-not-in-the-list)'ONE-IS-IN-THE-LIST

T his seem s pre t ty stra ightforward. However, once aga in, the re is som ething happening behind the scenes tha t you m ay notexpec t . L e t’s run the member com m and in isola t ion:> (member 1 '(3 4 1 5))

(1 5)W hat the heck happened here? W hy is i t re turning (1 5)?Actua lly, the re ’s a pe rfec tly ra t iona l explana tion for this. W henever a L isper writes a func tion tha t re turns true and fa lse , she

will think to herse lf, “ Is the re anything e lse I could re turn other than just t?” Since a l l non-nil va lues in Com m on L isp eva lua te totrue , re turning som e other va lue is essentia l ly a freebie . T he im plem ente rs of the member func tion dec ided tha t som e c razy L ispersom ewhere m ay see the va lue in having the ta i l of the l ist for som e ca lcula t ion tha t uses this func tion.

Note

Rem em ber from Chapte r 3 tha t the l ist '(3 4 1 5) is the sam e as the nested contraption (cons 3 (cons 4 (cons 1 (cons 5nil)))). T his should m ake i t c lea r why the va lue (cons 1 (cons 5 nil)) is an easy thing for the member func tion to re turn.

But why doesn’t i t just re turn the va lue i t found, instead of the ta i l? In fac t , this would have been a use ful way to define themember func tion, because i t would a l low passing the origina l va lue to som e other func tion in such a m anner. Unfortuna te ly, oneedge case in part icula r would ruin this plan:> (if (member nil '(3 4 nil 5))

'nil-is-in-the-list'nil-is-not-in-the-list)'nil-is-in-the-list

As you can see in this exam ple , the member func tion st i l l gives the correc t answer, even when we search for nil a s the m em ber!If the member func tion had ac tua lly re turned nil (in other words, the origina l va lue we were sea rching for), i t would have eva lua tedas fa lse , and the exam ple would have incorrec tly sta ted tha t ni l isn’t in the l ist . However, since the member func tion re turns the ta i lof the l ist a t the point of the found i tem , i t can be guaranteed to a lways be a true va lue . A successful discovery of the desiredva lue wil l a lways re turn a l ist with a t least one va lue , which we know a lways eva lua tes as true .

One func tion tha t rea l ly benefi ts from rich re turn va lues is find-if, a s fol lows:> (find-if #'oddp '(2 4 5 6))

5

> (if (find-if #'oddp '(2 4 5 6))'there-is-an-odd-number'there-is-no-odd-number)'there-is-an-odd-number

T he find-if func tion ac tua lly takes another func tion, in this case oddp, a s a pa ram ete r. find-if wil l find the first va lue in thelist for which oddp re turns true . In this case , i t wil l find the first num ber (if any) tha t is an odd num ber.

You can see c lea rly how find-if can fi l l dua l roles: e i the r as a re triever of va lues m atching som e constra int or as a true /fa lseva lue inside a condit ion.

Note

Don’t worry ye t about the weird hash m ark (#) in front of oddp in the exam ple . W e’ll discuss the find-if func tion, and other so-ca lled higher-order func tions, in grea te r de ta i l in Chapte r 7 and Chapte r 14.

Alas, the e legant sym m etry of the find-if func tion has a single , sm all , ugly wart . If we try our edge case aga in, sea rching for anil va lue , we ge t a ra ther disappointing result :> (find-if #'null '(2 4 nil 6))

NILT he null func tion, which re turns true for any of the nil va lues, correc tly finds the nil. Unfortuna te ly, in this one annoying case ,

we would not want to use find-if inside a condit iona l sta tem ent, because a correc tly found va lue st i l l re turns a result tha teva lua tes as fa lse . T he sym m etry has been broken.

T hese a re the kinds of sm all things tha t m ake even grown L ispers shed a tea r.

Page 76: Land of Lisp - Barski M.D., Conrad

Comparing Stuff: eq, equal, and M ore

T here’s a lot of beautiful sym m etry in L isp. One part of L isp tha t isn’t so beautiful , though, involves the com m ands forcom paring things.

If you want to com pare two va lues in L isp to find out i f they a re “ the sam e,” you wil l find a bewildering assortm ent of diffe rentfunc tions tha t purport to accom plish this. Of these , equal, eql, eq, =, string-equal, and equalp a re the m ost com m only used. AL isper m ust understand the subtle t ies of these func tions int im ate ly in order to know how to com pare va lues correc tly.

Before we sta rt dissec ting this m adness, le t m e give you Conrad’s Rule of T hum b for Com paring Stuff. Follow this rule , andthough you m ay not be writ ing the world’s c leanest L isp code , you wil l probably be able to post som e sam ples to a newsgroupwithout m ore seasoned L ispers running you out of town with torches and pitchforks.

Sym bols should a lways be com pared to other sym bols with eq:> (defparameter *fruit* 'apple)

*FRUIT*

> (cond ((eq *fruit* 'apple) 'its-an-apple)((eq *fruit* 'orange) 'its-an-orange))

Page 77: Land of Lisp - Barski M.D., Conrad

ITS-AN-APPLET he eq func tion is the sim plest of a l l the L isp com parison func tions, and i t’s a lso very fast . It doesn’t rea l ly work for com paring

item s besides sym bols, but i f you consider the centra l role sym bols play in L isp, you’l l rea l ize how useful this func tion can be .E xperienced L ispers m ight look down on code if i t com pares two things, known to be sym bols, with som ething other than eq.

Note

eq can a lso be used to com pare conses (the l inks c rea ted by the cons com m and). However, i t re turns true va lues only when a consis com pared direc t ly to i tse lf, c rea ted by the sam e cons ca l l . T his m eans, two unre la ted conses tha t “ look” exac tly the sam e canfa il an eq test . Since eq can check a cons ce l l only aga inst i tse lf, using eq with conses isn’t rea l ly tha t use ful for a beginner.However, an advanced L isper m ay want to com pare conses with eq under ce rta in c ircum stances.

If you’re not dea ling with two sym bols, just use equal. T his com m and wil l te l l you when two things a re isomorphic, m eaningthey “ look the sam e.” It works for the whole suite of basic L isp da ta types, a s shown here :;;comparing symbols

> (equal 'apple 'apple)T

;;comparing lists> (equal (list 1 2 3) (list 1 2 3))T

;;Identical lists created in different ways still compare as the same> (equal '(1 2 3) (cons 1 (cons 2 (cons 3))))T

;;comparing integers> (equal 5 5)T

;;comparing floating point numbers> (equal 2.5 2.5)T

;;comparing strings> (equal "foo" "foo")T

;;comparing characters> (equal #\a #\a)T

As you can see , m ost i tem s in L isp can be e ffec t ive ly com pared with equal, inc luding strings and charac te rs (which a re discussedin the next chapte r).

Now tha t you know the bare m inim um about L isp com parisons to fake your way through your next cockta i l pa rty, le t’s look a t a l lthe other com parison com m ands.

T he eql com m and is sim ila r to the eq com m and, but unlike eq, i t a lso handles com parisons of num bers and charac te rs:

Page 78: Land of Lisp - Barski M.D., Conrad

;;comparing symbols> (eql 'foo 'foo)T

;;comparing numbers> (eql 3.4 3.4)T

;;comparing characters> (eql #\a #\a)T

T he equalp com m and is essentia l ly the sam e as the equal com m and, except tha t i t can handle som e difficult com parison caseswith a bi t of extra sophist ica t ion. For instance , i t can com pare strings with diffe rent capita l iza t ions and can com pare integersaga inst floa ting-point num bers:;;comparing strings with different CAPS

> (equalp "Bob Smith" "bob smith")T;;comparing integers against floating point numbers> (equalp 0 0.0)T

T he rem aining com parison com m ands a re just spec ia l iza t ions for spec ific da ta types. Otherwise , they a re sim ila r to equal. Forinstance , the = (equa l sign) func tion handles num bers, string-equal handles strings, and char-equal handles charac te rs.

I hope tha t you can now apprec ia te just how se riously L ispers take com parisons.

Page 79: Land of Lisp - Barski M.D., Conrad

What You've Learned

In this chapte r, we discussed how condit ions work in L isp. Along the way, you lea rned the fol lowing:T he va lues nil, 'nil, (), and '() a re a l l basica l ly the sam e thing in Com m on L isp.L isp m akes i t easy to check for em pty l ists. T his m akes i t sim ple to write l ist-ea te rs.L isp condit iona ls, such as the if com m and, cause L isp code to be eva lua ted only under the right condit ions.If you need a condit iona l com m and tha t does everything, then you want to use cond.Com paring stuff in L isp is com plica ted, but you can ge t by if you just use eq for com paring sym bols and equal for com paring

everything e lse .

Page 80: Land of Lisp - Barski M.D., Conrad

Chapter 5. Building a Text G ame Engine

W hen you write a program , no m atte r which program m ing language you’re using or wha t your program does, i t wil l probablyneed to work with text . Sure , one day we m ay a l l have E therne t ports a t the base of our skulls (100Mbps E therne t wil l have beenfully adopted by then, of course ). But unti l the day a rrives when you can just exchange thoughts with your MacBook using a direc thookup, you’l l be stuck using a lphabe tic text for input and output in your software .

Com pute rs have a lways had a bi t of a tenuous re la t ionship with text . Although we tend to think of text processing as a centra ltask for com pute r ha rdware and software (indeed, the 8-bit byte is the standard design e lem ent in m odern com pute rs, in la rge part ,due to how well sui ted i t is for encoding W este rn charac te r se ts), the truth of the m atte r is tha t the hum an concept of tex t is rea l lya lien to a com pute r.

In this chapte r, you’l l lea rn how to use L isp to m anipula te text . You’l l see , once aga in, tha t the L ispy approach to solvingproblem s a l lows you to c rea te code tha t is ful l of e legance and sym m etry. T o dem onstra te this approach, we wil l do som ething tha twould seem to m ake thinking with text unavoidable : build the engine for a sim ple text adventure gam e. However, we’l l do this ina way tha t avoids constra ining our code by a rt ific ia l ly forc ing the hum an notion of text into i ts design. T his wil l a l low us to writecode tha t focuses on the strengths of a com pute r.

As you read this chapte r, rem em ber tha t handling text is not a com pute r’s strength. It is a necessa ry evil best kept to am inim um .

Page 81: Land of Lisp - Barski M.D., Conrad

The Wizard's Adventure G ame

In this gam e, you a re a wizard’s apprentice . You’l l explore the wizard’s house . W hen we com ple te the gam e (in Chapte r 17),you’l l be able to solve puzz les and win a m agica l donut.

Page 82: Land of Lisp - Barski M.D., Conrad

O ur G ame World

Here is a pic ture of our gam e world:

As you can see , we can visi t three diffe rent loca tions: a l iving room , an a t t ic , and a garden. Players can m ove be tween placesusing the door and the ladder to the a t t ic .

T hink of this gam e world as a sim ple direc ted graph with three nodes (represented as e l l ipses) and four edges (represented asa rrows):

Page 83: Land of Lisp - Barski M.D., Conrad

Players m ove be tween nodes by trave ling a long the edges in e i the r direc t ion. W herever the players a re , they can inte rac t withvarious objec ts a round them .

Page 84: Land of Lisp - Barski M.D., Conrad

Basic Requirements

Our gam e code wil l need to handle a few basic things:L ooking a roundW alking to diffe rent loca tionsPicking up objec tsPerform ing ac tions on the objec ts picked up

In this chapte r, we’l l address the first three of these requirem ents. T o perform m ore com plex ac tions on objec ts, we’l l use them ore advanced L isp techniques covered in la te r chapte rs. Because of this, our gam e engine wil l be som ewhat l im ited in i tsabil i t ie s unti l we finish i t in Chapte r 17.

W hen looking a round in our gam e world, you wil l be able to “see” three kinds of things from any loca tion:

Basic sceneryOne or m ore pa ths to other loca tionsObjec ts tha t you can pick up and m anipula te

L e t’s add fea tures for these one a t a t im e .

Page 85: Land of Lisp - Barski M.D., Conrad

Descr ibing the Scenery with an Assoc iation List

T he world inside our adventure gam e is ve ry sim ple , conta ining only three loca tions. L e t’s first c rea te a top-leve l va riable ,*nodes*, to conta in descript ions of the loca tions tha t exist in our gam e:(defparameter *nodes* '((living-room (you are in the living-room.

a wizard is snoring loudly on the couch.))(garden (you are in a beautiful garden.there is a well in front of you.))(attic (you are in the attic.there is a giant welding torch in the corner.))))

T his variable conta ins a l ist and descript ion of our three loca tions. In essence , the *nodes* va riable basica l ly gives us a way tofind a piece of da ta assoc ia ted with a lookup key. In this case , the key is the nam e of the place (living-room, garden, or attic),and the da ta is a text desc ript ion of the scenery a t tha t place . T his type of struc ture is ca l led an assoc iation l ist , or alist for short(a l ists a re covered in grea te r de ta i l in Chapte r 7).

One thing is ra ther unusua l about the defini t ion of this *nodes* va riable : E ven though i t conta ins descript ions of the variousloca tions in our gam e world, i t does not ac tua lly conta in any text strings. Since Com m on L isp has a string da ta type , we could havewrit ten descript ions using quotes. For instance , we could have writ ten "You are in a beautiful garden. There is a well infront of you." Instead, we use m ore fundam enta l da ta types—sym bols and l ists—to encode this inform ation.

W hy wouldn’t we just use strings? As I m entioned a t the beginning of this chapte r, the m anipula t ion of text is not rea l ly afundam enta l com puting concept . In this gam e, we’l l m anipula te the m essages displayed to players based on the ir inte rac tion withthe gam e world in com plica ted ways. For m ost rea l-world program s, the inform ation you’l l genera te as output (such as HT ML ,PDFs, or even richer graphica l form ats) wil l probably be fa r m ore com plica ted than just sim ple text .

By keeping your source da ta struc tures free from assum ptions regarding the output form at from the sta rt , your coding can takefull advantage of your program m ing language . Since the easiest things to m anipula te in L isp a re sym bols and l ists, m ostexperienced L ispers wil l t ry to focus on these da ta types in the design of the ir software whenever possible . So, we wil l stay awayfrom strings in our design. (In the next chapte r, we wil l t ransla te these l ists and sym bols into properly form atted text . )

Note

Com m on L isp doesn’t force you to represent strings with l ists and sym bols in this way. If i t ’s m ore convenient , you can work withstrings direc t ly. (You’l l see exam ples of working with strings la te r in the book, espec ia l ly in Chapte r 11. ) Using l ists and sym bols asan inte rm edia ry for m anipula t ing text is de fini te ly an old-school L isp technique . However, i t can often lead to very e legant code ,since l ist opera t ions a re so fundam enta l to L isp.

Page 86: Land of Lisp - Barski M.D., Conrad

Descr ibing the Location

Now tha t we’ve c rea ted an a l ist of our gam e world, we need to c rea te a com m and to describe a loca tion. T o accom plish this,we’l l use the assoc func tion to find the correc t i tem in the l ist using a key:> (assoc 'garden *nodes*)

(GARDEN (YOU ARE IN A BEAUTIFUL GARDEN. THERE IS A WELL IN FRONT OF YOU.))Using assoc, we can easi ly c rea te the describe-location func tion:(defun describe-location (location nodes)

(cadr (assoc location nodes)))T o use this func tion, we pass in a loca tion and the *nodes* l ist :> (describe-location 'living-room *nodes*)

(YOU ARE IN THE LIVING-ROOM. A WIZARD IS SNORING LOUDLY ON THE COUCH.)W hy don’t we just re fe rence the *nodes* va riable direc t ly from the describe-location func tion? Because this func tion is

writ ten in the func tional programming style . In this style , a func tion wil l re fe rence only param ete rs or va riables dec la red in thefunc tion i tse lf, and i t wil l do nothing besides re turn a va lue , which is the descript ion of the loca tion in this case .

By writ ing func tions tha t don’t re fe rence variables in the “outside world” direc t ly and tha t don’t pe rform any ac tions other thanre turning a va lue , you can write code tha t can easi ly be tested in isola t ion. You should try to write your L isp func tions in this stylewhenever possible . (W e wil l discuss the func tiona l program m ing style in grea te r de ta i l in Chapte r 14. )

Page 87: Land of Lisp - Barski M.D., Conrad

Descr ibing the P aths

Now tha t we have basic descript ions of each loca tion, we need descript ions of pa ths to other loca tions as well . W e’ll c rea te asecond variable , *edges*, tha t conta ins the pa ths tha t players can take to m ove be tween places on our m ap. (W e use the te rmedges because tha t’s the proper m ath te rm for the l ines connec ting nodes in a graph. )(defparameter *edges* '((living-room (garden west door)

(attic upstairs ladder))(garden (living-room east door))(attic (living-room downstairs ladder))))

Using this struc ture , we c rea te the describe-path func tion, which builds a textua l desc ript ion of a given edge using our sym bolssystem .(defun describe-path (edge)

`(there is a ,(caddr edge) going ,(cadr edge) from here.))T his describe-path func tion looks pre t ty strange—alm ost l ike a piece of da ta m ore than a func tion. L e t’s t ry i t , and then figure

out how i t works.> (describe-path '(garden west door))

(THERE IS A DOOR GOING WEST FROM HERE.)T his func tion basica l ly re turns a piece of da ta with sm all bi ts of ca lcula ted inform ation inse rted into i t . T his fea ture of L isp,

ca l led quasiquoting, a l lows us to c rea te chunks of da ta tha t have sm all pieces of L isp code em bedded in them .

Page 88: Land of Lisp - Barski M.D., Conrad

H ow Q uasiquoting Works

T o enable quasiquoting, you m ust use a backquote [`] not a single quote ['] when switching from code to da ta m ode . T hedescribe-path func tion has just such a backquote in i t .

Both the single quote and backquote in L isp “ fl ip” a piece of code into da ta m ode , but only a backquote can a lso be unquotedusing the com m a charac te r, to fl ip back into code m ode .

W ith a l i t t le im agina tion, this should m ake sense to you. Afte r a l l , a com m a does look just l ike an upside-down backquote ,doesn’t i t? Here ’s how the fl ip-flop in the describe-path func tion works (the parts in code m ode a re shaded):

L isp a t tem pts to m ake l ist m anipula t ion as easy as possible . Here , you can see how our program , which uses l ists of sym bols tostore our text , can now leverage the quasiquoting fea ture to construc t sentences in a very conc ise and c lea r way.

Page 89: Land of Lisp - Barski M.D., Conrad

Descr ibing M ultiple P aths at O nce

Now le t’s use our describe-path func tion to c rea te a m ore advanced func tion. Since a loca tion m ay have any num ber of pa thsexit ing from it , we need a func tion tha t can genera te descript ions for a l l edges from a given loca tion by looking up the loca tionfrom our da ta struc ture of edges:(defun describe-paths (location edges)

(apply #'append (mapcar #'describe-path (cdr (assoc location edges)))))T his func tion uses a bunch of com m ands tha t m ay seem very exotic to a person not accustom ed to the world of L isp. Many

program m ing languages would use som e kind of for-next loop to run through the edges, and then c ram the descript ions of each pa thtoge ther using a tem porary variable . L isp uses a m uch m ore e legant approach. L e t’s see i t in ac t ion:> (describe-paths 'living-room *edges*)

(THERE IS A DOOR GOING WEST FROM HERE. THERE IS A LADDER GOING UPSTAIRS FROM HERE.)T he describe-paths func tion takes the fol lowing steps:

1. Find the re levant edges.2. Convert the edges to descript ions.3. Join the descript ions.

L e t’s see how i t pe rform s each of these steps.

F inding the Relevant Edges

T he first , inner part of the describe-paths func tion is pre t ty stra ightforward. T o find the re levant pa ths and edges leading fromthe l iving room , we use assoc aga in to look up the loca tion in our l ist of edges:> (cdr (assoc 'living-room *edges*))

((GARDEN WEST DOOR) (ATTIC UPSTAIRS LADDER))

Converting the Edges to Descr iptions

Next, the edges a re converted to descript ions. Here is just the code to accom plish this, shown in isola t ion:> (mapcar #'describe-path '((GARDEN WEST DOOR) (ATTIC UPSTAIRS LADDER)))

((THERE IS A DOOR GOING WEST FROM HERE.)(THERE IS A LADDER GOING UPSTAIRS FROM HERE.))

T he mapcar func tion is used frequently by L ispers. T his func tion takes another func tion and a l ist , and then applies this func tionto every m em ber of a l ist . Here ’s an exam ple :> (mapcar #'sqrt '(1 2 3 4 5))

(1 1.4142135 1.7320508 2 2.236068)T his exam ple passes the sqrt (square root) func tion, a long with the (1 2 3 4 5) l ist , into mapcar. As a result , the func tion

genera tes a l ist of the square roots of the origina l num bers by applying sqrt to every m em ber of the l ist and c rea ting a new l ist .Func tions tha t take other func tions as param ete rs, such as mapcar, a re very use ful and a dist inguishing fea ture of L isp. Such

func tions a re ca l led higher-order func tions.Here is another exam ple :> (mapcar #'car '((foo bar) (baz qux)))

(foo baz)T his t im e , our source l ist conta ins two sm alle r l ists. T he car func tion, which grabs the first i tem in a l ist , causes mapcar to

re turn the first i tem s from each sm alle r l ist , foo and baz.You m ay be wondering why the func tion nam es we pass into mapcar have the #' sym bols in front of them . T his sym bol sequence

is a shorthand for the function opera tor. T he L isp reader (the part of your L isp environm ent tha t reads the code you type) wil lconvert the previous exam ple into the fol lowing longer version:> (mapcar (function car) '((foo bar) (baz qux)))

(foo baz)Com m on L isp requires you to use the function opera tor when re fe rring to a func tion as a va lue direc t ly l ike this, because the

nam e of a func tion m ay confl ic t with other nam ed i tem s in a program , causing unpredic table e rrors. For instance , im agine if weadded m ore stuff to the previous exam ple , l ike this:

> (let ((car "Honda Civic"))

(mapcar #'car '((foo bar) (baz qux))))(foo baz)

In this ve rsion, the car sym bol could have two diffe rent m eanings. T he first m eaning of ca r is tha t i t is a standard func tion buil t

into L isp (introduced in Chapte r 3). However, we’re a lso c rea ting a loca l va riable nam ed car . Because we prepended the

word car with #' in our ca l l to mapcar , the re is no confusion about which car we a re ta lking about.Now le t’s look a t the describe-paths func tion aga in:(defun describe-paths (location edges)

(apply #'append (mapcar #'describe-path (cdr (assoc location edges)))))Notice how the append and describe-path func tions a re passed in as va lues to the apply and mapcar func tions, which a re

designed to rece ive and use the func tions.Com m on L isp tracks func tion nam es diffe rently from variable nam es. It has m ult iple namespaces, inc luding one for va riables and

one for func tions. (W e’ll lea rn m ore about nam espaces la te r, e spec ia l ly in Chapte r 16. ) Schem e, the other popula r L isp dia lec t ,doesn’t force you to m ark func tions with a func tion opera tor when using them as va lues.

In other words, Schem e has only one nam espace for both func tions and variables. For instance , in Schem e, you can just wri te(map sqrt '(1 2 3 4 5)) to genera te the square roots of the num bers 1 through 5 without genera t ing an e rror (map is the Schem eversion of mapcar). As a result of this design, in Schem e, a va riable and a separa te func tion can’t be ava ilable in the sam e block ofcode . T ha t design dec ision is one of the grea t benefi ts (or curses) of Schem e, depending on your point of view. Because of this

Page 90: Land of Lisp - Barski M.D., Conrad

diffe rence in the num ber of nam espaces, Schem e is som etim es ca l led a Lisp-1, whereas Com m on L isp is som etim es re fe rred to as aLisp-2.

Joining the Descr iptions

Once we’ve used mapcar to genera te a l ist of desc ript ions for a l l the pa ths and edges, we need to com bine them into a singledescript ion. W e accom plish this with the append func tion, which joins severa l l ists into one big l ist :> (append '(mary had) '(a) '(little lamb))

(MARY HAD A LITTLE LAMB)W e use the append func tion to c ram the l ist of pa th descript ions into one l ist tha t desc ribes the whole enchilada , in one swoop.

T he problem is tha t append needs a l l of the l ists handed to i t a s separa te param ete rs. In describe-paths, we have our l ists in onebig l ist , not as separa te objec ts we can pass as param ete rs. Heck, we don’t even know how m any pa ths the re m ay be from any givenspot.

T he apply func tion solves this problem . You pass i t a func tion and a l ist of objec ts, and i t pre tends tha t the i tem s in the l ist a resepara te objec ts and passes them to the given func tion as such. For exam ple , i f we have the nested l ist '((mary had) (a) (littlelamb)), the apply func tion wil l add in tha t l i t t le bi t of duc t tape needed to m ake the append func tion work with a single big l ist :> (apply #'append '((mary had) (a) (little lamb)))

(MARY HAD A LITTLE LAMB)

Warning

Since the apply func tion passes each i tem in a l ist a s an a rgum ent to the target func tion, you can run into problem s whenca ll ing i t on very la rge l ists tha t have thousands of i tem s or m ore . You can check the va lue of the call-arguments-limit va riablein the RE PL to see the m axim um num ber of a l lowed a rgum ents to a func tion. (More recent dia lec ts of L isp a re typica lly designedto a l low a rgum ent l ists of any size , without an a rt ific ia l l im it . )

You can see how apply enables the describe-paths func tion to build one long l ist desc ribing a l l pa ths leading from a singleloca tion. L e t’s use this sam e approach on the pa th descript ion l ists we construc ted:> (apply #'append '((THERE IS A DOOR GOING WEST FROM HERE.)

(THERE IS A LADDER GOING UPSTAIRS FROM HERE.)))(THERE IS A DOOR GOING WEST FROM HERE. THERE IS A LADDER GOING UPSTAIRS FROM HERE.)

Now tha t we’ve looked a t each part of the describe-paths func tion, le t’s review how it works:(defun describe-paths (location edges)

(apply #'append (mapcar #'describe-path (cdr (assoc location edges)))))T he func tion takes two param ete rs: the current player’s loca tion, as well a s an a l ist of edges/pa ths for the gam e m ap. First , i t

uses assoc to look up the correc t loca tion from the edge a l ist . Since assoc re turns both the key and the va lue from the a l ist , weca ll cdr to re trieve only the va lue . Next, we use mapcar to m ap the describe-path func tion aga inst each edge tha t we found.Fina lly, we conca tena te the l ists for desc ribing a l l the pa ths into one long l ist by applying append aga inst the l ist .

T he program m ing style used by describe-path is ve ry typica l for L isp code . It involves passing a long a com plica ted chunk ofda ta and m anipula t ing i t in severa l steps, often using higher-order func tions. T o becom e a profic ient L isp program m er, you shouldtry to ge t com fortable reading code writ ten in this way.

Page 91: Land of Lisp - Barski M.D., Conrad

Descr ibing O bjects at a Spec ific Location

T o c rea te the fina l piece of code to he lp us visua lize our gam e world, we need to describe the objec ts on the floor a t a givenloca tion, which a player can pick up and use .

Page 92: Land of Lisp - Barski M.D., Conrad

Listing Visible O bjects

T o do so, we first c rea te a l ist of the objec ts:> (defparameter *objects* '(whiskey bucket frog chain))

*OBJECTS*

W e can a lso c rea te a second variable , *object-locations*, to track the loca tion of each objec t in the form of an a l ist :(defparameter *object-locations* '((whiskey living-room)

(bucket living-room)(chain garden)(frog garden)))

Next, we write a func tion tha t l ists the objec ts visible from a given loca tion:(defun objects-at (loc objs obj-locs)

(labels ((at-loc-p (obj)

(eq (cadr (assoc obj obj-locs)) loc)))

(remove-if-not #'at-loc-p objs)))

T hi s objects-at func tion dec la res a new func tion nam ed at-loc-p using the labels com m and . (Rem em ber tha t thelabels func tion a l lows you to define func tions loca lly. ) Since the at-loc-p func tion won’t be used e lsewhere , we can just dec la rei t direc t ly within objects-at, hiding i t from the rest of the code in our program .

T he at-loc-p func tion takes the sym bol for an objec t and re turns t or nil, depending on whether tha t objec t exists a t theloca tion loc. It does this by looking up the objec t in the obj-locs a l ist . T hen, i t uses eq to see whe ther the loca tion i t finds

m atches the loca tion in quest ion .W hy did we nam e this func tion at-loc-p? W hen a func tion re turns nil or a t ruth va lue , i t ’s a Com m on L isp convention to

append a p to the end of tha t func tion’s nam e. For instance , you can check tha t the num ber 5 is odd by ca ll ing (oddp 5). Suchtrue /fa lse func tions a re ca l led predicates, which is why we use the le t te r p.

T he remove-if-not func tion in the last l ine of the l ist ing , a s you m ight expec t , rem oves a l l things from a l ist for which apassed-in func tion (in this case , at-loc-p) doesn’t re turn true . E ssentia l ly, i t re turns a fi l te red l ist of objec ts consist ing of thoseitem s for which at-loc-p is t rue .

Here’s wha t object-at looks l ike in ac t ion:> (objects-at 'living-room *objects* *object-locations*)

(WHISKEY BUCKET)

Page 93: Land of Lisp - Barski M.D., Conrad

Descr ibing Visible O bjects

Now we can write a func tion to describe the objec ts visible a t a given loca tion:(defun describe-objects (loc objs obj-loc)

(labels ((describe-obj (obj)

`(you see a ,obj on the floor.)))

(apply #'append (mapcar #'describe-obj (objects-at loc objs obj-loc)))))

In this l ist ing, describe-objects fi rst c rea tes the describe-obj func tion . T his func tion genera tes a pre t ty sentence sta t ing

tha t a given objec t is on the floor, using quasiquoting . T he m ain part of the func tion consists of ca l l ing objects-at to findthe objec ts a t the current loca tion, m apping describe-obj ac ross this l ist of objec ts, and fina lly appending the descript ions into a

single l ist .L e t’s t ry running describe-objects:> (describe-objects 'living-room *objects* *object-locations*)

(YOU SEE A WHISKEY ON THE FLOOR. YOU SEE A BUCKET ON THE FLOOR)Perfec t!

Page 94: Land of Lisp - Barski M.D., Conrad

Descr ibing It All

Now we’ll t ie a l l of these descript ion func tions into one easy com m and ca lled look. Because this wil l be the ac tua l com m andplayers can ente r to look a round them in the gam e, look wil l need to know a player’s current loca tion. So, we need a variable totrack the player’s current posi t ion. L e t’s ca l l i t *location*:(defparameter *location* 'living-room)Because the *location* va lue is ini t ia l ized to the living-room sym bol, which occurs a t the very sta rt of the gam e, players wil l

find them se lves in the l iving room of the wizard’s house . At this point , we can write a look func tion to describe everything weneed by having i t ca l l a l l of our desc riptor func tions:(defun look ()

(append (describe-location *location* *nodes*)(describe-paths *location* *edges*)(describe-objects *location* *objects* *object-locations*)))

Since the look func tion uses globa l va riable nam es (such as *location*, *nodes*, and so on), the player won’t need to pass inany funky va lues in order to look out a t the world. However, this a lso m eans tha t the look func tion is not in the func tiona lprogram m ing style , because func tions in the func tiona l program m ing style re fe rence only param ete rs or va riables dec la red in thefunc tion i tse lf. *location* and i ts i lk a re globa l va riables, so the look func tion doesn’t hold up m uste r.

Since the player’s loca tion changes as the gam e progresses, look wil l do dif ferent things at di f fe rent t imes in the gam e. In otherwords, the things you see when looking a round wil l change depending on your loca tion. In contrast , a func tion in the func tiona lprogram m ing style a lways re turns the sam e result , a s long as the sam e va lues a re given as param ete rs. T he ea rl ie r func tions wecrea ted, such as describe-location, describe-paths, and describe-objects, a lways re turn the sam e thing, no m atte r when theyare ca l led, as long as the ir parameters are kept the same .

Now here ’s wha t we see when we use look:> (look)

(YOU ARE IN THE LIVING-ROOM OF A WIZARD’S HOUSE.THERE IS A WIZARD SNORING LOUDLY ON THE COUCH.THERE IS A DOOR GOING WEST FROM HERE.THERE IS A LADDER GOING UPSTAIRS FROM HERE.YOU SEE A WHISKEY ON THE FLOOR.YOU SEE A BUCKET ON THE FLOOR)

Page 95: Land of Lisp - Barski M.D., Conrad
Page 96: Land of Lisp - Barski M.D., Conrad

Walking Around in O ur World

Now tha t we can see things in our world, le t’s write som e code so tha t we can walk a round. T he walk func tion (not in thefunc tiona l style ) takes a direc t ion and le ts us walk the re :(defun walk (direction)

(let ((next (find direction

(cdr (assoc *location* *edges*))

:key #'cadr)))

(if next

(progn (setf *location* (car next))

(look))

'(you cannot go that way.))))

First , this func tion looks up the ava ilable walking pa ths in the *edges* table , using the current loca tion . T his is used by the

find func tion to loca te the pa th m arked with the appropria te direc t ion . (find sea rches a l ist for an i tem , then re turns tha tfound i tem . ) T he direction (such as west, upstairs, and so on) wil l be in the cadr of each pa th, so we need to te l l find to m atchthe direction aga inst the cadr of a l l the pa ths in the l ist .

W e can do this by passing find a keyword parameter . In Com m on L isp, m any func tions (such as find) have buil t-infea tures tha t can be accessed by passing in spec ia l pa ram ete rs a t the end of the func tion ca ll . For instance , the fol lowing code findsthe first i tem in a l ist tha t has the sym bol y in the cadr loca tion:> (find 'y '((5 x) (3 y) (7 z)) :key #'cadr)

(3 Y)A keyword param ete r has two parts:

T he first is the nam e (in this case :key), which begins with a colon. (W e’ll discuss the m eaning of this colon inm ore de ta i l in Chapte r 7. )

T he second is the va lue , which in this case is #'cadr.

W e use keyword param ete rs the sam e way in our walk func tion to find the proper pa th based on the given direc t ion.

Once we have the correc t pa th, we store the result in the variable next . T he if expression then checks whe ther next has a

va lue (the next va riable isn’t nil). If next has a va lue , if adjusts the player’s posit ion because this is a va lid direc t ion

. T he ca ll to look re trieves the descript ion for the new loca tion and re turns i t a s a va lue . If the player chooses an

inva lid direc t ion, look wil l genera te an adm onishm ent instead of a new descript ion .Here ’s wha t our walk func tion looks l ike now:> (walk 'west)

(YOU ARE IN A BEAUTIFUL GARDEN.THERE IS A WELL IN FRONT OF YOU.THERE IS A DOOR GOING EAST FROM HERE.YOU SEE A CHAIN ON THE FLOOR.YOU SEE A FROG ON THE FLOOR.)

T here’s a quote in front of the direc t ion, since the direc t ion nam e needs to be writ ten in da ta m ode . It’s kind of awkward toforce a player to put a quote in a gam e com m and, but the inte rface we a re c rea ting now is intended for easy debugging anddeve lopm ent. Heck, i t ’s a lm ost not even worth ca l l ing an “ inte rface , ” since we just ente r the gam e com m ands direc t ly into theRE PL . In the next chapte r, we’l l c rea te a m uch nice r inte rface using a custom RE PL designed for playing text gam es tha t wil ltake ca re of this wart .

Note

You could use L isp macros to c rea te a com m and in a vanil la L isp RE PL tha t doesn’t require the quote in front of the direc t ion,so tha t you could just wri te (walk west), for instance . You’l l lea rn m ore about m acros in Chapte r 16.

Page 97: Land of Lisp - Barski M.D., Conrad

P icking Up O bjects

Next, le t’s c rea te a com m and to pick up objec ts in our world. T o do so, we m odify the variable *object-locations* tha t we’reusing to track the loca tion of objec ts:(defun pickup (object)

(cond ((member object

(objects-at *location* *objects* *object-locations*))

(push (list object 'body) *object-locations*)`(you are now carrying the ,object))(t '(you cannot get that.))))

T he pickup func tion uses the member func tion to see if the object is indeed on the floor of the current loca tion. (T he

member func tion checks to see if a pa rt icula r i tem is found in a l ist of i tem s. ) W e use the objects-at com m and to genera tethe l ists of objec ts a t the current loca tion.

If the objec t is a t the current loca tion, we use the push com m and to push a new i tem onto the *object-locations* l ist ,consist ing of the i tem and i ts new loca tion. T he new loca tion wil l just be body, for the player’s body.

T he push com m and sim ply adds a new i tem to the front of a l ist va riable ’s l ist . For exam ple , the fol lowing exam ple addsthe num ber 7 to the l ist 1 2 3:> (defparameter *foo* '(1 2 3))

*FOO*> (push 7 *foo*)(7 1 2 3)> *foo*(7 1 2 3)

T hi s push com m and is basica l ly a convenience func tion buil t on top of setf. For exam ple , we could have replaced thepreceding push com m and with (setf *foo* (cons 7 *foo*)) and obta ined the sam e result . It ’s just easie r to use push.

Pushing a new loca tion for an objec t onto our *object-locations* a l ist does seem a bi t odd. Since we’re never rem oving oldloca tions for objec ts, just pushing new ones, i t m eans tha t *object-locations* m ay conta in m ult iple entries for a single objec t ,and tha t this l ist now has two stored loca tions for the objec t in quest ion. Fortuna te ly, the assoc com m and, which we use to findobjec ts in a given loca tion (within the objects-at com m and), a lways re turns the first i tem i t finds in a l ist . T here fore , using thepush com m and m akes the assoc com m and behave as if the va lue in the l ist for a given key has been replaced a l toge ther.

Using the push and assoc com m ands toge ther in this way a l lows us to pre tend tha t va lues in an a l ist a re changing, while st i l lprese rving old va lues. Old va lues a re sim ply suppressed by newer va lues, thus prese rving a history of a l l old va lues. T he push/associdiom is a com m on technique used by L ispers.

Now le t’s wa lk back to the l iving room and try to pick up an objec t:> (walk 'east)

(YOU ARE IN THE LIVING-ROOM OF A WIZARDS HOUSE. THERE IS A WIZARD SNORINGLOUDLY ON THE COUCH. THERE IS A DOOR GOING WEST FROM HERE. THERE IS A LADDERGOING UPSTAIRS FROM HERE. YOU SEE A WHISKEY ON THE FLOOR. YOU SEE A BUCKET ONTHE FLOOR.)> (pickup 'whiskey)(YOU ARE NOW CARRYING THE WHISKEY)

It worked. W e’re ca rrying the whiskey, which m eans tha t we can now pick up things in our world!

Page 98: Land of Lisp - Barski M.D., Conrad

Checking O ur Inventory

Fina lly, le t’s c rea te a func tion tha t le ts players see an inventory of objec ts they a re ca rrying:(defun inventory ()

(cons 'items- (objects-at 'body *objects* *object-locations*)))T his inventory func tion uses the objects-at func tion to re trieve a l ist of objec ts a t a requested loca tion. W hat loca tion does i t

sea rch for? If you rem em ber, when an objec t was picked up by the player, we changed i ts loca tion to 'body: T his is the loca tionwe now use to query.

L e t’s t ry out this inventory func tion:> (inventory)

(ITEMS-WHISKEY)As you can see , we a re ca rrying only one i tem right now: the whiskey bott le we just picked up.T here you have i t! W e now have a basic engine for a text adventure gam e. W e can look a round the world with look; wa lk

be tween places with walk; pick up objec ts with pickup; and check our inventory with inventory.Of course , we don’t rea l ly have m uch of a gam e, since we can’t do anything with the objec ts we find. W e’ll add a m echanism

for ac tua lly m anipula t ing objec ts in Chapte r 17. In the next chapte r, we’l l focus on im proving our gam e’s use r inte rface . E venthough the RE PL is pe rfec t for prototyping our gam e, adding a custom text gam e inte rface wil l m ake the gam e play m ore seam lessfor the player.

Page 99: Land of Lisp - Barski M.D., Conrad

What You've Learned

In this chapte r, we put toge ther a sim ple engine for a text adventure gam e. Along the way, you lea rned the fol lowing:A gam e world can be represented by a m athem atica l graph, consist ing of nodes for the places the player can visi t and edges for

the pa ths be tween these places.You can store these nodes in an assoc iation l ist (alist) ca l led *nodes*. T his alist a l lows you to look up propert ies of a

node /place by using i ts nam e. In the case of our gam e, the property we’re storing is a desc ript ion of each node /place .You use the assoc func tion to look up a key (loca tion nam e in our exam ple) in an a l ist .Quasiquoting is a technique tha t a l lows you to inse rt sm all bi ts of com pute r code into la rger pieces of da ta .Som e L isp func tions accept other func tions as a rgum ents. T hese a re ca l led higher-order func tions. T he mapcar func tion is the

m ost popula r higher-order func tion in Com m on L isp.T o replace a va lue from an a l ist , you push new i tem s onto the l ist . Only the m ost recent va lue wil l be reported by the assoc

func tion.

Page 100: Land of Lisp - Barski M.D., Conrad

Chapter 6. Interacting with the World: Reading and P r inting in Lisp

So fa r, we haven’t wri t ten any code tha t direc t ly inte rac ts with the outside world. Instead, a l l the results genera ted by com m andsare just re turned as va lues, which we can see by ca ll ing func tions from our L isp RE PL .

However, code can’t just spend i ts whole l i fe si t t ing in a black box. At som e point , i t ’s going to need to inte rac t with the world,so i t wil l need a use r inte rface . L uckily, L isp has m uch to offe r to he lp you c rea te use r inte rfaces. T here a re m any graphica l use rinte rface l ibra ries for diffe rent flavors of Com m on L isp, as well a s l ibra ries for building web inte rfaces. In fac t , we’l l be buildingour own toy web inte rface in Chapte r 13.

In this chapte r, we’l l focus on the m ost basic of a l l use r inte rfaces, the command-line interface .

Page 101: Land of Lisp - Barski M.D., Conrad

P rinting and Reading Text

For a com m and-line inte rface , we need com m ands tha t can direc t ly print text from the sc reen and read in text ente red by theuser. T he two com m ands tha t do this a re , appropria te ly enough, print and read. As you m ight expec t by now, the re is a lot ofsym m etry be tween these two com m ands.

Page 102: Land of Lisp - Barski M.D., Conrad

P rinting to the Screen

T he print func tion sim ply le ts you print stuff to the console :> (print "foo")

"foo"

"foo"

Don’t ge t confused by the fac t tha t ca l l ing the print func tion caused "foo" to be printed twice . T he first "foo" is wha t

the print func tion ac tually printed. T he second "foo" is the re because , as you know, the RE PL a lways prints the va lue ofany expression tha t is ente red. It so happens tha t the va lue of (print "foo") is "foo", causing the word to be shown twice . In theexam ples tha t fol low in this chapte r, I’l l typica lly om it this extra fina l va lue printed by the RE PL , just to avoid confusion.

T he print func tion is an easy way to print a L isp va lue to the sc reen. However, advanced L ispers often favor a re la ted func tionca lled prin1. T o understand the diffe rence , le t’s t ry both of these func tions out in the RE PL :> (progn (print "this")

(print "is")(print "a")(print "test"))"this""is""a""test"

T he print func tion causes each i tem to be printed on a separa te l ine . Now, le t’s t ry prin1:> (progn (prin1 "this")

(prin1 "is")(prin1 "a")(prin1 "test"))"this""is""a""test"

As you can see , prin1 does not put the printed i tem s on separa te l ines. T o be prec ise , the print and prin1 com m ands a re thesam e in every way, except tha t print wil l sta rt a new l ine before print ing a va lue . Addit iona lly, print places a space charac te r a tthe end of the printed va lue .

Because prin1 does less, i t is rea l ly a sim ple r, m ore fundam enta l func tion. It is m ore flexible and, the re fore , is com m only usedin m ore se rious L isp code . W e’ll use the print func tion m ore frequently in this book, but you should be aware of the prin1com m and, as well .

Page 103: Land of Lisp - Barski M.D., Conrad

Saying H ello to the User

T he following exam ple is a sim ple func tion, say-hello, tha t you can ca ll from your L isp prom pt. It a sks use rs for the ir nam eand responds with a gree ting. When you run the program, be sure to type quotes around your name, even i f this may seem odd.> (defun say-hello ()

(print "Please type your name:")

(let ((name (read)))

(print "Nice to meet you, ")

(print name)))SAY-HELLO.> (say-hello)"Please type your name:" "bob""Nice to meet you,""bob"

In the first l ine of the say-hello func tion, we print a m essage asking users for the ir nam e . Next, we define a loca l

variable ca l led name, which is se t to the va lue re turned by the read func tion . T he read func tion wil l cause L isp to wait forthe use r to type in som ething a t the RE PL . Only a fte r the use r has typed som ething in a t the prom pt and pressed ente r wil l the

variable name be se t to the result . Once we know the use r’s nam e, a pe rsona lized m essage is printed, gree ting the use r .As you can see from this sim ple func tion, the print and read func tions do (a lm ost) exac tly wha t you would expec t . T he print

func tion prints som ething on the sc reen. T he read func tion le ts the use r ente r som ething into the program . However, the re is onegla ring idiosyncrasy in these func tions: E very va lue displayed and ente red is surrounded by quota t ion m arks.

Page 104: Land of Lisp - Barski M.D., Conrad

Starting with pr int and read

W hen you need to print som ething on the sc reen, you should first think of the print com m and. If you need to read som ething in,you should first think of the read com m and. Other print ing com m ands le t you c rea te the previous exam ple without havingsuperfluous quotes, but whenever you have an input or output ta sk in L isp, you should ask yourse lf, “Can print or read do the job?”You wil l save yourse lf a lot of t rouble if you a lways use these two func tions as your sta rt ing point .

Warning

T he read com m and can be dangerous if used in the wrong way. See T he Dangers of read and eva l in T he Dangers of read andeva l for de ta i ls.

T he print and read func tions think about va lues with the m ind of a com pute r, not the m ind of a hum an. A com pute r loveshaving strings of text surrounded by quotes. It doesn’t have a hum an bra in, and consequently, i t can’t understand what we m eanwhen we feed i t raw textua l inform ation. However, i f a text fragm ent is surrounded by quotes, even a dum b old com pute r canfigure out tha t the va lue we’re handing i t is probably a string of text .

T he print and read com m ands ac tua lly take this philosophy to the extrem e. Alm ost any conce ivable type of da ta in L isp (withthe exception of ac tua l func tions and som e advanced da ta struc tures) can be printed and read using these com m ands, without thesl ightest bi t of loss a long the way. You can probably a lready im agine som e scenarios where this fea ture would be im m ense lyva luable , such as writ ing som e ha iry and huge piece of da ta to a fi le , and loading i t in aga in a t a la te r da te .

As a sim ple exam ple , the fol lowing code has exac tly the sam e design as the previous func tion, but am az ingly, i t can read andprint a num ber instead of a string. Notice how the program prints and reads num bers without the use of quotes, since L isp knowswhen som ething is a num ber just by see ing the num ber in i ts raw form .> (defun add-five ()

(print "please enter a number:")(let ((num (read)))(print "When I add five I get")(print (+ num 5))))ADD-FIVE> (add-five)"please enter a number:" 4"When I add five I get"9

L et’s look a t som e m ore exam ples of wha t happens when we use print to write out va lues.(print '3) => 3 An integer

(print '3.4) => 3.4 A float(print 'foo) => FOOA symbol. It may be printed in all caps, since CommonLisp symbols are blind to letter case.(print '"foo") => "foo" A string(print '#\a) => #\a A character

T hese exam ples a re a l l rea l ly boring, since print pre t ty m uch just prints out exac tly wha t we put in. Note tha t we put anexplic i t quote on the front of each va lue . It could be om itted and would be im plic i t in a l l cases but the sym bol nam e, since asym bol can a lso re fe r to func tions.

T he last exam ple shows how li te ra l charac te rs a re ente red in L isp. T o c rea te a L isp charac te r, just place the #\ sym bols in frontof the ac tua l charac te r. L isp a lso has spec ia l l i te ra ls de fined for nonvisible charac te rs. T he m ost im portant for everyday use a re#\newline, #\tab, and #\space.

A table of output from the read func tion would look just a s boring as this table for print, in the sam e sym m etrica l way.

Note

In the exam ples above I sta ted tha t Com m on L isp sym bols a re bl ind to le t te r case . W hile this is t rue for m ost strings, i t is in fac tpossible to c rea te case -sensit ive sym bols by surrounding the sym bol with the vert ica l pipe |. So the sym bol |CaseSensitiveSymbol|will re ta in i ts case . Sym bols surrounded by vert ica l pipes can even conta in punc tua tion. Hence |even this is a legal Lispsymbol!|

Page 105: Land of Lisp - Barski M.D., Conrad

Reading and P r inting Stuff the Way H umans Like It

Of course , our ini t ia l l i t t le say-hello func tion does a pre t ty awful job of gree ting people , even if i t has som e inte rest ingpropert ies. It would be m uch be tte r i f we had m ore func tions tha t could m ake i t friendlie r for hum ans. Ac tua lly, we can c rea te a(very sym m etrica l) l i t t le table tha t sum m arizes wha t we would l ike :

As you can see , L isp has a com m and tha t can print pieces of da ta in a way tha t is appea ling to hum ans. T he princ func tion cantake any piece of L isp da ta , and i t t rie s to print tha t da ta in a way hum ans would pre fe r. It wil l do the basic things you m ightexpec t: leave off the quotes on a string, print charac te rs in the ir raw form , and so on. Here a re som e exam ples:(princ '3) => 3

(princ '3.4) => 3.4(princ 'foo) => FOO(princ '"foo") => foo(princ '#\a) => a

Here’s an exam ple of princing a charac te r tha t has a spec ia l m eaning:> (progn (princ "This sentence will be interrupted")

(princ #\newline)(princ "by an annoying newline character."))This sentence will be interruptedby an annoying newline character.

By i ts na ture , princ could be used to print any a rbitra ry output of charac te rs you want. T his is fundam enta l ly diffe rent fromprint. As we’ve discussed, the cool thing about the print com m and is tha t i t prints objec ts in such a way tha t they can a lways be“read” back into the ir inte rna l representa t ion. However, this m eans print can’t be used to genera te any a rbitra ry bi t of text . Onthe other hand, princ can be used to print anything you want.

T here fore , a l though princ can print stuff in a way tha t hum ans pre fe r, i t ’s a one-way stree t . Once we’ve printed things withprinc, only a hum anlike inte l l igence could dec ipher how to change things back into a m eaningful , appropria te L isp da ta struc ture .Since com pute rs a re too stupid to do this right now, i t m eans our be loved sym m etry has been broken.

Of course , we could a lways chea t and com e up with som e a rbitra ry rules for how the com pute r should inte rpre t wha t the hum anente rs. An obvious way to do this would be to say to the com pute r, “ Just le t the use rs type in wha tever they want unti l they hit theente r key, then trea t the whole thing as a string. ” T he func tion tha t does this in Com m on L isp is ca l led read-line. However, i t hasnone of the sophist ica t ion of the read, print, and princ func tions, since i t knows about nothing beyond charac te rs and strings.

W ith this new knowledge , we can fina lly go full c irc le and c rea te a proper func tion for gree ting som eone , without ugly quotes orother oddit ies:> (defun say-hello ()

(princ "Please type your name:")

(let ((name (read-line)))

(princ "Nice to meet you, ")

(princ name)))SAY-HELLO> (say-hello)Please type your name: Bob O'MalleyNice to meet you, Bob O'Malley

T his version of the say-hello func tion is sim ila r to our first ve rsion. However, when the com pute r asks use rs for the ir nam e

, i t now does so without print ing quotes a round the text string. T he sam e holds true to when we print the gree ting

. Also, use rs can now ente r in any nam e (inc luding a nam e with spaces and quotes), since the read-line com m and capturesand re turns a l l the text ente red unti l the ente r key is pressed, without any fuss.

Page 106: Land of Lisp - Barski M.D., Conrad

The Symmetry Between Code and Data in Lisp

You have seen tha t L isp has very e legant and sym m etrica l fac i l i t ie s for t ransla t ing raw string da ta from the outside world andconvert ing i t to and from L isp syntax expressions. But L isp has an even deeper sym m etry. It can trea t program code and da tainte rchangeably. A program m ing language tha t uses the sam e da ta struc tures to store da ta and program code is ca l led homoiconic .

You saw an exam ple of hom oiconic i ty in Chapte r 3, when we discussed code m ode and da ta m ode . In tha t exam ple , we used thequote to change be tween the two m odes:> '(+ 1 2) ;data mode

(+ 1 2)> (+ 1 2) ;code mode3

In the previous chapte r, we took this concept one step further by using a quasiquote when defining the describe-path func tion.But the quoting and quasiquoting fac i l i t ie s in L isp a re som ewhat l im ited in the ir abil i t ie s. W hat if we genera te a piece of L isp

code from sc ra tch som ehow and wish to execute i t a s i f i t were a piece of code? For exam ple , le t’s store a raw chunk of codeinside a variable :> (defparameter *foo* '(+ 1 2))

*FOO*How could we execute the code tha t’s in the *foo* va riable? W e need an even m ore powerful com m and to m ake this possible .

T his is the eval com m and:> (eval *foo*)

3Because the eval com m and is so powerful and ye t so sim ple , i t is extrem ely entic ing to beginning L ispers. You want to write a

program with se lf-m odifying code? T hen eval wil l be your best friend. In fac t , this is probably the m ain reason why the a rt ific ia linte l l igence (AI) freaks back in the day loved L isp so m uch. Go ahead and try writ ing som e program s tha t use the eval com m and.You’ll find tha t i t ’s a lot of fun.

However, an experienced L isper wil l only ra re ly use eval. Unti l you have a few thousand l ines of L isp code under your be lt , yourea lly won’t know when i t is appropria te to use this extrem ely powerful com m and. Often, a beginning L isper wil l use the evalcom m and instead of de fining a L isp m acro. W e wil l discuss m acros in Chapte r 16.

T he bottom line is tha t the sym m etry of da ta and code in L isp pre t ty m uch m akes L isp the poste r child of hom oiconic i ty.Quoting, quasiquoting, the eval com m and, and m acros a l low you to take advantage of this property in your code .

Warning

Inexperienced use of eval can pose a securi ty risk. See T he Dangers of read and eva l in T he Dangers of read and eva l for m oreinform ation.

Page 107: Land of Lisp - Barski M.D., Conrad

Adding a Custom Interface to O ur G ame Engine

So fa r, we’ve been using the L isp RE PL to ente r our gam e com m ands. It’s am az ing how well this works for prototyping ourgam e. But now tha t you’ve ga ined an understanding of the basic Com m on L isp input and output com m ands, we can begin to put inplace our own custom text gam e inte rface , which wil l be be tte r suited for inte rac ting with the player.

Page 108: Land of Lisp - Barski M.D., Conrad

Setting Up a Custom REP L

Crea ting your own RE PL in L isp is a lm ost laughably easy. Here ’s a sim ple custom RE PL for our gam e, which le ts us ca l l thelook com m and in exac tly the sam e way as the standard RE PL did:> (defun game-repl ()

(loop (print (eval (read)))))GAME-REPL> (game-repl)(look)(YOU ARE IN THE LIVING-ROOM. A WIZARD IS SNORING LOUDLY ON THE COUCH. THERE ISA DOOR GOING WEST FROM HERE. THERE IS A LADDER GOING UPSTAIRS FROM HERE. YOUSEE A WHISKEY ON THE FLOOR.)

Stop m e if this explana tion of game-repl is confusing: First i t reads a com m and, then evals i t , and fina lly prints i t . T he onlycom m and you haven’t seen before is loop (covered in de ta i l in Chapte r 10), which as you m ight expec t , sim ply loops forever. (InCL ISP, you’l l need to hi t c trl-C and type :a to ge t out of the infini te loop. ) As you can see , i t ’s easy to build your own RE PL bysim ply ca l l ing read, eval, print, and loop.

Of course , to custom ize the behavior of our RE PL , we’l l want to ca l l our own versions of these func tions. Also, we’l l want a wayto exit from our gam e in a m ore graceful m anner. So, le t’s redefine game-repl a s fol lows:(defun game-repl ()

(let ((cmd (game-read)))

(unless (eq (car cmd) 'quit)

(game-print (game-eval cmd))

(game-repl))))

In this ve rsion, we first capture the com m and the player types using a loca l va riable , cmd . T his way, we can inte rcept anya ttem pt to ca l l quit and use i t to exit our game-repl. In other words, we want to continue running our RE PL unless the use r typed

quit . Otherwise , the func tion evals and prints , but using our custom versions of these func tions, which we’l l wri te

short ly. Fina lly, the game-repl func tion ca lls i tse lf recursive ly , causing i t to loop back, as long as we had not dec ided toquit ea rl ie r.

Page 109: Land of Lisp - Barski M.D., Conrad
Page 110: Land of Lisp - Barski M.D., Conrad

Writing a Custom read F unction

T he purpose of our game-read func tion is to fix the two annoyances tha t m ake the standard L isp read func tion wrong for playingour gam e:

T he standard L isp read forces us to put pa rentheses a round our com m ands. As any old-school text adventureplayer knows, we should be able to just type look without any parentheses. T o accom plish this, we can just ca l lread-line and st ick in our own parentheses.

W i t h read, we m ust put a quote in front of any func tion com m ands. W e should be able to type walk eastwithout a quote in front of east. T o do this, we’l l st ick a quote in front of the param ete rs a fte r the fac t .

Here ’s a de fini t ion of game-read tha t does both of these things:(defun game-read ()

(let ((cmd (read-from-string

(concatenate 'string "(" (read-line) ")"))))

(flet ((quote-it (x)(list 'quote x)))

(cons (car cmd) (mapcar #'quote-it (cdr cmd))))))

T he read-from-string com m and works just l ike the read com m and, but le ts us read a syntax expression (or any otherbasic L isp da ta type) from a string instead of direc t ly from the console .

T he string we use for this is a tweaked version of a string we ge t from read-line . W e tweak i t by adding quotes a round i tusing the concatenate com m and, which can be used for conca tena ting strings toge ther, a s we ll a s som e parentheses. T he result istha t the cmd va riable wil l be se t to the player’s requested com m and and converted into a L isp syntax expression. For exam ple , i fthe player types in walk east, the cmd va riable wil l be se t to the expression (walk east), which is a l ist conta ining two sym bols.

Next, we define a loca l func tion ca lled quote-it , which we can use to quote any a rgum ents the player has in acom m and. How exac tly does i t m anage to quote a param ete r? W ell , i t turns out tha t the single quote is just shorthand for a L ispcom m and ca lled quote. T his m eans tha t 'foo and (quote foo) a re the sam e. W e can quote a raw param ete r by sim ply putt ing theparam ete r in a l ist with the quote com m and in front .

Rem em ber tha t loca l func tions can be defined with labels or flet. Since we a re not using any recursion in the quote-it

func t ion , we can use the sim ple r flet com m and. T he fina l l ine in the game-read func tion applies quote-it to every

argum ent in the player’s com m and. It does this by m apping quote-it ac ross the cdr of the cmd va riable (and then a t tachingthe first word in the com m and back on front with car).

L e t’s t ry our new func tion:> (game-read)

walk east(WALK 'EAST)

As you can see , the game-read func tion is able to add parentheses and quotes—just wha t our gam e needs!

Note

Our custom reader has som e l im ita t ions tha t a suffic iently boneheaded gam e player could conce ivably bring to the surface . T heplayer could ente r a we ird string l ike "(look", with m ism atched parentheses, and i t would cause a L isp exception in the game-read

Page 111: Land of Lisp - Barski M.D., Conrad

com m and. T here ’s nothing wrong with this, pe r se , since the standard read com m and wil l a lso ac t strange ly when given garbledinput. (In this case , i t wil l le t you ente r another l ine of input in the hopes tha t you wil l eventua lly supply i t with the m issingparenthesis. ) However, our game-repl doesn’t handle this si tua t ion properly, causing the ac tua l game-repl to c rash. T his would beas if you were playing Z ork and typed in a com m and so vile tha t i t took down the Z ork gam e i tse lf. T his ra re si tua tion could beaddressed by having addit iona l exception handling, as discussed in Chapte r 13.

Page 112: Land of Lisp - Barski M.D., Conrad

Writing a game-eval F unction

Now tha t we’ve c rea ted a nigh-perfec t L isp reader, le t’s think about how we could im prove the eval com m and. T he m ainproblem with using eval in a gam e is i t a l lows you to ca l l any L isp com m and, even if tha t com m and has nothing to do withplaying the gam e. T o he lp protec t our program from hackers, we’l l c rea te a game-eval func tion tha t a l lows only ce rta in com m andsto be ca l led, as fol lows:(defparameter *allowed-commands* '(look walk pickup inventory))

(defun game-eval (sexp)

(if (member (car sexp) *allowed-commands*)

(eval sexp)'(i do not know that command.)))

T he game-eval func tion checks if the first word in the ente red com m and is in the l ist of a l lowed com m ands, using the member

func tion . If i t is, we then use the standard eval to execute the player’s com m and . By checking tha t the com m andca lled by the player is in the offic ia l l ist , we protec t ourse lves aga inst any a t tem pts to ca l l m alic ious com m ands.

Warning

Our game-eval func tion does not offe r 100 percent protec tion aga inst hacking. See T he Dangers of read and eva l in T he Dangersof read and eva l for de ta i ls.

Page 113: Land of Lisp - Barski M.D., Conrad

Writing a game-pr int F unction

T he fina l m issing piece in our game-repl system is the game-print func tion. Of a l l the l im ita t ions in the L isp RE PL version ofour gam e, one was the m ost obvious: All the text desc ript ions printed in the gam e were in uppercase .

L ast I checked, throughout the current m il lennium , com pute rs have been able to display both uppercase and lowercasecharac te rs. By writ ing our own game-print func tion, we can solve this problem .

Before we step through the game-print func tion’s code , le t’s look a t an exam ple of i ts output:> (game-print '(THIS IS A SENTENCE. WHAT ABOUT THIS? PROBABLY.))

This is a sentence. What about this? Probably.As you can see , the game-print func tion converts our sym bol-based writ ing into properly capita l ized text . By having this

func tion ava ilable , we can store the text in our gam e engine in the m ost com fortable form at possible : l ists of sym bols. T his form atm akes i t easie r to m anipula te the text . T hen, a t the point of presenta t ion, we can decora te these sym bol l ists with presenta t ionde ta i ls.

Of course , in this exam ple , the decora tions a re very sim ple . All we do is adjust the case . But you can a lready see som e sm allbenefi ts of separa t ing the presenta t ion de ta i ls from the da ta m ode l. For instance , suppose we changed the describe-path func tionto write sentences l ike “L eft of he re l ie s a door. ” No further changes would be needed; the program would autom atica l ly know tocapita l ize the Left a t the beginning of the sentence .

However, the rea l benefi ts com e into play when you want to use m ore sophist ica ted m ethods of presenta t ion, such as genera t ingHT ML code . You m ight want to incorpora te custom sem antics for your text gam e to enhance the appearance of the text , such aschanging colors, fonts, and so on. For instance , you could a l low your gam e descript ions to conta in phrases such as “You a re be inga ttacked by a (red evil dem on). ” T hen you could just ca tch the keyword red in the game-print func tion to write the enc losed textin red. W e wil l be c rea ting an HT ML presenta t ion system sim ila r to this in Chapte r 17.

Now we’re ready to look a t the game-print func tion’s code :(defun tweak-text (lst caps lit)

(when lst

(let ((item (car lst))(rest (cdr lst)))

(cond ((eq item #\space) (cons item (tweak-text rest caps lit)))

((member item '(#\! #\? #\.)) (cons item (tweak-text rest t lit)))

((eq item #\") (tweak-text rest caps (not lit)))(lit (cons item (tweak-text rest nil lit)))

((or caps lit) (cons (char-upcase item) (tweak-text rest nil lit)))

(t (cons (char-downcase item) (tweak-text rest nil nil)))))))

(defun game-print (lst)

(princ (coerce (tweak-text (coerce (string-trim "() "

(prin1-to-string lst))'list)t

nil)'string))(fresh-line))

Page 114: Land of Lisp - Barski M.D., Conrad

T he game-print func tion and i ts he lper func tion a re a bi t m ore com plica ted than the other func tions we’ve looked a t so fa r. T hefirst im portant pa rt of the code tha t is executed is in game-print, where i t converts the sym bol l ist (conta ining the text whose

layout we want to fix) into a string with prin1-to-string , one of L isp’s m any print va riants. T he to-string pa rt m eansthis func tion doesn’t dum p the result to the sc reen, but just re turns i t a s a string. T he 1 m eans i t wil l stay on a single l ine . T hestandard print com m and precedes i ts output with a newline charac te r and a lso fol lows i t with a space . T he func tions prin1 andprin1-to-string va riants don’t add these extra charac te rs.

Next, game-print converts the string to a l ist of charac te rs with the coerce func tion . By coerc ing our string into a l ist , wecan reduce the bigger goa l of the func tion into a l ist-processing problem . T his is sm ack-dab in the L isp com fort zone . In this case ,we’re c rea ting a l ist of the charac te rs m aking up the text we want to fix.

W e can now send the da ta to the l ist-ea te r func tion tweak-text . Notice tha t som e of the a rgum ents used in the code oft h e game-print func tion a re printed on the ir own l ine for c la ri ty. You can easi ly see which a rgum ents a re m eant for which

com m ands by looking a t the indenta t ion. For instance , the t and nil a rgum ents be long to tweak-text.T he tweak-text func tion looks a t each charac te r in the l ist and m odifies i t a s needed. At the top of this func tion, we define two

loca l va riables, item and rest, which we ge t by chewing off an i tem from the front of the sentence we’re tweaking . T hen,

the tweak-text func tion uses a cond to check the charac te r a t the top of the l ist for diffe rent condit ions .

T he first condit ion i t checks for is whe ther the charac te r is a space charac te r . If so, i t just leaves the space unchanged and

processes the next charac te r in the l ist . If the charac te r is a pe riod, quest ion m ark, or exc lam ation point , we turn on the capparam ete r for the rest of the string (by using the va lue t a s an a rgum ent in the recursive ca l l) to indica te tha t the next sym bol is a tthe beginning of a sentence and needs a capita l le t te r.

W e a lso track whether we’ve encounte red a quota t ion m ark . W e do this because , infrequently, a sym bol l ist is notadequa te for encoding E nglish text . E xam ples inc lude having a com m a (com m as a re not a l lowed in standard Com m on L ispsym bols) or produc t nam es with nonstandard capita l iza t ion. In these cases, we can just fa l l back on using text strings. Here ’s anexam ple :> (game-print '(not only does this sentence

have a "comma," it also mentions the "iPad."))Not only does this sentence have a comma, it also mentions the iPad.

Our sam ple gam e doesn’t ac tua lly need the fa l lback fac i l i ty. None the less, this fea ture a l lows the game-print func tion to handlem any basic exceptiona l text si tua t ions tha t you m ay encounte r if you try to expand the gam e on your own. W e te l l the func tion totrea t the capita l iza t ion as shown l i te ra l ly by turning on the lit va riable in the recursive ca l l . As long as this va lue is se t , the

tweak-text func tion prevents the capita l iza t ion rules (which sta rt a t ) from be ing reached.T he next thing the tweak-text func tion checks is whe ther the next charac te r is supposed to be capita l ized. If i t is, we use the

char-upcase func tion to change the current charac te r to uppercase (if i t isn’t a lready) before processing the next i tem in the l ist

.

If none of the other condit ions were m et, we know tha t the current charac te r should be lowercase , and we can convert i tusing the char-downcase func tion.

Afte r tweak-text is finished correc ting the text in the charac te r l ist , the game-print func tion coerces i t back into a proper string

and princs i t . T he fresh-line func tion a t the end of game-print m akes sure tha t the next i tem appearing on the sc reen wil lsta rt on a fresh l ine .

W e have now com ple ted the task of print ing the origina l l ist of sym bols to the sc reen, using a se t of decora tions appropria te forthe needs of an adventure gam e engine .

Page 115: Land of Lisp - Barski M.D., Conrad

Trying O ut O ur F ancy New G ame Interface

W e have now com ple ted a l l the pieces needed for a custom RE PL for our gam e engine . Sim ply ca l l the game-repl func tion andexplore our new gam e world. Rem em ber tha t we wil l be expanding this engine into a ful l gam e, with addit iona l com m ands, inChapte r 17.> (game-repl)

lookYou are in the living-room. A wizard is snoring loudly on the couch. There isa door going west from here. There is a ladder going upstairs from here. Yousee a whiskey on the floor. You see a bucket on the floor.walk westYou are in a beautiful garden. There is a well in front of you. There is adoor going east from here. You see a frog on the floor. You see a chain onthe floor.pickup chainYou are now carrying the chainscratch headI do not know that command.pickup chickenYou cannot get that.walk eastYou are in the living-room. A wizard is snoring loudly on the couch. There isa door going west from here. There is a ladder going upstairs from here. Yousee a whiskey on the floor. You see a bucket on the floor.walk upstairsYou are in the attic. There is a giant welding torch in the corner. There is aladder going downstairs from here.inventoryItems-chainwalk chinaYou cannot go that way.walk downstairsYou are in the living-room. A wizard is snoring loudly on the couch. There isa door going west from here. There is a ladder going upstairs from here. Yousee a whiskey on the floor. You see a bucket on the floor.pickup bucketYou are now carrying the bucketlookYou are in the living-room. A wizard is snoring loudly on the couch. There isa door going west from here. There is a ladder going upstairs from here. Yousee a whiskey on the floor.quit

Success! W e now have an extrem ely flexible text gam e engine . It can be expanded and debugged within the L isp RE PL . It a lsohas a ful ly custom izable inte rface to offe r the player a seam less text adventure experience . As we put i t toge ther, you saw som em ind-bending L isp techniques tha t le t us construc t this engine with a m inim um of fi l le r code or other overhead.

Page 116: Land of Lisp - Barski M.D., Conrad

The Dangers of read and eval

W e’ve used both the eval and the read com m ands in c rea ting a custom L isp RE PL . T hese com m ands a re very powerful , but a lsovery dangerous. Using them without taking the proper precautions m ight a l low a hacker to a t tack your software by runningm alic ious com m ands.

For exam ple , suppose our program needed a func tion ca lled format-harddrive. T his is not a func tion we would want just anyperson to have access to, and i t could be very dangerous if a hacker som ehow tricked our gam e RE PL into ca l l ing i t .

T he game-eval func tion we c rea ted ea rl ie r in this chapte r has som e c rude sa feguards to prevent a player from ente ring format-harddrive a s a gam e com m and. Here ’s wha t happens if we try to run this com m and in our new gam e RE PL :> (game-repl)

format-harddriveI do not know that command.

Our game-eval func tion wil l run only com m ands tha t a re in an approved l ist . T his gives our gam e a sort of firewall , which le ts usaccess the powers of L isp to eva lua te com m ands while st i l l preventing the player from hacking the gam e.

However, the re a re a lso m ore sophist ica ted exploits players could try. For instance , they could ente r walk (format-harddrive).Fortuna te ly, our game-read func tion forces a l l func tion param ete rs into da ta m ode by using quote-it. By using quote-it in game-read, the ac tua l code tha t is executed is (walk '(format-harddrive)). T he quote in front of (format-hardrive) puts the m alic iouscom m and into da ta m ode , so nothing bad can happen.

One a t tack m ethod tha t will break our program is to use reader macros. T hese a re an advanced se t of fea tures, buil t into theCom m on L isp read com m and, tha t open another avenue for executing m alic ious com pute r code . (Rem em ber tha t be fore we useeval on gam e com m ands, they first pass through read. ) An exam ple of a gam e com m and tha t wil l successfully execute evil code iswalk #.{format-harddrive}.

T he bottom line is tha t you can never be sure tha t a L isp program using eval or read is com ple te ly sa fe from a hacker. W henwrit ing produc tion L isp code , you should try to avoid these two com m ands when possible .

Page 117: Land of Lisp - Barski M.D., Conrad

What You've Learned

In this chapte r, we c rea ted a custom RE PL to supercharge our text adventure gam e. Along the way, you lea rned the fol lowing:T he print and read func tions le t you direc t ly com m unica te with the use r through the console . T hese two func tions work in a

com pute r-friendly way.Other input/output func tions a re not as e legant as read and print, but a re friendlie r for inte rac ting with hum ans. E xam ples

inc lude princ and read-line.A homoiconic program m ing language stores i ts program code and program da ta in a sim ila r form at. L isp’s quoting,

quasiquoting, eval, and m acro fea tures m ake i t extrem ely hom oiconic .It’s easy to write your own custom RE PL .It’s sim ple to transform your inte rna l L isp da ta into the form at m ost suitable for your program ’s inte rface . T his m akes i t easy

to separa te presenta t ion de ta i ls from your program ’s inte rna l da ta struc tures.

Page 118: Land of Lisp - Barski M.D., Conrad

Chapter 6.5. lambda: A F unction So Important It Deserves Its O wn Chapter

It’s im possible to oversta te the im portance of the lambda com m and in L isp. In fac t , this com m and is pre t ty m uch the entirereason tha t L isp exists in the first place .

Page 119: Land of Lisp - Barski M.D., Conrad

What lambda Does

In short , lambda le ts you c rea te a func tion without giving i t a nam e. For exam ple , le t’s say we c rea te a half func tion tha t takesa num ber and divides i t in ha lf. Unti l now, we’ve writ ten such a func tion this way:(defun half (n)

(/ n 2))

It turns out tha t , in L isp, func tions a re ac tua lly va lues tha t we can view and pass a round just as if they were num bers or l ists. Anexperienced L isp program m er would say tha t func tions a re f irst-c lass values in L isp. As you saw in Chapte r 5, you can ac tua lly ge ta t the func tion represented by the word half by using the func tion opera tor:> #'half

#<FUNCTION HALF ...>T he lambda com m and just le ts you do these sam e two things in a single step. You can define a func tion and then ge t i t , without

giving your func tion a nam e:> (lambda (n) (/ n 2))

#<FUNCTION :LAMBDA ...>T he first pa ram ete r to the lambda com m and is a pa ram ete r l ist , no diffe rent from the param ete r l ist used in defun. T he rest of

the param ete rs a re just the com m ands for the body of the unnam ed func tion.Once you have a va lue representing your unnam ed ha lving func tion, you can pass i t direc t ly to other Com m on L isp com m ands,

such as the mapcar or apply com m ands. For instance , we could do the fol lowing to e legantly ha lve a l l the va lues in a l ist :> (mapcar (lambda (n) (/ n 2)) '(2 4 6))

(1 2 3)Because not a l l pa ram ete rs of the lambda com m and a re eva lua ted, lambda i tse lf is not ac tua lly a true func tion. It is som ething

ca lled a macro. Rem em ber from Chapte r 2 tha t a l l pa ram ete rs to a L isp func tion a re eva lua ted before the func tion i tse lf iseva lua ted. Macros, on the other hand, have spec ia l powers and a re a l lowed to break those rules. You’l l lea rn m ore about m acros inChapte r 16.

Also, to confuse m atte rs a bi t , the ac tua l va lue tha t lambda re turns is a regula r L isp func tion—in this case , a func tion tha t cuts anum ber in ha lf. W hen L ispers ta lk about lam bda func tions—which they pre t ty m uch do for breakfast , lunch, and dinner—they’reta lking about func tions c rea ted using lambda. T hey’re not ta lking about the lambda m acro i tse lf, which is not a func tion. Got tha t?lambda le ts your program s do very com plica ted things.T he lambda form a llows your program m ing code to take a conceptua l leap.W hile m ost program m ing languages try to keep the worlds of func tions and va lues separa te , L isp le ts you bridge these worlds as

desired. For instance , i f you want to package up a l i t t le ad hoc func tion and pass i t off to another part of your program , lambdadoes exac tly wha t you need.

You wil l see tha t m ost L isp program s use this com m and very heavily. T he sam e holds true for the rem aining exam ples in thisbook.

Page 120: Land of Lisp - Barski M.D., Conrad

Why lambda Is So Important

T he abil i ty to pass a round func tions as if they were just pla in old pieces of da ta is inc redibly va luable . Once you ge t used todoing this, you open up a l l kinds of conceptua l possibi l i t ie s in the design of your program s. E ventua lly, your program s wil l sta rtlooking very diffe rent from program s in m ore (dare I say) pedestrian languages, such as Java or C. T he nam e for the style ofprogram m ing tha t re l ies heavily on passing func tions as va lues is ca l led higher-order func tional programming. W e wil l look a t thisstyle in m ore de ta i l in Chapte r 14.

An even m ore im portant reason why L ispers go gaga over lambda is tha t , a s i t turns out , in a pure ly m athem atica l sense , lambdais ac tua lly the only L isp com m and there is!

Reca ll tha t L isp is unusua l am ong program m ing languages in tha t i t was derived direc t ly from a m athem atica l concept ca l ledthe lambda calculus. In short , the lam bda ca lculus is a theore t ica l program m ing language tha t conta ins only one com m and: thelambda com m and. By having only this single com m and and using spec ia l code transform ations, i t ’s possible to c rea te a ful lyfunc tioning (though perhaps not prac tica l) program m ing language .

T he take-hom e point is tha t the lambda spec ia l form is the m ost fundam enta l com m and in a L isp system , and the centra lconcept from which other func tions in L isp derive . In fac t , i t is the centra l concept from which the very idea of L isp i tse lforigina ted.

Now tha t you have a basic understanding of lambda, you’re ready to tackle som e m ore com plica ted program m ing exam ples tha twould be hard to write without the anonym ous func tions this com m and perm its.

Page 121: Land of Lisp - Barski M.D., Conrad

What You've Learned

T his short chapte r discussed how to c rea te anonym ous func tions. Here a re the m ain points:By using lambda, you can c rea te a func tion without needing to give i t a nam e.Many func tions in L isp accept func tions as param ete rs. If you use these func tions, you a re using a technique ca lled higher-order

func tional programming.

Page 122: Land of Lisp - Barski M.D., Conrad

Chapter 7. G oing Beyond Basic Lists

In this chapte r, we’l l go beyond basic l ist concepts. W e’ll ta lk about spec ia l kinds of l ists, and we’l l wri te a gam e tha t wil l takelist m anipula t ion to a new leve l .

Page 123: Land of Lisp - Barski M.D., Conrad

Exotic Lists

As you lea rned in Chapte r 3, l ists in L isp a re buil t out of cons ce l ls—sm all da ta struc tures tha t a l low you to l ink toge ther twopieces of da ta . T he right slot in the last cons ce l l in a l ist should conta in a nil.

By stringing toge ther severa l cons ce l ls, you can c rea te a l ist of any length. For instance , this is how we would use cons ce l ls toc rea te a l ist of the num bers 1, 2, and 3:(cons 1 (cons 2 (cons 3 nil)))

Since i t’s so cum bersom e for hum ans to think of a cha in of cons ce l ls as a l ist , L isp has a spec ia l , sim plified syntax for print ingout such l ists. You can see this for yourse lf by eva lua ting a cha in of cons ce l ls in the RE PL :> (cons 1 (cons 2 (cons 3 nil)))

(1 2 3)L isp uses the sim ple r l ist syntax when i t pa rrots our cha in back to us in the RE PL . It shows our string of cons ce l ls as a l ist of

three i tem s. T he im portant point to rem em ber is tha t this di f fe rence in appearance is entire ly superfic ial . No m atte r how a L isplist is displayed, fundam enta l ly, i t a lways rem ains a cha in of cons ce l ls.

Page 124: Land of Lisp - Barski M.D., Conrad

Dotted Lists

So, wha t happens if we devia te from the c lassic “string of conses” form ula? How will a L isp environm ent dea l with this whenprinting l ists?

Suppose we try to c rea te a l ist of the num bers 1, 2, and 3, l ike this:(cons 1 (cons 2 3))Here , instead of c rea ting a third cons ce l l for the third num ber of our l ist , we stuff i t into the right slot of the previous ce l l .

W hat would the printed response look l ike if we were to ente r this struc ture into a L isp RE PL ? L e t’s t ry i t out:> (cons 1 (cons 2 3))

(1 2 . 3)T o indica te tha t the fina l i tem in the l ist wasn’t found in the proper loca tion for a nil-te rm ina ted l ist , L isp places a dot in front

of this fina l i tem . T his dot is basica l ly L isp’s way of saying, “ I t ried to print this struc ture you ente red using l ist nota t ion, but thelast i tem in the l ist didn’t conta in the usua l nil I expec ted; instead, i t conta ined 3. ”

A l ist in L isp tha t ends in som ething other than a nil is re fe rred to as a dotted l ist . Dotted l ists a re kind of an oddity in the L andof L isp. In and of them se lves, they a re not tha t use ful a tool for L isp program m ing. It would be quite unusua l for a L ispprogram m er to store da ta in dotted l ists as a regula r prac tice . However, given the pervasiveness of cons ce l ls in L isp, you wil lfrequently encounte r a non-nil va lue a t the end of a cha in of cons ce l ls. T ha t’s why you should becom e fam ilia r with dotted l ists,even if you m ay never use them direc t ly.

Another way of thinking about this dot nota t ion is to consider i t a s sim ply an a l te rna te syntax for the cons com m and, used inda ta m ode . In fac t , i f we wanted to m ake l ife ha rd for ourse lves, we could even c rea te regula r, proper l ists using the dot nota t ion,l ike this:> '(1 . (2 . (3 . nil)))

(1 2 3)Using this l ine of thinking, the dot appears in a dotted l ist sim ply because L isp is forced to show the fina l cons ce l l in order to

m ainta in the consistency of i ts l ist-print ing m echanism .

Page 125: Land of Lisp - Barski M.D., Conrad

Pairs

One com m on and prac tica l use for dotted l ists in L isp program s is to e legantly represent pa irs. For instance , suppose we wantedto represent a pa ir of the num bers 2 and 3. One way to do this would be to cons toge ther these two num bers:> (cons 2 3)

(2 . 3)E ssentia l ly, a l l we’re doing here is c rea t ing a dotted l ist of length two. As expec ted, L isp uses dot nota t ion to display this pa ir.Crea ting pa irs in this m anner in L isp is ve ry convenient and e ffic ient . It ’s convenient because we can extrac t m em bers from the

pa ir using the standard car and cdr com m ands. It’s re la t ive ly e ffic ient because the L isp environm ent needs to a l loca te only asingle cons ce l l to connec t the two i tem s.

T hese types of pa irs a re com m only used in L isp program s. For instance , you could use them to store the x-and y-coordina tes of apoint or a key/va lue pa ir in a com plex da ta struc ture . You wil l see this la t te r use for pa irs when we discuss assoc ia t ion l ists.

Page 126: Land of Lisp - Barski M.D., Conrad

Circular Lists

Here is the pic ture we used in Chapte r 3 to i l lustra te the cons ce l ls tha t m ake up the l ist '(1 2 3):

Now suppose tha t we c rea ted a weird m utant of this l ist . L e t’s have the cdr of the third cons ce l l point back to the first consce ll , ra ther than to nil:

Page 127: Land of Lisp - Barski M.D., Conrad

E very cons ce l l in a l ist theore t ica l ly exists as a separa te objec t in m em ory. Since the car and cdr slots in a ce l l can point toany other objec t in m em ory, a cons ce l l can point to an upstream cons ce l l of a l ist . W e ca ll this a c ircular l ist .

But be fore you experim ent with c ircula r l ists in any Com m on L isp environm ent, you should run this com m and:(setf *print-circle* t)Sett ing *print-circle* to true warns L isp tha t you plan on playing shenanigans with se lf-re fe rentia l da ta struc tures, and tha t i t

needs to be extra ca re ful when print ing on the sc reen any of the m onstrosi t ie s you m ay c rea te . If you were to print a c ircula r l istwithout this va riable se t , the re ’s no te l l ing wha t would happen, but wha tever the outcom e, i t wouldn’t be pre t ty (unless you findsom e beauty in stack overflows and infini te loop print ing).

W hen you have *print-circle* se t to true , Com m on L isp wil l use m ore com plex print ing routines for print ing da ta struc tures.T hese routines (which a re disabled by default to im prove perform ance) wil l check to see if you’ve run into a previously seen consce ll , so tha t print ing doesn’t end up putt ing you into an infini te loop.

So how would you go about c rea ting a c ircula r l ist? T he m ost stra ightforward way is to use the setf com m and to put extra stuffin the first pa ram ete r, l ike so:> (defparameter foo '(1 2 3))

FOO> (setf (cdddr foo) foo)#1=(1 2 3 . #1#)

In this exam ple , we’ve c rea ted an infini te l ist of '(1 2 3 1 2 3 1 2 3 ...) by replac ing the nil a t the end of a sim ple l istwith a re fe rence to the l ist i tse lf.

T he abil i ty to place com plex expressions in the first pa ram ete r of a setf com m and, as in this exam ple , is ve ry cool , and we’l lexplore i t in grea te r de ta i l in Chapte r 9.

Note

CL ISP (and other Com m on L isps) can dea l with the print ing of c ircula r l ists ve ry sensibly. Som ehow, i t m ust address the fac t tha tone part of the l ist re fe rs to another part . As you can see , i t uses an esote ric , but quite c lever, nota t ion to l ink the se lf-re fe rentia lparts of the expression. However, I’m sure you can a lso apprec ia te tha t , a s the com plexity of any se lf-re fe rentia l da ta inc reases, theprinted results offe red by a L isp printe r for this type of da ta can becom e hard for a program m er to grok.

Page 128: Land of Lisp - Barski M.D., Conrad

Assoc iation Lists

One part icula rly use ful da ta struc ture tha t can be c rea ted out of cons ce l ls is an assoc iation l ist , or alist for short . An a l istconsists of key/va lue pa irs stored in a l ist .

By convention, i f a key appears m ult iple t im es in the l ist , i t is a ssum ed tha t the first appearance of the key conta ins the desiredva lue . For instance , he re is an a l ist representing an order for coffee drinks placed by Bil l , L isa , and John:(defparameter *drink-order* '((bill . double-espresso)

(lisa . small-drip-coffee)(john . medium-latte)))

T o look up the order for a given person, use the func tion assoc:> (assoc 'lisa *drink-order*)

(LISA . SMALL-DRIP-COFFEE)T his func tion sea rches the l ist from the beginning for the desired key, and then re turns the key/va lue pa ir. Now suppose tha t ,

be fore picking up the drink order, L isa flags you down and opts to change her order to som ething sl ightly m ore decadent. You canchange her order using the push func tion:> (push '(lisa . large-mocha-with-whipped-cream) *drink-order*)

((LISA . LARGE-MOCHA-WITH-WHIPPED-CREAM)(BILL . DOUBLE-ESPRESSO)(LISA . SMALL-DRIP-COFFEE)(JOHN . MEDIUM-LATTE))

T his func tion sim ply adds a new i tem to the front of an exist ing l ist .Because , by default , the first re fe rence to a key in an assoc ia t ion l ist takes precedence over la te r re fe rences to the sam e key, the

order L isa placed for a sm all drip coffee is superseded by her m ore recent order:> (assoc 'lisa *drink-order*)

(LISA . LARGE-MOCHA-WITH-WHIPPED-CREAM)As you can see , a l ists a re a grea t way to keep track of any changeable collec t ion of key/va lue pa irs. Alists a re easy to

understand, to m anipula te with L isp func tions, and to com prehend when printed out (they’re just l ists of pa irs, a fte r a l l).Furtherm ore , once a va lue is stored in an a l ist , i t rem ains the re forever, m aking i t easy to audit the history of any da ta . For

instance , in our coffee exam ple , the order L isa placed for he r drip coffee is st i l l ava ilable even a fte r i t has been replaced.However, a l ists do have one se rious l im ita t ion: T hey a re not a ve ry e ffic ient way to store and re trieve da ta , unless you’re dea ling

with very short l ists (under a dozen i tem s). Because of this ine ffic iency, a l though a l ists a re often one of the first tools in the L ispprogram m er’s toolbox, they m ay be replaced by other types of da ta struc tures as a program m atures. (In Chapte r 9, we’l l discuss theperform ance l im ita t ions of l ist-based da ta struc tures, such as a l ists, in grea te r de ta i l . )

Page 129: Land of Lisp - Barski M.D., Conrad

Coping with Complicated Data

Cons ce lls a re a grea t tool for representing a wide varie ty of l ist-l ike struc tures. In fac t , m ost L isp program m ers, when faced witha program m ing task tha t is not bound by perform ance constra ints, wil l re ly on them a lm ost exc lusive ly. Because the m anipula t ionand visua liza t ion of struc tures m ade of cons ce l ls a re centra l to the design of L isp, these struc tures a re extrem ely convenient to useand debug.

In fac t , even if you do have perform ance constra ints, struc tures m ade of cons ce l ls can often be a grea t choice . A L isp com pile rcan often reduce a change to a cons ce l l down to a single assem bly instruc tion!

Page 130: Land of Lisp - Barski M.D., Conrad

Visualiz ing Tree-l ike Data

As discussed in Chapte r 3, the da ta (and code) in a L isp program is represented with syntax expressions. In this form at, da ta isrepresented using nested l ists, often with L isp sym bols a t the front of each l ist expla ining the struc ture of the da ta .

For exam ple , suppose we wanted to represent the com ponent pa rts of a house in L isp:(defparameter *house* '((walls (mortar (cement)

(water)(sand))(bricks))

(windows (glass)(frame)

(curtains))(roof (shingles)(chimney))))

T his da ta struc ture very e legantly captures the hie ra rchica l na ture of the parts tha t m ake up a house . Since i t is struc tured as aL isp syntax expression, we can see the l ists tha t m ake up the leve ls of the hie ra rchy. Also, i t fol lows the convention of a syntaxexpression by putt ing a sym bol a t the front of each l ist . For instance , we can see how the l ist desc ribing the windows first conta ins

the L isp sym bol windows , which is then followed by three i tem s, representing the glass, fram e, and fina lly the curta ins .As you can see , da ta tha t is hie ra rchica l and tree -l ike in na ture can be very na tura l ly expressed in this way. In fac t , m any

L ispers consider XML (a popula r form at for representing hie ra rchica l da ta ) som ewhat of a re invention of the syntax expressionform at tha t L isp pioneered.

If, however, we m ove beyond tree -l ike struc tures, da ta stored in a syntax expression can sta rt becom ing hard to visua lize , even ifi t ’s re la t ive ly easy to store the da ta in cons ce l ls. For instance , suppose we have a m athem atica l graph stored in a syntaxexpression. T hese types of graphs, where any a rbitra ry node of the graph m ay be connec ted to another by an edge , a re notoriouslyhard to visua lize in a com pute r program . E ven L isp’s e legant system for representing cons ce l ls can’t he lp m uch for such da ta .Next, we’l l look a t our options for visua liz ing such graphs.

Page 131: Land of Lisp - Barski M.D., Conrad

Visualiz ing G raphs

In m athem atics, a graph consists of a bunch of nodes connec ted by edges. T hese nodes or edges m ight have addit iona l da taassoc ia ted with them .

Such graphs can be stored in cons ce l ls, but they a re difficult to visua lize . W e saw this in Chapte r 5, when we stored the m ap ofthe wizard’s house (which consisted of a direc ted graph) in two a l ists: one conta ining the node inform ation and one conta ining theedge inform ation. I’ve renam ed them *wizard-nodes* and *wizard-edges* for this chapte r, a s shown here :(defparameter *wizard-nodes* '((living-room (you are in the living-room.

a wizard is snoring loudly on the couch.))(garden (you are in a beautiful garden.there is a well in front of you.))(attic (you are in the attic. thereis a giant welding torch in the corner.))))(defparameter *wizard-edges* '((living-room (garden west door)(attic upstairs ladder))(garden (living-room east door))(attic (living-room downstairs ladder))))

As you can see , i t is ha rd to ge t a decent understanding of the struc ture of this gam e world from these raw da ta tables.Unfortuna te ly, da ta tha t has the shape of a graph or conta ins other propert ies tha t go beyond sim ple tree struc tures a re verycom m on. W ouldn’t i t be grea t i f we had a tool tha t could optim ally a rrange this da ta to c rea te a pre t ty drawing of a graph?L uckily, the re is a fantast ic open source tool tha t pe rform s exac tly this ta sk, which you’l l t ry out next .

Page 132: Land of Lisp - Barski M.D., Conrad

Creating a G raph

Graphviz genera tes graphs from your da ta . Indeed, you saw a sim ple Graphviz representa t ion of the wizard’s house in Chapte r 5:

Graphviz is open source and ava ilable from the Graphviz website (ht tp: / /www.graphviz .org/). Afte r you’ve downloaded andinsta l led i t , c rea t ing a graph is easy. First , you’l l c rea te a DOT fi le tha t desc ribes the shape of your graph. For exam ple , inGraphviz , c rea te a fi le nam ed te st . dot on your com pute r and ente r the fol lowing inform ation:digraph {

a->b;}

T his defines a direc ted graph with nodes A and B connec ted by an a rrow. (T here a re num erous syntax options ava ilable in theDOT fi le form at, a s docum ented a t the Graphviz website . )

Now, to genera te a graphica l bi tm ap from the DOT fi le , run neato (one of the Graphviz ut i l i t ie s) from the com m and l ine , a sfollows:neato -Tpng -O test.dotT his should c rea te a pic ture in the fi le te st . dot . png tha t looks l ike this:

As you can see , Graphviz is sim ple to use . It can even genera te la rge , com plica ted graphs quickly, with only m inor graphica lgli tches. (Since perfec t graph layouts a re st i l l an unsolved problem in com pute r sc ience , Graphviz layouts a ren’t pe rfec t . T hey a re ,however, c lose r to perfec t than you m ight expec t . )

Now tha t you have Graphviz up and running, le t’s c rea te a l ibra ry of com m ands tha t wil l le t us conveniently draw graphs withL isp. W e can use this to draw som e graphs of our adventure gam e world.

Note

T he graph uti l i t ie s used in the exam ples in this chapte r pe rform certa in system ca lls in a way tha t is not pa rt of the Com m onL isp standard. T hey a re ava ilable only in the CL ISP environm ent. T he code would require som e m odifica t ions to run within other

Page 133: Land of Lisp - Barski M.D., Conrad

L isp system s.

Page 134: Land of Lisp - Barski M.D., Conrad

G enerating the DO T Information

In order to c rea te a graph drawing l ibra ry, we want to genera te a Graphviz DOT fi le tha t captures a l l the de ta i ls of a graph. T odo this, we wil l need to convert the identifie rs of the nodes the player can visi t , convert the edges connec ting these nodes, andgenera te labe ls for every node and edge . W e wil l te st our l ibra ry using the nodes representing the m ap of the wizard’s world.

Converting Node Identifiers

W hen convert ing nodes into DOT form at, the first thing we need to do is to convert the node identifie rs into va lid DOTidentifie rs. W e do this by writ ing a dot-name func tion:(defun dot-name (exp)

(substitute-if #\_ (complement #'alphanumericp) (prin1-to-string exp)))A node in DOT form at can conta in only le t te rs, digi ts, and the underscore charac te r. T o m ake sure the node identifie r we’re

using is lega l , we’l l change any forbidden charac te rs to underscores. Here a re exam ples of the dot-name func tion in use :> (dot-name 'living-room)

"LIVING_ROOM"> (dot-name 'foo!)"FOO_"> (dot-name '24)"24"

T his func tion accepts any basic L isp type , which we can then convert to a string using the prin1-to-string func tion. W e canprocess the result ing string and substi tute underscores as needed.

Note

For the sake of sim plic i ty, our dot-name func tion assum es tha t no node identifie rs diffe r only in the ir nona lphanum ericcom ponents. For instance , i f we had one node ca lled foo? and another node ca lled foo*, the dot-name func tion would convertthem both to foo, causing the nam es to c lash.

T he substitute-if func tion substi tutes va lues based on the result of a test func tion:> (substitute-if #\e #'digit-char-p "I'm a l33t hack3r!")

"I'm a leet hacker!"T he test func tion in this exam ple , digit-char-p, te l ls us if a charac te r in a string is a num erica l digi t . T est func tions l ike this,

which accept a va lue and de te rm ine truth based on tha t va lue , a re often re fe rred to as predicates.Another inte rest ing property of the substitute-if func tion is tha t we can use i t on l ists as well :> (substitute-if 0 #'oddp '(1 2 3 4 5 6 7 8))

'(0 2 0 4 0 6 0 8)Here , a l l odd num bers in a l ist have been replaced by the num ber 0. T he substitute-if func tion is one exam ple of a generic

func tion—a func tion tha t can accept m ult iple da ta types as param ete rs and handle them appropria te ly. (Generic program m ing isdiscussed in Chapte r 9. )

W hen we use substitute-if in our dot-name func tion, we substi tute only those charac te rs tha t a ren’t a lphanum eric . W hile nopredica te tha t te sts for exac tly this is ava ilable for us in Com m on L isp, i t is easy to c rea te this predica te on the fly. T he followingfragm ent in the dot-name func tion c rea tes a predica te func tion for us with exac tly the right behavior:(complement #'alphanumericp)L isp a lready has a predica te func tion tha t te l ls us if a charac te r is a lphanum eric , ca l led alphanumericp. However, we want to

substi tute only charac te rs tha t a re not a lphanum eric . W e can c rea te this opposite (or complement) func tion to alphanumericp bypassing i t to a higher-order func tion nam ed complement.

By passing this func tion into substitute-if, we ge t the behavior we want, without needing to use defun to pollute the top leve lwith a new func tion just to feed to substitute-if.

Note

Com m on L isp has a func tion ca lled substitute-if-not tha t could have been used in the dot-name func tion in l ieu ofsubstitute-if to a l low us to leave the not out of the lambda func tion. However, L isp func tions tha t end in not a re be tte r avoided.T hey m ay be rem oved from future versions in the ANSI Com m on L isp standard, which m eans they a re considered depreca ted.

Adding Labels to G raph Nodes

Now tha t we can tweak our node identifie rs to m ake them appropria te for DOT , le t’s write another func tion tha t wil l genera tethe labe l tha t should appear in the node when i t is drawn. T he labe l wil l consist of the node nam e and the da ta l inked to the nodein the node a l ist . But we a lso need to m ake sure tha t we a re not t rying to put too m uch text in the labe l . Here is the code tha tgenera tes the labe l:

(defparameter *max-label-length* 30)

(defun dot-label (exp)(if exp

(let ((s (write-to-string exp :pretty nil)))

(if (> (length s) *max-label-length*)

(concatenate 'string (subseq s 0 (- *max-label-length* 3)) "...")

Page 135: Land of Lisp - Barski M.D., Conrad

s))""))

*max-label-length* is a globa l va riable tha t de te rm ines the m axim um num ber of charac te rs for the labe l . If a node labe l

is la rger than the l im it , i t ge ts c ropped, and an e l l ipsis is added to indica te tha t fac t . T he write-to-string func tion

is sim ila r to the prin1-to-string func tion we used ea rl ie r—it writes an expression to a string.T he :pretty pa ram ete r is an exam ple of a keyword parameter, which is used by ce rta in L isp func tions to le t you choose which

param ete rs you want to pass in. In the case of write-to-string, i t te l ls L isp not to a l te r the string to m ake i t pre t ty. W ithout this,L isp would place new l ines or tabs into our converted string to m ake i t look m ore pleasing to the eye . By se t t ing the :prettykeyword param ete r to nil, we a re te l l ing L isp to output the expression without any decora tions. (Having new l ines in a labe l canconfuse Graphviz , so we don’t want to give L isp any ideas. )

G enerating the DO T Information for the Nodes

Now tha t we can genera te both a nam e and labe l for each node , we can write a func tion tha t takes an a l ist of nodes andgenera tes the DOT inform ation tha t encodes them , l ike so:(defun nodes->dot (nodes)

(mapc (lambda (node)(fresh-line)

(princ (dot-name (car node)))(princ "[label=\"")

(princ (dot-label node))(princ "\"];"))nodes))

T his func tion uses mapc to go through every node in the l ist of nodes , and princ prints each node in the DOT form atdirec t ly to the sc reen. mapc is a sl ightly m ore e ffic ient va riant of mapcar; the diffe rence is tha t i t does not re turn the transform ed

list . T he nodes->dot func tion uses the dot-name and dot-label func tions we c rea ted to convert the da ta .L a te r, when we want to genera te a fi le tha t conta ins this inform ation, we’l l wri te a func tion tha t takes this da ta from the

console .It m ay seem a bi t odd to use the console as an inte rm edia ry for genera t ing a fi le , instead of just wri t ing direc t ly into a fi le , but

this is ac tua lly a com m on paradigm in L isp. One im m edia te benefi t of this approach is tha t we can easi ly debug the code in theRE PL , where the printed l ines a re easy to see .

Now le t’s t ry using the nodes->dot func tion to genera te the DOT inform ation for the nodes in the wizard’s house :> (nodes->dot *wizard-nodes*)

LIVING_ROOM[label="(LIVING-ROOM (YOU ARE IN TH..."];GARDEN[label="(GARDEN (YOU ARE IN A BEAUT..."];ATTIC[label="(ATTIC (YOU ARE IN THE ATTI..."];

Here , you can see the nodes of the wizard’s house and an abbrevia ted version of the inform ation a t tached to each node , shown inDOT form at. Notice tha t we a re not inte rested in the va lue re turned from the nodes->dot func tion—only in the inform ation i tprints in the RE PL . L ispers would say tha t we a re only inte rested in the side e f fec ts of this func tion. Although mapc does not re turnthe l ist , i t st i l l causes the code to i te ra te through the l ist and genera te the sam e printed output tha t using mapcar would have , so i tgenera tes the sam e side e ffec ts as mapcar, a bi t m ore quickly.

Converting Edges into DO T F ormat

T he next step is to genera te the DOT inform ation for the edges tha t l ink our nodes. T hese wil l becom e the a rrows in our visua lgraph. T he func tion edges->dot genera tes the necessa ry da ta , aga in by print ing i t direc t ly to the console .(defun edges->dot (edges)

(mapc (lambda (node)(mapc (lambda (edge)(fresh-line)(princ (dot-name (car node)))(princ "->")(princ (dot-name (car edge)))(princ "[label=\"")(princ (dot-label (cdr edge)))(princ "\"];"))(cdr node)))edges))

L et’s use this func tion to genera te the DOT inform ation for the edges of the wizard’s house :> (edges->dot *wizard-edges*)

LIVING_ROOM->GARDEN[label="(WEST DOOR)"];LIVING_ROOM->ATTIC[label="(UPSTAIRS LADDER)"];GARDEN->LIVING_ROOM[label="(EAST DOOR)"];ATTIC->LIVING_ROOM[label="(DOWNSTAIRS LADDER)"];

Here , we can c lea rly see the re la t ionships be tween the nodes in the wizard’s house , in the DOT form at. For instance , the first

Page 136: Land of Lisp - Barski M.D., Conrad

l ine indica tes tha t the player can walk from the LIVING_ROOM node to the GARDEN node by using an edge labe led (WESTDOOR).

G enerating All the DO T Data

T o com ple te our genera t ion of the DOT da ta , we ca ll both nodes->dot and edges->dot, and wrap i t up with som e extradecora tion, as fol lows:(defun graph->dot (nodes edges)

(princ "digraph{")

(nodes->dot nodes)

(edges->dot edges)(princ "}"))

T his func tion t ies everything toge ther by defining our graph as a direc t iona l graph , and then ca ll ing our nodes->dot

and edges->dot func tions.Here’s wha t the fina l DOT inform ation for our wizard gam e looks l ike , a s c rea ted by our new l ibra ry:> (graph->dot *wizard-nodes* *wizard-edges*)

digraph{LIVING_ROOM[label="(LIVING-ROOM (YOU ARE IN TH..."];GARDEN[label="(GARDEN (YOU ARE IN A BEAUT..."];ATTIC[label="(ATTIC (YOU ARE IN THE ATTI..."];LIVING_ROOM->GARDEN[label="(WEST DOOR)"];LIVING_ROOM->ATTIC[label="(UPSTAIRS LADDER)"];GARDEN->LIVING_ROOM[label="(EAST DOOR)"];ATTIC->LIVING_ROOM[label="(DOWNSTAIRS LADDER)"];}

W e can now genera te a proper Graphviz DOT fi le tha t captures a l l the de ta i ls of our wizard m ap tha t we need to genera te apre t ty pic ture . T hese inc lude the nodes the player can visi t , the edges connec ting these nodes, and labe ls for every node and edge .

Page 137: Land of Lisp - Barski M.D., Conrad

Turning the DO T F ile into a P ic ture

T o turn the DOT fi le into an ac tua l bi tm ap, we capture the DOT fi le da ta , put i t into a fi le , and then execute the dot com m anddirec tly from the system com m and l ine , l ike this:

(defun dot->png (fname thunk)

(with-open-file (*standard-output*fname:direction :output:if-exists :supersede)(funcall thunk))(ext:shell (concatenate 'string "dot -Tpng -O " fname)))

T his func tion perform s the m ost c ri t ica l ac t ions in our graph drawing l ibra ry, using som e advanced L isp techniques.First , to keep this dot->png func tion as reusable as possible , the graph->dot func tion isn’t ca l led direc t ly. Instead, we write dot-

>png to accept a thunk .

Using Thunks

It is com m on in L isp to c rea te sm all func tions tha t have ze ro a rgum ents. T hese func tions a re offic ia l ly ca l led nullary func tions.However, L ispers wil l often c rea te such func tions in order to describe a com puta tion tha t they don’t want to run unti l la te r. In thisscenario, a func tion without a rgum ents is com m only ca lled a thunk or a suspension. In this case , the thunk our dot->png func tionneeds would be a func tion tha t , when ca lled, prints a DOT fi le to the console .

W hy is a thunk useful in our dot->png func tion? Rem em ber tha t the easiest way for us to write and debug graph->dot and otherDOT fi le func tions is to have them print the ir results direc t ly to the console . W hen we ca ll graph->dot, i t doesn’t re turn i ts resultsas a va lue , but , instead, prints them a t the console as a side e ffec t . T here fore , we can’t just pass the va lue of graph->dot to dot->png. Instead, we pass in graph->dot a s a thunk. T hen dot->png is responsible for ca l l ing graph->dot, capturing the results, andsending them to a fi le .

Since i t is so com m on to genera te textua l da ta with a com pute r program , this pa rt icula r technique is used a lot in L isp code :First , we print stuff right to the console ; next , we wrap i t in a thunk; fina lly, we redirec t the results to som e other loca tion.

As you’l l see in Chapte r 14, L ispers who follow the func tiona l program m ing style eschew this technique , because side e ffec ts a rerequired when print ing to the console .

Writing to a F ile

T he func tion with-open-file enables dot->png to write inform ation to a fi le . T o give you a fee l for how this func tionworks, he re ’s an exam ple tha t c rea tes a new fi le nam ed te st f i le . tx t and writes the text “Hello File !” to i t :

(with-open-file (my-stream"testfile.txt":direction :output:if-exists :supersede)(princ "Hello File!" my-stream))

In this exam ple , you can see tha t the first i tem passed into with-open-file becom es the nam e of a spec ia l Com m on L ispda ta type ca l led a stream, which is c rea ted for us by with-open-file.

Creating a Stream

Printing func tions, such as princ, can accept a stream as an optiona l pa ram ete r. In this case , these print ing func tions won’t printanything to the console , but instead wil l print to the stream objec t .

It is im portant to understand tha t with-open-file c rea tes a stream variable from a stream variable nam e, in the sam e way tha tlet c rea tes a va riable from a variable nam e:

(with-open-file (my-stream ...)

...body has my-stream defined...)

(let ((my-variable ...))

...body has my-variable defined...)

So if we pass the nam e my-stream in a t the front of the first l ist to with-open-file , this is ana logous to defining my-

variable a t the sta rt of a let . A variable nam ed my-stream wil l be ava ilable to us in the body of with-open-file , in

the sam e way tha t my-variable wil l be ava ilable to us in the body of the let .But don’t worry too m uch about exac tly wha t a stream is just ye t . W e’ll be looking a t them m ore c lose ly in Chapte r 12. For

now, you just need to know tha t a stream is an objec t tha t can be connec ted to a fi le , and we can pass i t to func tions (such asprinc) to write stuff to the connec ted fi le .

Page 138: Land of Lisp - Barski M.D., Conrad

Understanding K eyword P arameters

T h e with-open-file com m and a lso m akes heavy use of keyword param ete rs. L e t’s look a t our previous exam ple of thiscom m and aga in:(with-open-file (my-stream

"testfile.txt"

:direction :output

:if-exists :supersede)(princ "Hello File!" my-stream))

A keyword param ete r has two parts: the nam e of the param ete r and the va lue of the param ete r. T he nam e of the param ete r is

a lways a sym bol beginning with a colon. T his exam ple has two keyword param ete rs: :direction , which is se t to :output

(we’re only writ ing to the fi le and not reading i t), and :if-exists , which is se t to :superseded (i f a fi le by tha t nam ealready exists, just toss out the old version).with-open-file has keyword param ete rs because opening a fi le is a com plex opera t ion, and m any esote ric options a re ava ilable .

If with-open-file just gave you regula r pa ram ete rs to se t a l l this, every ca l l to with-open-file would be long and cum bersom edue to a l l the param ete rs. Also, hum ans have a hard t im e looking a t a long l ist of pa ram ete rs and rem em bering which one doeswhat.

As you’ve probably noticed, sym bols in Com m on L isp som etim es begin with a colon. T his inc ludes keyword param ete rs, whicha lways sta rt with a colon. T his is because a regula r sym bol in L isp can re fe r to som ething e lse . For instance , we could se t avariable cigar equa l to 5 and then re turn i t :> (let ((cigar 5))

cigar)5

However, som etim es we don’t want a sym bol to re fe r to som ething e lse . W e want to use the sym bol outright , and we want i t tohave i ts own m eaning. A colon-prepended sym bol in Com m on L isp (not surprisingly, ca l led a keyword symbol) a lways m eans i tse lf:

> :cigar:CIGAR

> (let ((:cigar 5)):cigar)*** - LET: :CIGAR is a constant, may not be used as a variable

As you can see , the keyword sym bol :cigar can be eva lua ted right a t the RE PL and a lready has a va lue . Its va lue is,

conveniently, :cigar. If we try to redefine :cigar to som ething e lse , Com m on L isp won’t le t us . T he fac t tha t i t is constantis use ful , because a L isp com pile r can potentia l ly optim ize this sim ple type of sym bol m ore than i t can optim ize other types. Also,we can reduce e rrors in our code by using keyword sym bols in places where we know a sym bol just has a m eaning in i ts own right .Som etim es a c igar is just a c igar.

Captur ing the Console O utput

Our dot->png sends our da ta to the fi le in a sl ightly diffe rent way than is shown in this exam ple : by dec la ring the nam e of thestream to be *standard-output* (a spec ia l globa l va riable in Com m on L isp tha t controls the default loca tion to which print ingfunc tions send the ir output). As a result , any print ing done inside the thunk wil l be redirec ted to our DOT fi le .

L e t’s look a t our dot->png func tion aga in to see this:(defun dot->png (fname thunk)

(with-open-file (*standard-output*fname:direction :output:if-exists :supersede)

(funcall thunk))(ext:shell (concatenate 'string "dot -Tpng -O " fname)))

So how exac tly does the dot->png func tion cause our DOT da ta to ge t saved to a fi le instead of just going to the console? T oanswer this, you’l l need to exerc ise your bra in a bi t . Also, you’l l need to reca ll our discussion of loca l and dynam ic variables inChapte r 2.

Rem em ber tha t the le t com m and usua lly c rea tes a lex ical , or loca l , va riable . As we’ve discussed, the stream variable c rea ted bywith-open-file is ana logous to using let to c rea te a va riable . Hence , i t usua lly leads to the c rea tion of a lexica l stream variablefor us.

However, i f a dynam ic variable a lready exists with the sam e nam e, let wil l instead, tem porari ly, override the va lue of thedynam ic variable to the new va lue . *standard-output* is such a dynam ic variable . T his m eans tha t we can tem porari ly override

the va lue of *standard-output* to a new va lue by passing i t into our with-open-file com m and .

In the body of the with-open-file, where we ca ll our thunk , any va lues printed to the console wil l now be autom agica llyrouted to our fi le , instead. T he surprising thing (enabled by the design of lexica l and dynam ic variables in Com m on L isp) is tha tthis is a lso true for the princ sta tem ents in our graph->dot func tion, even though they a re ca l led indirec t ly from dot->png.

Page 139: Land of Lisp - Barski M.D., Conrad

Creating a P ic ture of O ur G raph

L astly, we need a func tion tha t t ie s toge ther a l l the pieces to le t us easi ly c rea te a graph from som e nodes and edges:

(defun graph->png (fname nodes edges)

(dot->png fname

(lambda ()

(graph->dot nodes edges))))

T his func tion takes the nam e of a DOT fi le (as the variable fname), a s we ll a s the graph’s nodes and edges , and uses them

to genera te the graph. T o do this, i t ca l ls dot->png and c rea tes the appropria te thunk—a lambda func tion . As is usua lfor a thunk, i t takes no param ete rs.

T he graph->dot func tion is ca l led inside the thunk as a de layed computation. Spec ifica l ly, i f we had ca lled graph->dotdirec tly, i ts output would just show up in the console . However, when inside the thunk, i t wil l be ca l led a t the le isure of the dot->png func tion, and the output wil l be used to genera te the DOT fi le with the fi lenam e passed in as the first pa ram ete r to graph->png.

L e t’s t ry out our new func tion to draw a graph of the wizard’s house!(graph->png "wizard.dot" *wizard-nodes* *wizard-edges*)Afte r ca l l ing this func tion, you should now see a fi le nam ed wizard.dot . png, a pic ture of the m ap of the wizard’s house :

T his m ay not be the pre t t ie st graph on the plane t , but i t ’s packed with inform ation and is ve ry easy to understand. Also, the codeis extrem ely flexible , and places few dependenc ies on our node and edge da ta .

W ith these ut i l i t ie s in our a rsena l , we can now easi ly c rea te graphs from any inte rconnec ted da ta in our L isp program s. You’l lfind this technique to be a va luable debugging tool when you need to dea l with com plica ted da ta .

Page 140: Land of Lisp - Barski M.D., Conrad

Creating Undirected G raphs

A graph tha t has a rrows on i ts edges is ca l led a direc ted graph:

But som etim es we have da ta tha t is undirec ted, a l lowing us to trave l in both direc t ions a long an edge . Such a graph is le ss busythan a direc ted graph, and can be easie r to understand:

Page 141: Land of Lisp - Barski M.D., Conrad

T he following code expands our graph uti l i t ie s with new func tions tha t le t us draw undirec ted graphs:(defun uedges->dot (edges)

(maplist (lambda (lst)(mapc (lambda (edge)

(unless (assoc (car edge) (cdr lst))(fresh-line)(princ (dot-name (caar lst)))(princ "--")(princ (dot-name (car edge)))(princ "[label=\"")(princ (dot-label (cdr edge)))(princ "\"];")))(cdar lst)))edges))

(defun ugraph->dot (nodes edges)

(princ "graph{")(nodes->dot nodes)(uedges->dot edges)(princ "}"))

(defun ugraph->png (fname nodes edges)(dot->png fname

Page 142: Land of Lisp - Barski M.D., Conrad

(lambda ()(ugraph->dot nodes edges))))

T his code is ve ry sim ila r to the code for c rea ting our direc ted graphs. L e t’s look a t som e of the diffe rences.

T h e uedges->dot func tion is ve ry sim ila r to the edges->dot func tion . However, the graph we’re drawing m ay havem ult iple direc ted edges be tween the sam e nodes tha t we want to replace with a single , undirec ted edge . For instance , on ourwizard m ap, we can walk from the garden to the l iving room by going east through the door. Of course , we can a lso walk from theliving room to the garden by going west through the exac t sam e door. In our undirec ted graph, we’l l want to collapse this; inessence , we just want to say, “T here ’s a door be tween the garden and l iving room .”

T he uedges->dot func tion e rases such duplica te edges by running through the l ist of edges using the maplist func tion. T his isl ike the mapcar func tion, except tha t the func tion inside i t rece ives the entire rem ainder of the l ist , not just the current i tem in thelist :> (mapcar #'print '(a b c))

ABC...> (maplist #'print '(a b c))(A B C)(B C)(C)...

T he maplist func tion sends the print func tion everything in the l ist from the current i tem unti l the end. uedges->dot then uses the inform ation about future nodes i t ge ts from maplist to check whether the dest ina tion of the node appears la te r in theedge l ist . T he ac tua l checking is done with the assoc func tion, looking for the current edge in the l ist of rem aining edges,

ca lcula ted as (cdr lst) . In this case , i t skips the edge so tha t only one of any pa ir of edges wil l be printed.

T he ugraph->dot func tion is sim ila r to the graph->dot func tion, except tha t i t desc ribes the graph as just a graph

when genera t ing the DOT da ta , instead of a digraph. T he ugraph->png func tion is essentia l ly identica l to the graph->pngfunc tion, except tha t i t ca l ls ugraph->dot instead of graph->dot.

W e designed the dot->png func tion to accept diffe rent thunks so i t could work with diffe rent DOT da ta genera tors. Now we’veused this flexibil i ty to genera te these func tions tha t output pic tures for undirec ted graphs. For exam ple , le t’s t ry genera t ing anundirec ted graph for the wizard’s house :(ugraph->png "uwizard.dot" *wizard-nodes* *wizard-edges*)He re , "uwizard.dot" is the nam e of the DOT fi le we want to c rea te . T he *wizard-nodes* and *wizard-edges* va riables

conta in the da ta describing the nodes and edges of the m ap of the wizard’s world. T his code genera tes the uwizard.dot . png fi le ,which looks l ike this:

Now tha t you have a ful l sui te of ut i l i t ie s for both direc ted and undirec ted graphs, wri te these func tions to a fi le nam ed graph-uti l . l isp, so you can access them from other program s.

Page 143: Land of Lisp - Barski M.D., Conrad

What You've Learned

In this chapte r, we discussed exotic types of l ists and c rea ted a drawing l ibra ry for m athem atica l graphs. Along the way, youlearned the fol lowing:

You can c rea te l ists in L isp tha t end in a va lue other than nil . Such l ists a re displayed with an extra dot be fore the last i temand a re ca l led dotted l ists.

P airs a re wha t you ge t when you cons toge ther two i tem s tha t a re not l ists them se lves. T hey can a lso be thought of as dottedlists tha t conta in only two i tem s.

Circular l ists a re l ists where the last cons ce l l points to an ea rl ie r cons ce l l in the sam e l ist .A ssoc iation l ists (al ists) a re l ists of pa irs. T hey can be used to store da ta tha t is in the form of keys assoc ia ted with va lues.L isp syntax expressions a re grea t for storing and visua liz ing l ist-l ike and hie ra rchica l da ta . E xtra tools m ay be he lpful for

visua liz ing m ore com plex da ta .If your da ta is in the form of a m athem atica l graph, i t ’s he lpful to be able to genera te pic tures of your da ta using Graphviz .A com m on technique for genera t ing textua l da ta in a L isp program is to write func tions tha t print the text to the console for

easy debugging and wrap these func tions in thunks. T hen you can send these thunks to other func tions, which capture the consoleoutput and route the text to the appropria te dest ina tion, such as writ ing i t to a fi le .

Page 144: Land of Lisp - Barski M.D., Conrad

Chapter 8. This Ain't Your Daddy's Wumpus

In the previous chapte r, we worked with m athem atica l graphs in a sim ple gam e. However, a s an old-school geek, the first thing Ithink of when I see these graphs is the old gam e Hunt the W um pus. W hen I was nine , I could think of nothing m ore fun than si t t ingin front of m y T I-99/4A and playing this exce llent gam e.

Here is the origina l t i t le sc reen:In Hunt the W um pus, you a re a hunte r sea rching through a ne twork of caves to find a m yste rious m onste r—the fabled W um pus.

Along the way, you a lso dea l with ba ts and ta r pi ts. Ah, those were the days!

But, unfortuna te ly, those days a re long gone . W e’re in a new m illennium now, and no one would be im pressed by these c rudegraphics anym ore . And the story l ine , we ll , le t’s just say i t sounds a bi t corny by m odern standards. I think we can a l l agree tha tHunt the W um pus is in se rious need of a m akeover. T ha t’s quite a cha llenge , but one I think we can handle .

T here fore , I present to you . . .

Page 145: Land of Lisp - Barski M.D., Conrad
Page 146: Land of Lisp - Barski M.D., Conrad

The G rand Theft Wumpus G ame

In this new version of Hunt the W um pus, you a re the L isp a l ien. You and the W um pus have just robbed a l iquor store and m adeoff with the loot . However, during the escape , the W um pus dec ides to double -c ross you and run off with the m oney and your ca r.But before he drives off, you m anage to cap him a couple of t im es in the kidney.

Now you’re in a pre t ty tough si tua tion. You don’t have a ca r or any m oney, and no way to track down your form er partner inc rim e . But you a lso have no choice . You have your princ iples, so you’re going to hunt the Wumpus. You know he won’t be able toge t ve ry fa r with his injuries. He wil l m ost l ike ly need to l ie low for a few days to recover, which m eans he wil l st i l l be som ewherein Congestion City. T he problem is tha t the roads in this town a re im possibly convoluted, and no one can find the ir way a round,espec ia l ly an out-of-towner l ike yourse lf. How are you ever going to find the W um pus in this im possible m aze?

Page 147: Land of Lisp - Barski M.D., Conrad

L uckily, be ing the L isp a l ien, you a lways ca rry your trusty pocke t com pute r. Using L isp and your graph uti l i t ie s, you’re ful lyequipped to ana lyze com plica ted da ta such as the Congestion City roadways and inte rsec tions. Sure ly, you have the tools toconquer this im pene trable road system .

T he W um pus has been your partner in c rim e for a while now, so you know his MO quite well . He wil l a lways ca re fully scout outany new hiding place before he uses i t . And since he is injured, any loca tion one or two blocks away (tha t is, one or two graphedges away) from his hiding place should be m arked with som e te l l ta le signs: his blood sta ins.

A problem is tha t he st i l l has his t rusty AK-47, while you have only a handgun with a single bulle t . If you’re going to take him

Page 148: Land of Lisp - Barski M.D., Conrad

out, you’l l need to be absolute ly sure you’ve tracked him down. You’l l need to charge into his hideout and shoot him downim m edia te ly, and you’l l have only one chance to pull this off.

Unfortuna te ly, you and the W um pus a ren’t the only c rim ina ls in this town. T he m ost fea red outlaw group in Congestion City isthe Gruesom e Glowworm Gang. T hese guys a re a band of ruthless kidnappers. If you run into them , they wil l kidnap you, bea t youup, rob you, bl indfold you, and then kick you out of the ir ca r and leave you in som e random part of town.

L uckily, they can be avoided if you know to keep an eye out for the ir glowing thoraxes (hence the ir nam e). If you see som eblinking l ights, you know tha t these guys a re one stree t away from your current loca tion. Also, you know the gang has exac tly threesepara te team s tha t work the c i ty from three separa te loca tions.

Page 149: Land of Lisp - Barski M.D., Conrad

Fina lly, you st i l l need to contend with the cops. You know they’ve probably se t up som e roadblocks in town to try to ca tch youand the W um pus. You should st i l l be able to visi t any loca tion in Congestion City, but you need to be ca re ful which stree ts youtrave l . (In other words, the cops wil l ca tch you if you trave l a long the wrong edge . ) Unfortuna te ly, you don’t know how m any ofthese roadblocks the re m ay be .

As you can see , finding the W um pus and ge tt ing back your m oney and ca r wil l be tough. If you think you’re L isp a l ien enough totake on the W um pus, then le t’s write this gam e and hunt him down!

Page 150: Land of Lisp - Barski M.D., Conrad

Defining the Edges of Congestion City

T he m ap of Congestion City wil l be an undirec ted graph with da ta assoc ia ted with each node stored in the variable*congestion-city-nodes*. T he possible da ta a t each node wil l inc lude the presence of the W um pus, a Glowworm team , andvarious danger signs.

A se t of edges stored in *congestion-city-edges* wil l connec t the nodes, and da ta l inked to these edges wil l a le rt us to thepresence of any police roadblocks. W e dec la re these and other globa l va riables a t the top of our program using defparameter:

(load "graph-util")

(defparameter *congestion-city-nodes* nil)(defparameter *congestion-city-edges* nil)(defparameter *visited-nodes* nil)

(defparameter *node-num* 30)

(defparameter *edge-num* 45)

(defparameter *worm-num* 3)

(defparameter *cop-odds* 15)

W e first load our graph uti l i t ie s with the load com m and , which eva lua tes a l l the code in graph-uti l . l isp (which we c rea ted

in the previous chapte r) so the graph uti l i ty func tions wil l be ava ilable . Notice tha t Congestion City wil l have 30 loca tions

(nodes, de fined with *node-num*), 45 edges (roads, de fined with *edge-num*), and 3 worm team s (defined with *worm-

num*). E ach stree t wil l have a 1-in-15 chance of conta ining a roadblock (defined with *cop-odds*).

Page 151: Land of Lisp - Barski M.D., Conrad

G enerating Random Edges

Next, we c rea te a random list of edges to connec t a l l the nodes:

(defun random-node ()(1+ (random *node-num*)))

(defun edge-pair (a b)(unless (eql a b)(list (cons a b) (cons b a))))

(defun make-edge-list ()

(apply #'append (loop repeat *edge-num*

collect (edge-pair (random-node) (random-node)))))

First , we dec la re the random-node func tion , which re turns a random node identifie r. It uses the random func tion, whichre turns a random na tura l num ber less than the integer you pass to i t . Since we’re going to be showing the node identifie rs in ouruser inte rface , we use the 1+ func tion to num ber our nodes 1 through 30 (the upper l im it because the *node-num* va riable is se t to30), instead of 0 through 29.

T he make-edge-list func tion genera tes the ac tua l l ist of random edges. It uses the loop com m and to loop *edge-num*

t im es , and then collec ts the requisi te num ber of edges . W e’ll take a c lose r look a t the loop com m and in the next

sec tion. T he graph of the c i ty is undirec ted, so this func tion uses a he lper func tion, edge-pair , to c rea te two direc ted edgesbe tween the random ly se lec ted nodes. T his extra step m akes sense once you rem em ber tha t an undirec ted graph is the sam e as adirec ted graph, with two opposing direc ted edges m irroring each undirec ted edge . (W hen we build our edges into an a l ist la te r inthis chapte r, this step wil l ensure tha t the l ist is properly form ed. )

L e t’s t ry the make-edge-list func tion in the CL ISP RE PL :> (make-edge-list)

((16 . 20) (20 . 16) (9 . 3) (3 . 9) (25 . 18) (18 . 25) (30 . 29)(29 . 30) (26 . 13) (13 . 26) (12 . 25) (25 . 12) (26 . 22) (22 . 26)(30 . 29) (29 . 30) (3 . 14) (14 . 3) (28 . 6) (6 . 28) (4 . 8) (8 . 4)(27 . 8) (8 . 27) (3 . 30) (30 . 3) (25 . 16) (16 . 25) (5 . 21) (21 . 5)(11 . 24) (24 . 11) (14 . 1) (1 . 14) (25 . 11) (11 . 25) (21 . 9) (9 . 21)(12 . 22) (22 . 12) (21 . 11) (11 . 21) (11 . 17) (17 . 11) (30 . 21) (21 . 30)(3 . 11) (11 . 3) (24 . 23) (23 . 24) (1 . 24) (24 . 1) (21 . 19) (19 . 21) (25 . 29)(29 . 25) (1 . 26) (26 . 1) (28 . 24) (24 . 28) (20 . 15) (15 . 20)(28 . 25) (25 . 28)(2 . 11) (11 . 2) (11 . 24) (24 . 11) (29 . 24) (24 . 29)(18 . 28) (28 . 18) (14 . 15)(15 . 14) (16 . 10) (10 . 16) (3 . 26) (26 . 3) (18 . 9) (9 . 18) (5 . 12)(12 . 5) (11 . 18) (18 . 11) (20 . 17) (17 . 20) (25 . 3) (3 . 25))

You see the pa irs of node num bers tha t m ake up the edges. T his l ist of edge pa irs wil l form the ske le ton of the Congestion Cityroad system .

Page 152: Land of Lisp - Barski M.D., Conrad

Looping with the loop Command

Our make-edge-list func tion em ploys the powerful loop com m and, which can be used to loop over various types of da ta . W e’llbe looking a t loop in de ta i l in Chapte r 10. However, our gam e uses loop a few t im es, so le t’s consider som e sim ple exam ples toc la rify how i t works.

One handy thing you can do with loop is c rea te a l ist of num bers. For instance , the fol lowing com m and wil l c rea te a l ist of 10ones:> (loop repeat 10

collect 1)(1 1 1 1 1 1 1 1 1 1)

W ithin the loop com m and, we spec ify how m any t im es to repeat, and then spec ify an objec t to collect with every loop (in thiscase , the num ber 1).

Som etim es, we want to keep a running count as we’re looping. W e can do this with the fol lowing syntax:> (loop for n from 1 to 10

collect n)(1 2 3 4 5 6 7 8 9 10)

In this exam ple , we a re saying tha t n should loop from 1 to 10. T hen we collect each n and re turn i t a s a l ist .Ac tua lly, we can put any L isp code in the collect pa rt of the loop. In the fol lowing exam ple , we add 100 as we do our

collec t ing:> (loop for n from 1 to 10

collect (+ 100 n))(101 102 103 104 105 106 107 108 109 110)

Page 153: Land of Lisp - Barski M.D., Conrad

P reventing Islands

W e now can genera te random edges. Of course , i f we just connec t random nodes with random edges, the re ’s no guarantee tha t a l lof Congestion City wil l be connec ted because of a l l tha t random ness. For exam ple , som e parts of the c i ty m ight form an island,with no connec tions to the m ain road system .

T o prevent this, we’l l take our l ist of edges, find unconnec ted nodes, and connec t these islands to the rest of the c i ty ne tworkusing this code :

(defun direct-edges (node edge-list)

(remove-if-not (lambda (x)(eql (car x) node))edge-list))

(defun get-connected (node edge-list)

(let ((visited nil))(labels ((traverse (node)(unless (member node visited)

(push node visited)

(mapc (lambda (edge)(traverse (cdr edge)))(direct-edges node edge-list)))))(traverse node))visited))

(defun find-islands (nodes edge-list)(let ((islands nil))

(labels ((find-island (nodes)(let* ((connected (get-connected (car nodes) edge-list))(unconnected (set-difference nodes connected)))(push connected islands)

(when unconnected(find-island unconnected)))))(find-island nodes))islands))

(defun connect-with-bridges (islands)

(when (cdr islands)(append (edge-pair (caar islands) (caadr islands))(connect-with-bridges (cdr islands)))))

Page 154: Land of Lisp - Barski M.D., Conrad

(defun connect-all-islands (nodes edge-list)(append (connect-with-bridges (find-islands nodes edge-list)) edge-list))

First , we dec la re a ut i l i ty func tion ca l led direct-edges , which finds a l l the edges in an edge l ist tha t sta rt from a given

node . It does this by c rea ting a new l ist with a l l edges rem oved (using remove-if-not ) tha t don’t have the current node in thecar posi t ion.

T o find islands, we write the get-connected func tion . T his func tion takes an edge l ist and a source node and builds a l istof a l l nodes connec ted to tha t node , even if i t requires walking ac ross m ult iple edges.

T he usua l way to find connec ted nodes is to sta rt a visited l ist , and then perform a sea rch a long connec ted nodes, sta rt ing

with the source node . Newly found nodes a re added to the visi ted l ist with the push com m and . W e a lso traverse a l l the

children of this found node , using mapc .If, on the other hand, we encounte r a node tha t has a lready been visi ted, we know we can ignore i t . Once the sea rch is com ple te ,

the visited l ist wil l consist of a l l connec ted nodes.Now tha t we have a func tion for finding nodes tha t a re connec ted, we can use i t to c rea te a func tion tha t wil l find a l l the islands

in our graph. T he find-islands func tion first de fines a loca l func tion, ca l led find-island . T his func tion checks whichnodes a re connec ted to the first node in our l ist of nodes using the connected func tion. It then subtrac ts these nodes from the ful ll ist of nodes using the set-difference func tion. (set-difference takes two l ists, and re turns a l l i tem s tha t a re in the first l ist butnot the second. )

Any rem aining nodes a re deem ed unconnec ted. If any unconnec ted node exists , we ca ll the find-islands func tion aga inrecursive ly to find addit iona l islands.

Once we’ve found a l l the islands, we need a way of bridging them toge ther. T his is the job of the connect-with-bridgesfunc tion. It re turns a l ist of addit iona l edges tha t join a l l the islands toge ther. T o do this, i t takes the l ist of islands and checks if

the re is a cdr in this l ist . If the re is, i t m eans the re a re a t least two land m asses, which can be connec ted with a bridge . Ituses the edge-pair func tion to c rea te this bridge , and then ca lls i tse lf recursive ly on the ta i l of the island l ist , in case addit iona lbridges a re needed.

Fina lly, we t ie a l l of our island prevention func tions toge ther using the func tion connect-all-islands . It uses find-islands to find a l l the land m asses, and then ca lls connect-with-bridges to build appropria te bridges. It then appends thesebridges to the ini t ia l l ist of edges to produce a fina l , ful ly connec ted land m ass.

Page 155: Land of Lisp - Barski M.D., Conrad

Building the F inal Edges for Congestion City

T o com ple te our edges for Congestion City, we need to convert the edges from an edge l ist into an a l ist . W e a lso wil l add thepolice roadblocks, which wil l appear random ly on som e of the edges. For these tasks, we wil l c rea te the make-city-edges, edges-to-alist, and add-cops func tions:(defun make-city-edges ()

(let* ((nodes (loop for i from 1 to *node-num*collect i))

(edge-list (connect-all-islands nodes (make-edge-list)))

(cops (remove-if-not (lambda (x)(zerop (random *cop-odds*)))edge-list)))

(add-cops (edges-to-alist edge-list) cops)))

(defun edges-to-alist (edge-list)

(mapcar (lambda (node1)(cons node1

(mapcar (lambda (edge)(list (cdr edge)))(remove-duplicates (direct-edges node1 edge-list)

:test #'equal))))(remove-duplicates (mapcar #'car edge-list))))

(defun add-cops (edge-alist edges-with-cops)

(mapcar (lambda (x)(let ((node1 (car x))(node1-edges (cdr x)))(cons node1

(mapcar (lambda (edge)(let ((node2 (car edge)))

(if (intersection (edge-pair node1 node2)edges-with-cops:test #'equal)(list node2 'cops)edge)))node1-edges))))edge-alist))

T hese a re the m ost cum bersom e func tions in Grand T heft W um pus. L e t’s take a c lose r look a t them .

The make-c ity-edges F unction

First , the make-city-edges func tion c rea tes a l ist of nodes, using a loop . (T his is sim ply a l ist of num bers from 1 to*node-num*. ) Next, i t c rea tes a random (but ful ly connec ted) edge l ist by ca l l ing the make-edge-list and connect-edge-list

func tions . T his result is stored in the edge-list va riable . It then c rea tes a random list of edges tha t conta ins cops . W edefine these variables with the let* com m and, which a l lows us to re fe rence previously defined variables.

T he following exam ple shows the diffe rence be tween defining variables with let and let*:> (let ((a 5)

(b (+ a 2)))b)*** - EVAL: variable A has no value> (let* ((a 5)(b (+ a 2)))b)7

As you can see , let won’t a l low you to re fe r to other de fined variables (the variable b can’t re fe rence the va lue of a). W hendefining variables with let*, on the other hand, this kind of re fe rence is a l lowed. For our purposes, using let* a l lows our defini t ion

of cops to conta in a re fe rence to edge-list.Once we’ve c rea ted the edge l ist and de te rm ined where the cops a re , we need to convert our edge l ist into an a l ist and add the

cops to i t . T he edges a re converted to an a l ist with the edges-to-alist func tion, and the cops a re added with the add-copsfunc tion.

The edges-to-alist F unction

Page 156: Land of Lisp - Barski M.D., Conrad

The edges-to-alist F unction

T he edges-to-alist func tion converts a l ist of edges into an a l ist of edges. For exam ple , assum e we have the fol lowing c i ty,with only three loca tions and two edges connec ting those loca tions:

W e would describe this using an edge l ist a s '((1 . 2) (2 . 1) (2 . 3) (3 . 2)). Rem em ber tha t each of the edges isrepea ted, since the edges a re undirec ted and can be used in both direc t ions. If we described this sam e c i ty as an a l ist , wha t wouldtha t look l ike?

Rem em ber tha t an a l ist is a l ist tha t le ts us look up a key (in this exam ple , one of the three nodes in our c i ty) and find theinform ation assoc ia ted with tha t key (in this case , a l ist of the roads connec ted to i t ). For this sm all c i ty, the a l ist would be '((1(2)) (2 (1) (3)) (3 (2))).

T o build this a l ist , the edges-to-list func tion first mapcars over the nodes found in the edge l ist . T o build the l ist ofnodes, we use the remove-duplicates func tion, which rem oves duplica te i tem s from a l ist . By default , remove-duplicates usest h e eql func tion to check for equa li ty, though i t a lso a l lows you to choose a diffe rent te st func tion using the : te st keyword

param ete r. Since we’re checking for equa li ty of cons pa irs in our make-city-edges func tion, we se t :test to #'equal .

W ithin this oute r mapcar , we use another mapcar to m ap across a l l the direct-edges to this node . T oge ther, thesenested mapcar func tions a l low edges-to-alist to convert the edges of a c i ty into an a l ist .

The add-cops F unction

W hen we wrote the make-city-edges func tion, we random ly m arked som e of the edges to show tha t they have cops on them

. W e a re now going to use this l ist of cop edges to m ark the edges in our a l ist tha t conta in cops. T his is the job of the add-cops func tion.

T o do this, we use nested mapcar com m ands to m ap across the edges within each node . W e then check whether

there a re any cops on a given edge , using the intersection func tion . (T he intersection func tion te l ls us which i tem s a reshared be tween two l ists. )

T o understand exac tly wha t the add-cops func tion is doing, i t wil l he lp to once aga in im agine our c i ty with only three loca tionsand two stree ts. In this exam ple , one of the stree ts has cops on i t :

T he genera ted a l ist for this c i ty, c rea ted by add-cops, would look l ike this:((1 (2)) (2 (1) (3 COPS)) (3 (2 COPS)))T his is ac tua lly a nested a l ist . T he oute r a l ist is organized based on the first node , and the inner a l ists a re organized based on the

second node .W ith the edges in this form at, we can easi ly find a l l edges connec ted to a given node by ca ll ing (cdr (assoc node1 edges)).

T o see if a given edge conta ins cops, we can ca ll (cdr (assoc node2 (cdr (assoc node1 edges)))), which goes down two leve ls

Page 157: Land of Lisp - Barski M.D., Conrad

to grab the ac tua l da ta l inked to a spec ific edge be tween two nodes. (One addit iona l benefi t of using this nested a l ist form at is tha ti t is ful ly com patible with our graph l ibra ries—a fea ture tha t we’l l take advantage of short ly. )

Page 158: Land of Lisp - Barski M.D., Conrad

Building the Nodes for Congestion City

Now we’ll build an a l ist for the nodes in our c i ty. T hese nodes m ay conta in the W um pus or the Glowworm s, or they m ightconta in various c lues, such as blood, l ights, or sirens.

Most of the c lues in our gam e a re based on proxim ity to another node , so we need to write som e func tions tha t te l l us if twonodes a re one node apart in the c i ty graph. T he neighbors func tion looks up the node’s ne ighbors using the a l ist of edges. If thesecond node is in tha t l ist , we know we’re one away.(defun neighbors (node edge-alist)

(mapcar #'car (cdr (assoc node edge-alist))))

(defun within-one (a b edge-alist)(member b (neighbors a edge-alist)))

First , this func tion looks up the first node (a) in the a l ist of edges with neighbors. T hen i t uses member to see if the other node(b) is am ong these nodes.

T he blood sta in c lues for the W um pus can a lso be seen from two nodes away. W e can write a second func tion for checking twonodes l ike this:(defun within-two (a b edge-alist)

(or (within-one a b edge-alist)(some (lambda (x)

(within-one x b edge-alist))

(neighbors a edge-alist))))

First , we check if we a re within one node of our goa l , since if we’re within one , we’re a lso within two. Next, we extrac t

a l l the nodes tha t a re one away (sim ila r to wha t we did in the within-one func tion). Fina lly, we check if any of these new

nodes a re within one , which would m ake them within two of the origina l node .Now tha t we have those ut i l i ty func tions, le t’s write the func tion tha t builds the fina l node a l ist (basica l ly, the fina l m ap of our

c i ty. ) Here ’s the l ist ing:(defun make-city-nodes (edge-alist)

(let ((wumpus (random-node))

(glow-worms (loop for i below *worm-num*collect (random-node))))

(loop for n from 1 to *node-num*

collect (append (list n)

(cond ((eql n wumpus) '(wumpus))

((within-two n wumpus edge-alist) '(blood!)))

(cond ((member n glow-worms)'(glow-worm))

((some (lambda (worm)(within-one n worm edge-alist))glow-worms)'(lights!)))

(when (some #'cdr (cdr (assoc n edge-alist)))'(sirens!))))))

T he make-city-nodes func tion first picks random nodes for the W um pus and the Glowworm s , and then i t uses loop

to run through the node num bers. As i t runs through the nodes, i t builds a l ist desc ribing each node in the c i ty, appended

toge ther from various sources . By using append, each part of the code tha t desc ribes these nodes (and is within the body ofthe append) can choose to add ze ro, one , or m ult iple i tem s to the descript ion, c rea ting i ts own child l ists with ze ro, one , orm ult iple i tem s.

At the front of the l ist , we put the node nam e, n . If the W um pus is a t the current node , we add the word Wumpus

(but wrapped in a l ist , a s we just desc ribed). If we’re within two nodes of the W um pus, we show its blood . If the node has a

Page 159: Land of Lisp - Barski M.D., Conrad

Glowworm gang, we show it next , and if the Glowworm gang is one node away, we show its l ights . Fina lly, i f an edge

from the node conta ins cops, we indica te tha t sirens can be heard .T o check for the sirens c lue , we sim ply grab the edges with (cdr (assoc n edges)) and see if som e of these nodes have a va lue

in the cdr. T he 'cops sym bol would be a t tached to the edges a t the cdr. Since we have only one da ta point for edges in this gam e,looking for the presence of a cdr is an adequa te check for the presence of cops. For exam ple , i f we use our ea rl ie r exam ple of ana list with cops on i t :

((1 (2)) (2 (1) (3 COPS) ) (3 (2 COPS)))

You can see tha t i f an edge in the l ist has cops, such as here , the cdr wil l lead to a non-nil va lue . An edge without cops

wil l have a cdr tha t is nil.

Page 160: Land of Lisp - Barski M.D., Conrad

Initial iz ing a New G ame of G rand Theft Wumpus

W ith our graph construc tion stuff out of the way, we can write a sim ple func tion tha t ini t ia l izes a brand-new gam e of GrandT heft W um pus:(defun new-game ()

(setf *congestion-city-edges* (make-city-edges))(setf *congestion-city-nodes* (make-city-nodes *congestion-city-edges*))

(setf *player-pos* (find-empty-node))(setf *visited-nodes* (list *player-pos*))(draw-city))

T here a re two new func tions here . One , the find-empty-node func tion , ensures tha t the player doesn’t end up on top of abad guy right a t the beginning of the gam e. Here ’s the code for tha t func tion:(defun find-empty-node ()

(let ((x (random-node)))

(if (cdr (assoc x *congestion-city-nodes*))

(find-empty-node)x)))

T he find-empty-node func tion is pre t ty sim ple . First , i t picks a random node to consider as the player’s sta rt ing posit ion.

T hen i t checks whe ther i t is a com ple te ly em pty node . If the re ’s stuff in tha t node , i t sim ply ca l ls i tse lf aga in, t rying another

random spot .

Warning

If you ever dec ide to m odify the gam e and m ake i t m ore c rowded with bad guys, you could end up in a si tua tion where no em ptynodes exist . In tha t case , this func tion wil l sea rch forever and lock up your L isp RE PL , since we didn’t put in any checks to de tec tthis si tua t ion.

T he other new func tion in our new-game com m and is draw-city, which we’l l wri te next .

Page 161: Land of Lisp - Barski M.D., Conrad

Drawing a M ap of O ur City

W e’re fina lly ready to draw a m ap of our new c ity. W e’re using a standard form at for our graph da ta , so writ ing this func tion is abreeze :(defun draw-city ()

(ugraph->png "city" *congestion-city-nodes* *congestion-city-edges*))W e crea ted the ugraph->png func tion in the previous chapte r, a s pa rt of our graph l ibra ry.Now ca ll (new-game) from the RE PL , and open the c ity .dot . png pic ture in your web browser:

Note

Since every c i ty m ap c rea ted by our code is unique , your m ap wil l look com ple te ly diffe rent from the one in this pic ture .

Fina lly, we can m arve l a t the results of our urban planning!

Page 162: Land of Lisp - Barski M.D., Conrad

Drawing a City from P artial K nowledge

Of course , i t ’s awfully boring to hunt som ething if you a lready know where i t is be fore the hunt sta rts. T o solve this problem , wewant a m ap of the c i ty tha t shows only the nodes tha t we’ve visi ted so fa r. T o tha t end, we use a globa l l ist ca l led *visited-nodes* tha t is ini t ia l ly se t to the player’s posit ion only, but which we’l l upda te as we walk a round the c i ty visi t ing other nodes.Using this *visited-nodes* va riable , we can ca lcula te a sm alle r graph tha t inc ludes only those parts of the c i ty tha t a re known tous.

K nown Nodes

First , we can build an a l ist of just the known nodes:(defun known-city-nodes ()

(mapcar (lambda (node)

(if (member node *visited-nodes*)(let ((n (assoc node *congestion-city-nodes*)))

(if (eql node *player-pos*)(append n '(*))n))

(list node '?)))(remove-duplicates

(append *visited-nodes*

(mapcan (lambda (node)(mapcar #'car(cdr (assoc node*congestion-city-edges*))))*visited-nodes*)))))

At the bottom of known-city-nodes, we need to figure out which nodes we can “see” based on where we’ve been. W e’ll be able

to see a l l visi ted nodes , but we a lso want to track a l l nodes within one node of a visi ted node . (W e wil l discuss themapcan func tion short ly. ) W e ca lcula te who is “within one” using code sim ila r to the previously discussed within-one func tion.

Next, we mapcar over this l ist of re levant nodes, processing each . If the current node is occupied by the player, we m ark i t

with an aste risk . If the node hasn’t been visi ted ye t , we m ark i t with a quest ion m ark .

K nown Edges

Now, we need to c rea te an a l ist stripped of any cop sirens tha t we haven’t reached ye t:(defun known-city-edges ()

(mapcar (lambda (node)(cons node (mapcar (lambda (x)(if (member (car x) *visited-nodes*)x

(list (car x))))(cdr (assoc node *congestion-city-edges*)))))*visited-nodes*))

T his func tion is sim ila r to the known-city-nodes func tion. T he noteworthy l ine of code is he re where we strip the cdrfrom the edge l ist for edges so tha t cops a re indica ted on the m ap only if we’ve visi ted the nodes on both ends of an edgeconta ining cops.

The mapcan F unction

T he mapcan func tion we used in known-city-nodes is a va riant of mapcar. However, unlike mapcar, mapcan a ssum es tha t theva lues genera ted by the m apping func tion a re a l l l ists tha t should be appended toge ther. T his is use ful when there isn’t a one-to-onere la t ionship be tween the i tem s in a l ist and the result you want to genera te .

For exam ple , suppose we run a burger shop and se l l three types of burgers: the single , the double , and the double cheese . T oconvert a l ist of burgers into a l ist of pa tt ies and cheese sl ices, we could write the fol lowing func tion:> (defun ingredients (order)

(mapcan (lambda (burger)(case burger(single '(patty))(double '(patty patty))(double-cheese '(patty patty cheese))))order))INGREDIENTS> (ingredients '(single double-cheese double))'(PATTY PATTY PATTY CHEESE PATTY PATTY)

Drawing O nly the K nown P arts of the City

Page 163: Land of Lisp - Barski M.D., Conrad

Drawing O nly the K nown P arts of the City

Because we now have func tions tha t can genera te the known inform ation about nodes and edges, we can write a func tion tha tturns this inform ation into a pic ture , a s fol lows:(defun draw-known-city ()

(ugraph->png "known-city" (known-city-nodes) (known-city-edges)))Now le t’s redefine our new-game func tion to draw the known c ity when the gam e sta rts:(defun new-game ()

(setf *congestion-city-edges* (make-city-edges))(setf *congestion-city-nodes* (make-city-nodes *congestion-city-edges*))(setf *player-pos* (find-empty-node))(setf *visited-nodes* (list *player-pos*))(draw-city)

(draw-known-city))T his func tion is a lm ost exac tly the sam e as the previous version of new-game, except tha t we a lso c rea te a drawing com posed

only of the known parts of the c i ty .Now, if we ca ll the new-game func tion from the RE PL , we’l l ge t a new pic ture nam ed known-c ity .dot . png tha t we can view in

our browser. It wil l look som ething l ike this:

Now we’re ready to walk a round our m ap of Congestion City!

Page 164: Land of Lisp - Barski M.D., Conrad

Walking Around Town

W e’ll need two func tions for trave ling be tween the nodes in our c i ty: a regula r walk func tion and one for when we think we’vefound the W um pus, and we want to charge tha t loca tion with our fina l bulle t . Since these two func tions a re very sim ila r, we’l lhave both of them de lega te the bulk of the work to a com m on handle-direction func tion:(defun walk (pos)

(handle-direction pos nil))

(defun charge (pos)(handle-direction pos t))

T he only diffe rence be tween these two func tions is the flag they pass to handle-direction, which is se t to e i the r nil or t,depending on the kind of trave ling.

T he handle-direction func tion’s m ain job is to m ake sure tha t a m ove is lega l , which i t does by checking the edges of the c i ty:(defun handle-direction (pos charging)

(let ((edge (assoc pos

(cdr (assoc *player-pos* *congestion-city-edges*)))))(if edge

(handle-new-place edge pos charging)

(princ "That location does not exist!"))))

First , this func tion looks up the lega l direc t ions players can m ove to from the ir current loca tion . It then uses the pos theplayer wants to m ove to and looks i t up in tha t l ist of possible direc t ions. Once we’ve de te rm ined tha t a direc t ion is lega l (tha t is,a node with tha t num ber shares an edge with the player’s current posi t ion), we need to find out wha t surprises a re wait ing as the

player trave ls to this new place , using the handle-new-place func tion, which we’l l c rea te next . Otherwise , we display a

he lpful e rror m essage .Now le t’s c rea te the handle-new-place func tion, which ge ts ca l led when the player has trave led to a new place :(defun handle-new-place (edge pos charging)

(let* ((node (assoc pos *congestion-city-nodes*))

(has-worm (and (member 'glow-worm node)(not (member pos *visited-nodes*)))))

(pushnew pos *visited-nodes*)

(setf *player-pos* pos)

(draw-known-city)

(cond ((member 'cops edge) (princ "You ran into the cops. Game Over."))

((member 'wumpus node) (if charging(princ "You found the Wumpus!")(princ "You ran into the Wumpus")))

(charging (princ "You wasted your last bullet. Game Over."))

(has-worm (let ((new-pos (random-node)))(princ "You ran into a Glow Worm Gang! You're now at ")(princ new-pos)(handle-new-place nil new-pos nil))))))

First , we re trieve the node the player is t rave ling to from the a l ist of nodes . Next, we figure out i f the node conta ins a

Glowworm gang . W e ignore the gang if they’re in a node a lready visi ted, because they’l l only a t tack once .

Next, the handle-new-place func tion upda tes *visited-nodes* (adding the new posit ion to the l ist) and *player-pos*

. T hen i t ca l ls draw-known-city aga in, since we now have a new place we know about.

Next, i t checks to see if the re a re any cops on the edge , and then whether the W um pus is a t tha t loca tion . If theplayer encounte rs the W um pus, our handle-new-place func tion needs to know whether we were charging the loca tion. If we a recharging a t the W um pus, we win the gam e. Otherwise , the W um pus kil ls us and the gam e is over.

If, on the other hand, we charge a t a loca tion tha t does not conta in the W um pus, we waste our single bulle t and we lose the

gam e as well . Fina lly, i f the loca tion has a previously unencounte red Glowworm gang, jum p to a random new loca tion,

Page 165: Land of Lisp - Barski M.D., Conrad

ca ll ing handle-new-place recursive ly .Our gam e is now com ple te !

Page 166: Land of Lisp - Barski M.D., Conrad

Let's H unt Some Wumpus!

T o play our gam e, sim ply ente r the trave ling com m ands we c rea ted (walk and charge) a t the RE PL , then switch to your browserand re fresh known-c ity .dot . png to plan your next m ove .

For exam ple , he re ’s where we le ft off in our sam ple gam e:

Since we have no c lues, we know tha t any of these nodes wil l be sa fe to visi t . L e t’s t ry (walk 20):

Uh oh! T here ’s blood here . T ha t m eans the W um pus m ust be two nodes away! It should st i l l be sa fe to (walk 11) though, becausetha t’s only one node away:

Page 167: Land of Lisp - Barski M.D., Conrad

Oh no! One of these stree ts has a police roadblock. L e t’s backtrack with (walk 20) (walk 19), and then we can try (walk 7):

Darn! Now we have the W um pus and som e Glowworm s nearby. L e t’s take a shot in the dark and try (walk 10):

Page 168: Land of Lisp - Barski M.D., Conrad

W ell, tha t didn’t he lp, since the re a re cops down this pa th. However, because node 10 has only one other unexplored stree t , wecan say with ce rta inty tha t the stree t be tween 1 and 10 has cops on i t .

You can see tha t i t takes som e se rious thinking to becom e a m aste r in Grand T heft W um pus! Rem em ber, you can a lways sta rt anew gam e, with a new m ap, by using the new-game func tion. Once you’ve tracked down the W um pus, use the charge func tion toa ttack him .

If you m aste r the basic version of this gam e, try inc reasing the num ber of nodes, edges, cops, and Glowworm s for an even grea te rcha llenge!

Page 169: Land of Lisp - Barski M.D., Conrad

What You've Learned

In this chapte r, we’ve used graph uti l i t ie s with L isp to m ake a m ore sophist ica ted gam e. Along the way, you lea rned thefollowing:

T he loop func tion a l lows us to loop ac ross various types of da ta . It wil l be discussed in m ore de ta i l in Chapte r 10.T he set-difference func tion te l ls you which i tem s a re in one l ist but not in another l ist .T he intersection func tion te l ls you which i tem s a re shared by l ists.T he remove-duplicates func tion rem oves duplica te i tem s from a l ist .

Page 170: Land of Lisp - Barski M.D., Conrad

Chapter 9. Advanced Datatypes and G ener ic P rogramming

As you’ve seen so fa r, a lot can be accom plished in L isp by using cons ce l ls, sym bols, strings, and num eric da ta types. As a verym ature language , Com m on L isp conta ins m any m ore da ta types tha t m ove well beyond these basics. In this chapte r, we wil l discussthe m ost use ful of these advanced da ta types, inc luding a rrays, hash tables, and struc tures.

Page 171: Land of Lisp - Barski M.D., Conrad

Arrays

T he Common Lisp array is ve ry sim ila r to a l ist . T he m ain advantage of using a rrays is tha t they require only a constant am ountof t im e to access a va lue a t any spec ific loca tion. W e’ll be discussing what this m eans short ly.

Page 172: Land of Lisp - Barski M.D., Conrad

Working with Arrays

T o c rea te a new array, use the make-array com m and, spec ifying the a rray’s size :> (make-array 3)

#(NIL NIL NIL)T his c rea tes an a rray of length 3. In order to indica te tha t the va lue c rea ted is not just a l ist , Com m on L isp prepends a hash

m ark (#) in front of the a rray.T o ge t and se t i tem s in an a rray, use the aref func tion. For exam ple , he re ’s how we ge t the i tem a t index 1:> (defparameter x (make-array 3))

#(NIL NIL NIL)> (aref x 1)NIL

Of course , our a rray is just fi l led with nils right now, so the re ’s not m uch worth ge tt ing. T o se t i tem s in the a rray to m oreinte rest ing va lues, use aref in conjunc tion with the setf com m and:> (defparameter x (make-array 3))

#(NIL NIL NIL)> (setf (aref x 1) 'foo)FOO> x#(NIL FOO NIL)> (aref x 1)FOO

Although aref is usua lly a com m and used to ge t a va lue out of an a rray, when used in this spec ia l way indica ted in the exam ple ,i t le ts us se t a va lue in an a rray, instead. T his abil i ty to use the setf and aref com m ands toge ther shows off a fea ture in Com m onL isp: i ts support for generic program m ing. L e t’s take a c lose r look a t the setf com m and to lea rn m ore about how this fea tureworks.

Page 173: Land of Lisp - Barski M.D., Conrad

Using a G ener ic Setter

T he Com m on L isp language is sa id to support generic se t ters. T his m eans tha t in m ost cases, the code for pull ing a value out ofa da ta struc ture (whe ther an a rray, l ist , string, or som ething e lse ) is identica l to the code for putt ing data into tha t sam e da tastruc ture . T he setf com m and can be used in conjunc tion with func tions tha t pe rform ge tt ing opera t ions and can use the sam efunc tions to perform se tt ing opera t ions, instead.

W e’ve a lready seen tha t aref can be used to ge t va lues out of an a rray, and when used with setf, i t can be used for se t t ingva lues in the sam e a rray. T he setf com m and can perform this t rick in a genera l way ac ross m ost of the com m ands in Com m onL isp tha t ge t i tem s from a da ta struc ture . T ake , for instance , the fol lowing exam ple involving a l ist :> (setf foo '(a b c))

(A B C)> (second foo)B

> (setf (second foo) 'z)Z> foo(A Z C)

As you would expec t , the expression (second foo) re turns B. But, when we pass (second foo) to the setf com m and , i tsom ehow knows where the B cam e from , and i t is able to trea t the expression (second foo) a s i f i t were a regula r va riable .Basica lly, the setf com m and asks i tse lf the quest ion, “W here did the i tem in m y first a rgum ent origina lly com e from ?” In thiscase , the va lue cam e from the second i tem in the l ist nam ed foo. T here fore , i f we try to setf this loca tion, the source variable ,foo, is m odified in response .

In fac t , the first a rgum ent in setf is a spec ia l sublanguage of Com m on L isp, ca l led a generalized re ference . Not every L ispcom m and is a l lowed in a genera l ized re fe rence , but you can st i l l put in som e pre t ty com plica ted stuff:> (setf foo (make-array 4))

#(NIL NIL NIL NIL)

> (setf (aref foo 2) '(x y z))(X Y Z)> foo

#(NIL NIL (X Y Z) NIL)

> (setf (car (aref foo 2)) (make-hash-table))#S(HASH-TABLE)

> (setf (gethash 'zoink (car (aref foo 2))) 5)5> foo#(NIL NIL (#S(HASH-TABLE (ZOINK . 5)) Y Z) NIL)

T his exam ple dem onstra tes the true power of setf in Com m on L isp. In the first use , we put the l ist (x y z) into an a rray as the

third i tem . If we now print foo, we can see tha t i t worked . In the second use , we replace the first i tem in this l ist

inside the foo a rray with a hash table . Hash tables a re another advanced da ta type we’l l be lea rning about short ly, in HashT ables. It is surprisingly easy to do this with setf, because the genera l ized re fe rence in the first a rgum ent to setf can bearbitra ri ly com plica ted.

Fina lly, we go a l l out and inse rt the va lue 5 into this hash table with the key of zoink . T he gethash func tion le ts you ge ta va lue out of a hash table , a s we’l l see short ly. Here , with the he lp of setf, we a re putt ing the num ber 5 into the hash tableinstead.

I hope you can apprec ia te from this exam ple how useful setf can be when m odifying com plica ted da ta struc tures in a program .Another cool fea ture of setf is tha t you can expand the genera l ized re fe rence syntax to support new ways of accessing va lues.

setf is a t ruly generic way of m odifying va lues, regardless of the leve l of nest ing or the da ta types be ing used.

Page 174: Land of Lisp - Barski M.D., Conrad

Arrays vs. Lists

You’ve now seen som e basic exam ples of working with a rrays in L isp. However, to ful ly understand the benefi ts of a rrays, weneed to com pare them with l ists.

Alm ost anything tha t can be done with a l ist can a lso be done with an a rray. However, a rrays a re usua lly m uch faste r than l istswhen accessing spec ific e lem ents, so the diffe rence is in perform ance .

For exam ple , the a rray-handling func tion aref is ve ry sim ila r to a l ist-handling func tion ca lled nth, which a l lows you access toan i tem a t a spec ific loca tion in a regula r l ist without using an a rray. Here is an exam ple using nth on a l ist :> (nth 1 '(foo bar baz))

BARHowever, i t m akes sense to use the nth func tion only with very sm all l ists. If, for exam ple , l ist X had thousands of i tem s in i t ,

running the com m and (nth 1000 x) would be excruc ia t ingly slow, because L isp l ists a re m ade out of cha ins of cons ce l ls. Hence ,the only way L isp can find the thousandth i tem in a l ist is to churn through the 999 preceding objec ts first .

In contrast , running the com m and (aref x 1000) on a la rge a rray accesses the thousandth i tem direc t ly, without countingthrough the previous 999 i tem s. T his m eans aref wil l execute m uch m ore quickly on a la rge a rray than the nth com m and would ona la rge l ist . In fac t , an aref ca l l wil l happen very quickly no m atte r how la rge the a rray. E ven if you had an a rray with a bi l l ionitem s, re trieving the last i tem would st i l l happen very quickly. T he only rea l l im it ing fac tor is your system : the am ount of RAMyour com pute r has and how capable your L isp environm ent is of taking advantage of i t .

Not only can we quickly access a rray va lues, but we can a lso change va lues a t any spec ific loca tion, usua lly faste r than we canby perform ing the sam e opera t ions on a l ist .

Because se t t ing and ge tt ing spec ific va lues in a la rge da ta struc ture is so im portant , keep a rrays in m ind as a tool to he lp you ge tthe best pe rform ance possible with your code .

Page 175: Land of Lisp - Barski M.D., Conrad

H ash Tables

In the sam e way tha t a rrays a re sort of l ike l ists, hash tables a re sort of l ike alists, except tha t they a lso a l low you to accessa rbitra ry e lem ents m ore quickly.

In fac t , hash tables a re so e ffic ient tha t they can, a t t im es, seem like m agic . T hink of the Babe l fish in the Hitchhiker’s Guideto the Galaxy t ri logy—som ething so im possibly use ful tha t i t rea l ly has no business exist ing in the first place . T ha t’s why a lm ost a l lm odern languages now offe r the hash table da ta type .

Page 176: Land of Lisp - Barski M.D., Conrad

Working with H ash Tables

Crea te a new hash table with the make-hash-table com m and:> (make-hash-table)

#S(HASH-TABLE ...)L ike a l ists, hash tables store i tem s using a lookup key and a va lue . W e can re trieve an i tem from the hash table using the i tem ’s

key with the gethash func tion:> (defparameter x (make-hash-table))

#S(HASH-TABLE ...)> (gethash 'yup x)

NIL ;

NILSo fa r, our hash table rem ains em pty. T his m eans tha t when we look up any key in the hash table , such as 'yup in this exam ple ,

we rece ive NIL a s an answer . Ac tua lly, we rece ive two NIL s —the gethash com m and re turns m ult iple va lues,which you can do in Com m on L isp (discussed in the next sec t ion). T he first re turned va lue is the ac tua l va lue stored in the hashtable , and the second indica tes whe ther the key was found in the table (in this case , i t wasn’t).

Just as with a rrays, we can once aga in com bine a com m and used for re fe renc ing da ta e lem ents—in this case , gethash—with thesetf com m and in order to fi l l our table with da ta :> (defparameter x (make-hash-table))

#S(HASH-TABLE ...)

> (setf (gethash 'yup x) '25)25> (gethash 'yup x)

25 ;

T

In this exam ple , we’ve stored the va lue 25 in the hash table with a lookup key of yup . T hen, when we look up yup in the

table , we ge t the answer of 25 . W e a lso ge t a second va lue of t , which m eans, “Yes, I found the key in the table . ”Rem em ber when we discussed a l ists, we se t up a da ta struc ture conta ining an order for coffee drinks? Here is tha t sam e da ta , but

this t im e i t’s stored using a hash table :> (defparameter *drink-order* (make-hash-table))

#S(HASH-TABLE ...)> (setf (gethash 'bill *drink-order*) 'double-espresso)DOUBLE-ESPRESSO> (setf (gethash 'lisa *drink-order*) 'small-drip-coffee)SMALL-DRIP-COFFEE> (setf (gethash 'john *drink-order*) 'medium-latte)MEDIUM-LATTE

Accessing the drink order for any person is now sim ple :> (gethash 'lisa *drink-order*)

'small-drip-coffee ;T

Page 177: Land of Lisp - Barski M.D., Conrad

Returning M ultiple Values

Com m on L isp a l lows you to re turn m ore than one va lue as a result . Som e of the core Com m on L isp core func tions do this,inc luding the gethash func tion, as you’ve seen. Another com m only used func tion tha t does this is the round func tion, which roundsoff a num ber:> (round 2.4)

2 ;

0.4

Call ing this func tion appropria te ly rounded our num ber to 2 , but then i t a lso genera ted a second va lue , which is the

rem ainder of the rounding opera t ion . Both va lues a re re turned from this func tion ca ll .You can a lso c rea te m ult iple va lues in your own code by using the values func tion. Here , for instance , we can write a foo

func tion tha t re turns two separa te num bers, 3 and 7:> (defun foo ()

(values 3 7))FOO> (foo)

3 ;

7

Both of these va lues a re printed out a t the RE PL , just a s with the round func tion. However, L isp considers the firstva lue to be m ore im portant , and i t wil l a lways be used by default during follow-up ca lcula t ions. For instance , we can perform anaddit ion a fte r ca l l ing foo, l ike this:> (+ (foo) 5)

8In this case , the addit ion opera tor just ignores the second va lue tha t foo re turns.However, som etim es you m ight need to use tha t addit iona l re turned va lue . You can do this by using the multiple-value-bind

com m and:> (multiple-value-bind (a b) (foo)

(* a b))21

In this exam ple , we’ve bound the variables a and b to both of the va lues re turned by foo (3 and 7). Ca ll ing our func tion withmultiple-value-bind le ts us use the extra va lues re turned from the func tion, which would otherwise be ignored.

You m ight be wondering whether you could just re turn a l ist from your func tion instead of using the m ult iple -va lue fea ture . T heanswer is, yes, you could. However, i t ’s possible tha t using the m ult iple -va lue fea ture can lead to m ore optim ized and c leanercode .

In this book, we wil l not be m aking m uch use of m ult iple va lues. In fac t , m ore recent L isp dia lec ts, such as Arc and Clojure , donot support m ult iple va lues a t a l l . Instead, they just re turn a l ist in the few cases where m ore than one va lue needs to be re turned.

Page 178: Land of Lisp - Barski M.D., Conrad

H ash Table P erformance

As with a rrays, accessing and m odifying a va lue inside a hash table requires only a constant am ount of t im e , no m atte r howm any i tem s your hash table conta ins. For instance , suppose we have a hash table with only 10 i tem s in i t . W e access a va lue in thetable , using a key, and find i t takes on average 1 m il l isecond to do so. Now suppose tha t the hash table has 1,000,000 i tem s in i t .Because of how hash tables a re designed, we could st i l l expec t i t to take only about 1 m il l isecond to re trieve a va lue . In otherwords, no m atte r how big the table is, we can access i tem s a t a constant t im e of 1 m il l isecond.

T hink of how incredible tha t is! E ven if your hash table conta ined 1,000,000 i tem s, the gethash func tion could take the key yougave i t and de te rm ine in a constant am ount of t im e exac tly where your desired i tem could be found!

In this e ra of web-based program s backed by enorm ous am ounts of da ta , the abil i ty of hash tables to store la rge num bers of va lueswith fast re trieva l m akes them indispensable . T he e ffic ient storage of key/va lue pa irs is essentia l for m ost online storage system s.E ven the la test tools for storing vast am ounts of online da ta , l ike Google ’s BigT able or Am azon’s S3, a re buil t a round the quickre trieva l of va lues using keys, which m akes them sim ila r to hash tables.

However, you can’t a lways expec t hash tables to provide the best pe rform ance . Here ’s why: Virtual memory paging and cachemisses:

As with a rrays, la rge hash tables m ay cause your opera t ing system to sta rt paging virtua l m em ory to your hard drive , thusdegrading perform ance . Sim ila rly, they can inc rease the num ber of cache m isses within your CPU.

H ash coll isions:Inte rna lly, hash tables use a spec ia l func tion ca lled a hash func tion, which converts keys into num bers. Such a hash func tion

can cause hash coll isions. Basica l ly, a hash coll ision happens when, by chance , two keys a re converted by the hash func tioninto the sam e num ber. In this case , the hash table wil l st i l l behave correc tly, but a t a sl ightly degraded perform ance . In ra recases, ce rta in types of keys can inte rac t with a hash func tion to inc rease the num ber of coll isions and im pede an applica t ion’sabil i ty to perform lookups, degrading perform ance even m ore .

Ineffic iency with small tables:W ith very sm all tables, the c rea tion and lookup t im e required by hash tables can m ake them less ine ffic ient than sim ple r

struc tures, such as a l ists. T he perform ance benefi ts of hash tables a re noticeable only when they conta in la rger am ounts of da tain them .

Varying speed for operations:In Com m on L isp, i f you c rea te a sm all hash table , and then fi l l i t with va lues, you wil l find tha t occasiona lly, adding a new

value wil l be unusua lly slow. T his is because the make-hash-table func tion is designed to m inim ize the cost for c rea tingsm all hash tables. However, a s you sta rt adding va lues to m ake the table big, L isp wil l need to take extra t im e to a l loca tem ore m em ory so tha t the table can hold m ore i tem s. T hese extra a l loca tions wil l lead to occasiona l slow inse rt ions into thetable as i t grows.

T here is one fina l reason why hash tables a re not a lways the best solution: T hey a re sim ply not as L ispy as tradit iona l L ispstruc tures buil t from cons ce l ls. T his m eans they can be harder to debug than cons ce l ls, since they cannot be read and printed asna tura l ly in the L isp RE PL . T here fore , a good rule of thum b is to stay away from arrays and hash tables as you conce ive a newpiece of code . T hen, i f pe rform ance ends up becom ing an issue , and only then, judic iously m odify the c ri t ica l sec t ions of yourcode to take advantage of a rrays and hash tables to resolve any perform ance problem s.

Page 179: Land of Lisp - Barski M.D., Conrad

A F aster G rand Theft Wumpus Using H ash Tables

L et’s look a t a prac tica l exam ple of wha t hash tables can do for your code . T here is a gla ring ine ffic iency in our la test gam e,Grand T heft W um pus, tha t we can now correc t with hash tables.

Reca ll from the previous chapte r tha t Grand T heft W um pus uses l ists of nodes and edges to represent the graph of the c i ty. T hism eans tha t in order to find connec tions to a given node , we m ust do a l inear sea rch through a l ist . T his isn’t a big dea l in GrandT heft W um pus, because Congestion City doesn’t have a lot of inte rsec tions. But wha t if our c i ty had a thousand nodes with athousand edges? L e t’s t im e the get-connected func tion and see wha t kind of num bers we ge t:> (setf *edge-num* 1000)

1000> (setf *node-num* 1000)1000> (time (dotimes (i 100) (get-connected 1 (make-edge-list))))Real time: 57.699303 sec.Run time: 57.687607 sec.Space: 39566832 BytesGC: 43, GC time: 0.120005 sec.

T he time com m and is a L isp uti l i ty tha t outputs a l l kinds of use ful t im ing inform ation about a chunk of code , and the dotimesfunc tion le ts us run our code 100 t im es, building 100 c i t ie s. Using these com m ands, i t took about a m inute to run this code on m ycom pute r. Given how m any gaz il l ion instruc tions a CPU can c runch in a m inute , this is absolute ly horrifyingly bad perform ance .

T o fix this problem , we’l l replace our edge l ist for this code with a hash table so tha t the get-connected func tion wil l be able tofind connec tions to a node in constant t im e . W e’ll a lso replace our visi ted l ist with a visi ted table , so the func tion can quickly te l lwhe ther a node has a lready been visi ted.

Here is the code tha t m akes this happen, consist ing of hashed versions of our previous func tions:

(defun hash-edges (edge-list)

(let ((tab (make-hash-table)))

(mapc (lambda (x)(let ((node (car x)))

(push (cdr x) (gethash node tab))))edge-list)

tab))

First , we need hash-edges, a func tion tha t converts our edge l ist into a hash table . At the beginning of the func tion, we

crea te a new hash table and nam e i t tab . T hen, we i te ra te through the table with mapc . Rem em ber tha t mapc is justl ike mapcar, except tha t you use i t in places where you ca re only about the side e ffec ts and don’t ca re about genera t ing a fina l l istas a result .

For every node , we want the table to conta in a l ist of nodes connec ted to i t . T here fore , a s we i te ra te through the l ist , we push a

new ne ighbor onto the current l ist of ne ighbors for the current sta rt ing node . W e can use the push com m and on hash tableva lues just a s for regula r L isp variable va lues. T his, aga in, m akes use of the genera l va riables system buil t into Com m on L isp,which we’l l discuss in Handling Data in a Generic W ay in Handling Data in a Generic W ay.

You m ay be wondering why we don’t need to dea l with the case where the re is no va lue ye t for a node in the table . How can wepush som ething into a va lue in the table if no va lue exists? W ell , i t turns out tha t because the gethash func tion re turns NIL when akey is not found in the table , this code wil l sim ply push the new ne ighbor onto an em pty l ist and st ick a new record into the tablewhere none was found before . In this way, the push com m and m agica lly does the “ right thing, ” no m atte r whe ther the node is newor old.

Fina lly, once our table is popula ted, we re turn i t a s a result . It conta ins the sam e da ta as the origina l edge l ist . T hediffe rence is tha t we can now find the ne ighbors of any node in Congestion City a t blaz ing speeds.

(defun get-connected-hash (node edge-tab)

(let ((visited (make-hash-table)))(labels ((traverse (node)

(unless (gethash node visited)

(setf (gethash node visited) t)

(mapc (lambda (edge)(traverse edge))(gethash node edge-tab)))))(traverse node))

visited))Now we’re ready to write get-connected-hash, which re trieves a l l the nodes connec ted to a sta rt ing node in Congestion City

Page 180: Land of Lisp - Barski M.D., Conrad

. It is identica l in behavior to get-connected, but is optim ized through hash tables.

T he first thing this func tion does is c rea te a hash table of visi ted nodes . T hen we trave l through the nodes of CongestionCity, beginning with the sta rt ing node . E very t im e we visi t a new node , we ask ourse lves if we’ve visi ted i t be fore . W e can now

answer this quest ion very e ffic iently by looking up the current node in the visited table . If the answer is no, we’l l need to

m ark this node as visi ted and check a l l of i ts ne ighbors by mapcing through them —checking our edge table . Fina lly,

we re turn our visited table , which in the end wil l hold a l l nodes tha t a re connec ted to the sta rt ing node .Now we can re run our test with this new logic :> (time (dotimes (i 100)

(get-connected-hash 1 (hash-edges (make-edge-list)))))Real time: 1.221269 sec.Run time: 1.224076 sec.Space: 33096264 BytesGC: 36, GC time: 0.10801 sec. :

As you can see , instead of taking a m inute to ca lcula te the connec tions in the graph, i t now takes only one second to do thesam e thing! T his is why you m ust know how to use hash tables.

Page 181: Land of Lisp - Barski M.D., Conrad

Common Lisp Structures

A struc ture is an advanced da ta type ava ilable in Com m on L isp. Struc tures and the ir propert ies can be a use ful way to representda ta in your code .

Page 182: Land of Lisp - Barski M.D., Conrad

Working with Structures

Struc tures can be used to represent objec ts with propert ies, a s you m ight find in a typica l objec t-oriented program m ing (OOP)language using the defstruct com m and, l ike so:> (defstruct person

nameagewaist-sizefavorite-color)PERSON

According to the defini t ion in this struc ture , a person has four propert ies (a lso ca l led slots by L ispers): name, age, waist-size,and favorite-color.

Having defined this struc ture , we can c rea te instances of a pe rson using the make-person com m and, a spec ia l func tion tha tdefstruct has autom atica l ly c rea ted for us:> (defparameter *bob* (make-person :name "Bob"

:age 35:waist-size 32:favorite-color "blue"))*BOB*

Now when we ente r *bob* into the RE PL , we see our new person m arked as a struc ture with the #S pre fix. W e a lso see tha t thestruc ture is of type person, and the va lues of each of i ts propert ies (name, age, waist size, and favorite-color):> *bob*

#S(PERSON :NAME "Bob" :AGE 35 :WAIST-SIZE 32 :FAVORITE-COLOR "blue")W e can de te rm ine Bob’s age by ca ll ing another autom atica l ly c rea ted func tion, person-age:> (person-age *bob*)

35W e can a lso use setf with these com m ands to change Bob’s age . (Happy birthday, Bob!)> (setf (person-age *bob*) 36)

36T he L isp reader can a lso c rea te a person direc t ly from the printed representa t ion of the person, another grea t exam ple of the

print /read sym m etry in L isp:

> (defparameter *that-guy* #S(person :name"Bob" :age 35 :waist-size 32 :favorite-color "blue"))

> (person-age *that-guy*)35

Here , we’re c rea ting a new variable ca l led *that-guy*, and we se t i ts va lue using only the printed representa t ion of the person

. T his va riable now has a rea l person struc ture in i t , just a s if we had used the make-person func tion .As you can see , defstruct is quite a powerful com m and tha t can be used to build spec ia l func tions tha t m ake i t easy to c rea te

instances of a new objec t and access i ts propert ies.

Page 183: Land of Lisp - Barski M.D., Conrad

When to Use Structures

T hese days, m any m ainstream program m ers be lieve tha t objec t orienta t ion is a necessi ty when deve loping la rge and robustapplica t ions. Many L ispers, on the other hand, be lieve tha t i t ’s possible to build high-qua li ty software without taking a pure ly OOPapproach.

Beginning with Chapte r 14, we’l l exam ine som e of these a l te rna te approaches, inc luding higher-order func tiona l program m ingand dom ain-spec ific language program m ing. T he design of the L isp language m akes i t m uch easie r to take advantage of thesea lte rna te approaches than is possible with other, m ore objec t-oriented languages.

Regardless, even if you’re not writ ing pure ly OOP-style software , struc tures and the ir propert ies can st i l l prove to be a use ful wayto represent da ta in your code . For instance , instead of c rea ting a person c lass with defstruct, we could do the sam e thing with astandard l ist and our own make-person func tion. Afte r a l l , why bother with struc tures if we can just rol l our own person using l ists,l ike so:> (defun make-person (name age waist-size favorite-color)

(list name age waist-size favorite-color))MAKE-PERSON> (defun person-age (person)(cadr person))PERSON-AGE> (defparameter *bob* (make-person "bob" 35 32 "blue"))*BOB*> *bob*

("bob" 35 32 "blue")> (person-age *bob*)35

Although this approach wil l work, i t has severa l downsides. First , in order to check a person’s age or other propert ies, we wouldneed to write a lot of e rror-prone func tions tha t pull propert ies out of the l ist from the correc t loca tions. Also, the printed version of

our ad hoc objec t is ve ry hard to understand. How do we know BOB is a pe rson? Is Bob’s age 35 or 32? Regula r l ists just don’tlend them se lves well to encoding objec ts with m ult iple propert ies.

Another problem with using l ists to represent an objec t in the rea l world is tha t the propert ies of an objec t (l ike a person objec t)m ay change over t im e . L ists in L isp work best when you a re dea ling with inform ation tha t never changes once the l ist is c rea ted.W hen Bob turns 36, however, we need to change his age property.

Having part of a da ta struc ture change over t im e is ca l led a mutation by com pute r sc ientists. It ’s easy to change the va lue of aspec ific property (m uta te the property) in a struc ture c rea ted with defstruct, so these struc tures a re very suitable for handling da tatha t needs to be m utable . T here fore , i t m akes sense to store a person (or any other objec t tha t changes over t im e) in a struc ture .W e wil l be discussing the issue of m uta tion in grea te r de ta i l in Chapte r 14.

Note

T he defstruct fac i l i ty is not the only tool tha t can be used to c rea te objec ts in Com m on L isp. For exam ple , in the epilogue ofthe book, you’l l see tha t Com m on L isp’s Com m on L isp Objec t System (CL OS) a l lows you to build very sophist ica ted objec t-basedsystem s. If you ca re to code with a strongly objec t-oriented m indse t , you wil l probably find a l l the OOP language func tiona li ty youneed in Com m on L isp. Indeed, CL OS has m any advanced objec t-oriented fea tures tha t you won’t find in m any other places.Because of this, CL OS has often been used as a research tool for studying OOP ideas.

Page 184: Land of Lisp - Barski M.D., Conrad

H andling Data in a G ener ic Way

Com m on L isp has m any diffe rent da ta types ava ilable for writ ing e legant and e ffic ient program s. But without som e care , havingso m any da ta types can lead to ugly and repe ti t ive code .

For exam ple , suppose we want to add severa l groups of num bers, which a re stored as both l ists and a rrays. Since l ists and a rraysbehave diffe rently, wil l we need to write two diffe rent addit ion func tions—one for l ists and the other for a rrays? It would be grea tif we could write a single chunk of code to handle both cases without ca ring about how the num bers a re stored.

Com m on L isp has a l l the fea tures we need to write such generic code , inc luding generic l ibra ry func tions, type predica tes,defmethod, and generic accessors. W e can use these fea tures to write code tha t works with m any types of da ta—inc luding buil t-inas well a s custom types tha t we m ight c rea te with defstruct—without superfluous repe ti t ion in our code .

Page 185: Land of Lisp - Barski M.D., Conrad

Working with Sequences

T he easiest way to write code tha t works with any type of a rgum ent is to hand the type-checking work to som eone e lse . T heCom m on L isp l ibra ries a re packed with func tions tha t can generica l ly handle da ta of va rying types in the ir a rgum ents, the m ostcom m only used of which a re the sequence func tions. T he sequence func tions work generica l ly ac ross the three m ain ways ofsequenc ing objec ts in L isp: l ists, a rrays, and strings.

You’ve a lready seen one of these sequence func tions without even rea l iz ing i t : the length func tion. You can use the lengthfunc tion to check for the length of a l l three sequence types:> (length '(a b c))

3> (length "blub")4> (length (make-array 5))5

W ithout the generic length func tion, you would need to use three separa te func tions to de te rm ine the length of strings, a rrays,and l ists.

Note

Com m on L isp has a spec ific func tion for checking the length of l ists, ca l led l ist-length. Because generic func tions tend torequire extra type-checking to de te rm ine the correc t behavior, they can be slower to execute . T he list-length func tion is use fulfor pe rform ance-sensit ive code , but m ost L ispers pre fe r using the generic length func tion in regula r code .

Sequence F unctions for Searching

Som e sequence func tions le t you search sequences:

find-if finds the first va lue tha t sa t isfies a predica te .count finds out how often a ce rta in objec t appears in sequence .position te l ls you where an i tem is loca ted.some and every te l l you if som e or every va lue in a sequence obeys a spec ific predica te .

Here a re som e exam ples:

> (find-if #'numberp '(a b 5 d))5

> (count #\s "mississippi")4

> (position #\4 "2kewl4skewl")5

> (some #'numberp '(a b 5 d))T

> (every #'numberp '(a b 5 d))NIL

In these exam ples, we use find-if to find the first num ber in a sequence , which is the num ber 5 . W e use count to find

out how m any t im es the charac te r s appears in "mississippi" . W e use position to find a t wha t posit ion the charac te r 4

appears. In this case , i t is in the fifth posit ion, sta rt ing the count from zero . W e use some to see if any i tem s in a sequence

are num bers. Indeed, the re is a num ber . Fina lly, we use every to see if every i tem in the l ist is a num ber, which is not the

case .

Sequence F unctions for Iterating Across a Sequence

One part icula rly use ful generic sequence func tion is reduce. T he reduce func tion a l lows you to i te ra te through a sequence anddist i l l i t down into a single result . Here , we use reduce to add toge ther i tem s in a l ist :> (reduce #'+ '(3 4 6 5 2))

20T he sum of those num bers turns out to be 20. Here is a diagram tha t shows exac tly wha t is happening in this exam ple :

Page 186: Land of Lisp - Barski M.D., Conrad

On the right side , shown in gray, is our l ist . On the le ft side , you can see the pa irs of num bers tha t a re fed into the plus (+)func tion and the inte rm edia te results tha t a re ca lcula ted. T his shows tha t the plus func tion a lways rece ives a single inte rm edia teresult a s we ll a s the next num ber in the l ist a s i ts a rgum ents. T he only exception to this is in the very first ca l l to the plus func tion.Since no inte rm edia te result exists when we sta rt , the first t im e we ca ll the plus func tion, we prom ote the num ber 3, which is a tthe sta rt of the l ist , into our inte rm edia te result colum n. T here fore , the first t im e the plus func tion is ca l led, i t ac tua lly rece ivestwo i tems stra ight off the top of the l ist .

L e t’s look a t a sl ightly m ore com plica ted exam ple , this t im e using our own reduc tion func tion. W e’re going to find the la rgesteven num ber in the l ist :

> (reduce (lambda (best item)

(if (and (evenp item) (> item best))

item

best))'(7 4 6 5 2)

:initial-value 0)6

Our reduc tion func tion, which we pass to reduce to dist i l l down our answer from the l ist , has two a rgum ents . T he firsta rgum ent is the best va lue we’ve found so fa r—in other words, the la rgest even num ber we’ve found so fa r. T he second a rgum ent isthe next num ber from the l ist .

Our reduce func tion needs to re turn as a result the new best num ber. T here fore , i f the la test num ber is be tte r than the previous

best , we re turn i t . Otherwise , we re turn the previous best .Rem em ber tha t the first num ber in the l ist we’re reduc ing wil l be used as a sta rt ing va lue . If this is a problem , we can instead

pass an explic i t ini t ia l va lue to the reduce func tion by passing in a keyword param ete r nam ed :initial-value .Spec ifying an ini t ia l va lue for the reduce func tion is often necessa ry, or a bug can sneak into your code . In our exam ple , i t

could a l low an odd num ber a t the front of the l ist to e rroneously be deem ed the best la rge even num ber. L e t’s see wha t happens ifwe leave out the ini t ia l va lue .> (reduce (lambda (best item)

(if (and (evenp item) (> item best))itembest))'(7 4 6 5 2))7

Yes, things go horribly, horribly wrong, as a result of not spec ifying an ini t ia l reduce va lue .Another grea t benefi t of the reduce func tion is tha t i t is generic , a s is t rue for a l l these sequence func tions. T his m eans tha t i t

can reduce l ists, a rrays, or strings in exac tly the sam e way, and you can use reduce to write func tions tha t a re oblivious to thediffe rence be tween these diffe rent sequence types.

E arl ie r, I m entioned tha t i t would be convenient to be able to write a single func tion tha t could sum toge ther num bers in l ists ora rrays equa lly well . Now we can write such a func tion:> (defun sum (lst)

(reduce #'+ lst))SUM> (sum '(1 2 3))6> (sum (make-array 5 :initial-contents '(1 2 3 4 5)))15> (sum "blablabla")

Page 187: Land of Lisp - Barski M.D., Conrad

Error: The value #\b is not of type NUMBER.

sum is bl issfully unaware of the diffe rence be tween a rrays and l ists; i t works on both. However, since addit ion doesn’t m ake anysense for charac te rs, the sum func tion re turns an e rror when used on a string.

Another func tion tha t is use ful for i te ra t ing ac ross a sequence is the map func tion. T his func tion is identica l in behavior tomapcar. However, unlike mapcar, the map func tion works on a l l sequence types, not just l ists. You spec ify the type of sequence tore turn from the m apping by passing an extra a rgum ent to the map func tion.

Here is an exam ple of map:

> (map 'list(lambda (x)

(if (eq x #\s)#\Sx))"this is a string")

(#\t #\h #\i #\S #\ #\i #\S #\ #\a #\ #\S #\t #\r #\i #\n #\g)In this exam ple , we’re turning every s charac te r in a string to i ts uppercase version. T he m apping func tion we pass into map

sim ply checks if the current charac te r is an s and re turns the uppercase S i f i t is .

T he result of this ca lcula t ion is a l ist of charac te rs . T his is because we told the map func tion tha t we wanted a l ist a s a

result . Had we asked for a string instead, a string would have been our result .

Two M ore Important Sequence F unctions

T he subseq func tion le ts you pull a subsequence out of a la rger sequence by spec ifying sta rt ing and ending points:> (subseq "america" 2 6)

"eric"As you can see , the word america conta ins the nam e eric, sta rt ing from the second charac te r and ending a t the sixth charac te r.T he sort func tion le ts you pass i t an a rbitra ry func tion to use for the sort ing. In this case , we’re just using the less-than (<)

func tion:> (sort '(5 8 2 4 9 3 6) #'<)

(2 3 4 5 6 8 9)T here a re m any m ore sequence func tions than we’ve discussed so fa r, but the exam ples in this chapte r wil l ge t you off to a good

sta rt .

Note

For a com prehensive l ist of sequence func tions, and indeed a l l Com m on L isp func tions, visi t the Common Lisp Hyperspec a tht tp: / /www.snipurl . com /rz3h0—an exhaustive , but daunting, desc ript ion of a l l Com m on L isp has to offe r.

Page 188: Land of Lisp - Barski M.D., Conrad

Creating Your O wn G ener ic F unctions with Type P redicates

Com m on L isp, l ike virtua lly a l l other L isps, is a dynam ica lly typed language . T his m eans tha t pa ram ete rs or va riables in yourcode can hold any type of da ta—sym bols, strings, num bers, func tions, or wha tever e lse you want to place in them . In fac t , thesam e param ete r or va riable can even hold diffe rent types of da ta a t diffe rent t im es in a running program .

T here fore , i t m akes sense to have a bunch of func tions tha t te l l you whether a va riable has a ce rta in type of da ta in i t . Forinstance , you can check whether you have a num ber with numberp:> (numberp 5)

TT he type predica tes you wil l probably use m ost frequently a re arrayp, characterp, consp, functionp, hash-table-p, listp,

stringp, and symbolp.You can use type predica tes to write func tions tha t handle diffe rent types of da ta generica l ly. Suppose we wanted to write a

func tion tha t le ts us add both num bers or l ists. Here ’s one way we could write such a func tion:> (defun add (a b)

(cond ((and (numberp a) (numberp b)) (+ a b))((and (listp a) (listp b)) (append a b))))ADD> (add 3 4)7> (add '(a b) '(c d))(A B C D)

In this add func tion, we use predica tes to see if the a rgum ents passed in a re num bers or l ists, and then we ac t appropria te ly. If wearen’t given two num bers or two l ists, i t sim ply re turns nil.

Although you can write func tions support ing m ult iple types of da ta using type predica tes, m ost L ispers wouldn’t wri te an addfunc tion this way, for the fol lowing reasons:

A single , monolithic function for al l types:T his is fine for just two types, but i f we wanted to handle a dozen or m ore types, our func tion would quickly turn into a

giant m onstrosi ty.M odifications required to accommodate new cases:

W e would need to change the add func tion whenever we want to support a new type , inc reasing the chance tha t we wouldbreak exist ing code . Idea lly, we would l ike to handle each new si tua tion by i tse lf without touching a lready working code .

H ard to understand:It is ha rd to see exac tly wha t the m ain cond sta tem ent is doing and if the types a re a l l be ing routed to the right place .

P erformance:T he result ing func tion m ight be slow. For instance , a L isp inte rpre te r/com pile r m ight be able to c rea te faste r code for

appending two l ists i f i t knew for sure tha t both i tem s were l ists when the appending happens. However, in our first a t tem pt a tthe add func tion, the type of the two a rgum ents is never rea l ly com ple te ly obvious. Our com pile r would need a bi t of sm arts tobe able to te l l from the condit ion (and (listp a) (listp b)) tha t both variables a re guaranteed to be l ists. L ife would beeasie r for the com pile r i f we explic i t ly sta ted the types of a rgum ents for each type si tua tion.

Because i t is so use ful to be able to have a single func tion tha t does diffe rent things when given ce rta in da ta types, the Com m onL isp com m and defmethod le ts us define m ult iple versions of a func tion tha t each supports diffe rent types. W hen tha t func tion isca l led, L isp checks the a rgum ent types a t the t im e of the ca l l and chooses the correc t ve rsion of the func tion autom atica l ly. T heproper te rm for having a com pile r/ inte rpre te r choose am ong diffe rent ve rsions of a func tion based on a rgum ent types is typedispatching.

Here ’s how we would write our add func tion using defmethod:> (defmethod add ((a number) (b number))

(+ a b))ADD> (defmethod add ((a list) (b list))(append a b))ADD> (add 3 4)7> (add '(a b) '(c d))(A B C D)

As you can see , this ve rsion of the add func tion handles every type of si tua t ion with a separa te func tion, and new cases can beadded without m odifying exist ing code . Overa ll , the code is m uch easie r to understand. Also, the com pile r can see the type of theparam ete rs and m ay be able to write faste r code using this knowledge .

T he defmethod func tion is l ike defun, except tha t i t a l lows us to write m ult iple func tions with the sam e nam e. W hen usingdefmethod, we can explic i t ly sta te the type of each param ete r in the func tion’s a rgum ent l ist so tha t L isp can use these typedec la ra t ions to figure out the correc t ve rsion of add for each si tua tion.

If you’re fam ilia r with the world of OOP, the word method probably has a spec ia l m eaning to you. Since this new com m and isca lled defmethod, does i t have anything to do with OOP? In short , yes. T his com m and can be used not only with Com m on L isp’sbuil t-in types, but a lso with struc tures you’ve c rea ted with defstruct. T he com bina tion of defstruct and defmethod basica l lyconsti tutes a sim ple objec t system .

Now we’ll use this objec t system to write a gam e!

Page 189: Land of Lisp - Barski M.D., Conrad

The O rc Battle G ame

In the Orc Ba tt le gam e, you’re a knight surrounded by 12 m onste rs, engaged in a fight to the dea th. W ith your superior wits andyour repertoire of sword-fighting m aneuvers, you m ust ca re fully stra tegize in your ba tt le with orcs, hydras, and other nasty enem ies.One wrong m ove and you m ay be unable to ki l l them a ll be fore be ing worn down by the ir superior num bers. Using defmethod anddefstruct, le t’s dispa tch som e whoop ass on these verm in!

Page 190: Land of Lisp - Barski M.D., Conrad

G lobal Var iables for the P layer and M onsters

W e’ll want to track three player sta ts: hea lth, agil i ty, and strength. W hen a player’s hea lth reaches ze ro, tha t player wil l die .Agil i ty wil l control how m any a t tacks a player can perform in a single round of ba tt le , and strength wil l control the fe roc ity of thea ttacks. As the gam e progresses, each of these wil l change and a ffec t gam eplay and stra tegy in subtle ways.(defparameter *player-health* nil)

(defparameter *player-agility* nil)(defparameter *player-strength* nil)

W e’ll store our m onste rs in an a rray ca l led *monsters*. T his a rray wil l be he terogeneous, m eaning i t can conta in diffe rent typesof m onste rs, be they orcs, hydras, or anything e lse . W e’ll c rea te our m onste r types with defstruct. Of course , we st i l l need tofigure out how to handle each type in the l ist in a m eaningful way—tha t’s where we’l l use L isp’s generic fea tures.

W e’ll a lso define a l ist of func tions for building m onste rs tha t we’l l store in the variable *monster-builders*. As we write thecode for each type of m onste r, we’l l c rea te a func tion tha t builds a m onste r of each type . W e’ll then push each of these m onste rbuilders onto this l ist . Having a l l the builder func tions in this l ist wil l m ake i t easy for us to c rea te random m onste rs a t wil l for ourgam e.

Fina lly, we’l l c rea te the variable *monster-num* to control how m any opponents our knight m ust fight . Change this va riable toincrease (or decrease) the difficulty leve l of Orc Ba tt le .(defparameter *monsters* nil)

(defparameter *monster-builders* nil)(defparameter *monster-num* 12)

Page 191: Land of Lisp - Barski M.D., Conrad

M ain G ame F unctions

Now we’re ready to write our first rea l code for the gam e, sta rt ing with the big pic ture func tions tha t drive the rest of the system .First , we’l l de fine a func tion ca lled orc-battle. T his func tion wil l ini t ia l ize the m onste rs and sta rt the gam e loop and, once the

ba tt le ends, i t wil l de te rm ine the vic tor and print the appropria te ending m essage for the gam e. As you can see , orc-battle ca l lsplenty of he lper func tions to do the ac tua l work:(defun orc-battle ()

(init-monsters)(init-player)

(game-loop)

(when (player-dead)(princ "You have been killed. Game Over."))

(when (monsters-dead)(princ "Congratulations! You have vanquished all of your foes.")))

At the top, we ca ll the ini t ia l iza t ion func tions for the m onste rs and the player . T hen we sta rt the m ain gam e loop .T he gam e loop wil l keep running unti l e i the r the player or the m onste rs a re dead. W e’ll print a gam e-ending m essage depending

on whether the player or m onste rs died.Next, we’l l c rea te the func tion game-loop to handle the gam e loop. T his func tion handles a round of the ba tt le , and then ca lls

i tse lf recursive ly for the fol lowing round:(defun game-loop ()

(unless (or (player-dead) (monsters-dead))

(show-player)

(dotimes (k (1+ (truncate (/ (max 0 *player-agility*) 15))))(unless (monsters-dead)(show-monsters)(player-attack)))(fresh-line)

(map 'list(lambda(m)

(or (monster-dead m) (monster-attack m)))*monsters*)

(game-loop)))T he game-loop func tion handles the repea ted cyc les of m onste r and player a t tacks. As long as both part ies in the fight a re st i l l

a l ive , the func tion wil l fi rst show som e inform ation about the player in the RE PL .Next, we a l low the player to a t tack the m onste rs. T he game-loop func tion uses the player’s agil i ty to m odula te how m any

a ttacks can be launched in a single round of ba tt le , using som e fudge fac tors to transform the agil i ty to a sm all , appropria te

num ber . W hen the gam e begins, the player wil l have three a t tacks per round. L a te r stages of ba tt le could cause this num berto drop to a single a t tack per round.

T he ca lcula ted agil i ty fac tor for our player a t tack loop is passed into the dotimes com m and, which takes a variable nam eand a num ber n, and runs a chunk of code n t im es:> (dotimes (i 3)

(fresh-line)(princ i)(princ ". Hatchoo!"))0. Hatchoo!1. Hatchoo!2. Hatchoo!

T he dotimes func tion is one of Com m on L isp’s looping com m ands (looping is covered in m ore de ta i l in Chapte r 10).Afte r the player has a t tacked, we a l low the m onste rs to a t tack. W e do this by i te ra t ing through our l ist of m onste rs with the map

func tion . E very type of m onste r has a spec ia l monster-attack com m and, which we’l l ca l l a s long as the m onste r is st i l l

a l ive .Fina lly, the game-loop func tion ca lls i tse lf recursive ly, so tha t the ba tt le can continue unti l one side or the other has been

vanquished .

Page 192: Land of Lisp - Barski M.D., Conrad

P layer M anagement F unctions

T he func tions we need for m anaging the player’s a t tributes (hea lth, agil i ty, and strength) a re very sim ple . Following a re thefunc tions we need to ini t ia l ize players, to see if they’ve died, and to output the ir a t tributes:(defun init-player ()

(setf *player-health* 30)(setf *player-agility* 30)(setf *player-strength* 30))

(defun player-dead ()(<= *player-health* 0))

(defun show-player ()(fresh-line)(princ "You are a valiant knight with a health of ")(princ *player-health*)(princ ", an agility of ")(princ *player-agility*)(princ ", and a strength of ")(princ *player-strength*))

T he player-attack func tion le ts us m anage a player’s a t tack:(defun player-attack ()

(fresh-line)

(princ "Attack style: [s]tab [d]ouble swing [r]oundhouse:")

(case (read)

(s (monster-hit (pick-monster)

(+ 2 (randval (ash *player-strength* −1)))))

(d (let ((x (randval (truncate (/ *player-strength* 6)))))(princ "Your double swing has a strength of ")(princ x)(fresh-line)

(monster-hit (pick-monster) x)(unless (monsters-dead)

(monster-hit (pick-monster) x))))

(otherwise (dotimes (x (1+ (randval (truncate (/ *player-strength* 3)))))(unless (monsters-dead)

(monster-hit (random-monster) 1))))))

First , this func tion prints out som e diffe rent types of a t tacks from which the player can choose . As you can see , the playeris offe red three possible a t tacks: a stab, a double swing, and a roundhouse swing. W e read in the player’s se lec t ion, and then handle

each type of a t tack in a case sta tem ent .T he stab a t tack is the m ost fe roc ious a t tack and can be de live red aga inst a single foe . Since a stab is pe rform ed aga inst a single

enem y, we wil l fi rst ca l l the pick-monster func tion to le t the player choose whom to a t tack . T he a t tack strength isca lcula ted from the *player-strength*, using a random fac tor and som e other l i t t le tweaks to genera te a nice , but never too

powerful , a t tack strength . Once the player has chosen a m onste r to a t tack and the a t tack strength has been ca lcula ted, we

ca ll the monster-hit func tion to apply the a t tack .Unlike the stab a t tack, the double swing is weaker, but a l lows two enem ies to be a t tacked a t once . An addit iona l benefi t of the

a ttack is tha t the knight can te l l , a s the swing begins, how strong i t wil l be—inform ation tha t can then be used to choose the bestenem ies to a t tack m idswing. T his extra fea ture of the double swing adds stra tegic depth to the gam e. Otherwise , the double -swing

code is sim ila r to the stab code , print ing a m essage and a l lowing the player to choose whom to a t tack. In this case , however,

two m onste rs can be chosen .T he fina l a t tack, the roundhouse swing, is a wild, chaotic a t tack tha t does not disc rim ina te am ong the enem ies. W e run through

a dotimes loop based on the player’s strength and then a t tack random foes m ult iple t im es. However, each a t tack is ve ry

weak, with a strength of only 1 .T hese a t tacks m ust be used correc tly, a t the right stages of a ba tt le , in order to achieve vic tory. T o add som e random ness to the

a ttacks in the player-attack func tion, we used the randval he lper func tion to genera te random num bers. It is de fined as fol lows:

Page 193: Land of Lisp - Barski M.D., Conrad

(defun randval (n)(1+ (random (max 1 n))))

T he randval func tion re turns a random num ber from one to n, while m aking sure tha t no m atte r how sm all n is, a t least thenum ber 1 wil l be re turned. Using randval instead of just the random func tion for genera t ing random num bers gives a rea l i ty checkto the random ness of the gam e, since 0 doesn’t m ake sense for som e of the va lues we use in our ca lcula t ions. For instance , even theweakest player or m onste r should a lways have an a t tack strength of a t least 1.

T he random func tion used by randval is the canonica l random va lue func tion in L isp. It can be used in severa l diffe rent ways,though m ost frequently i t is used by passing in an integer n and rece iving a random integer from 0 to n−1:> (dotimes (i 10)

(princ (random 5))(princ " "))1 2 2 4 0 4 2 4 2 3

Page 194: Land of Lisp - Barski M.D., Conrad

H elper F unctions for P layer Attacks

Our player-attack func tion needs two he lper func tions to do i ts job. First , i t needs a random-monster func tion tha t picks am onste r to ta rge t for the chaotic roundhouse a t tack, while ensuring tha t the chosen m onste r isn’t a lready dead:(defun random-monster ()

(let ((m (aref *monsters* (random (length *monsters*)))))(if (monster-dead m)

(random-monster)

m)))

T he random-monster func tion first picks a random m onste r out of the a rray of m onste rs and stores i t in the variable m .Since we want to pick a l iving m onste r to a t tack, we recursive ly try the func tion aga in if we inadvertently picked a dead m onste r

. Otherwise , we re turn the chosen m onste r .T he player-attack func tion a lso needs a func tion tha t a l lows the player to pick a m onste r to ta rge t for the nonrandom a ttacks.

T his is the job of the pick-monster func tion:(defun pick-monster ()

(fresh-line)

(princ "Monster #:")

(let ((x (read)))

(if (not (and (integerp x) (>= x 1) (<= x *monster-num*)))(progn (princ "That is not a valid monster number.")(pick-monster))

(let ((m (aref *monsters* (1-x))))

(if (monster-dead m)(progn (princ "That monster is alread dead.")(pick-monster))

m)))))

In order to le t the player pick a m onste r to a t tack, we first need to display a prom pt and read in the player’s choice .

T hen we need to m ake sure the player chose an integer tha t isn’t too big or too sm all . If this has happened, we print am essage and ca ll pick-monster aga in to le t the player choose aga in. Otherwise , we can sa fe ly place the chosen m onste r in the

variable m .Another e rror the player could m ake is to a t tack a m onste r tha t is a lready dead. W e check for this possibi l i ty next and, once

aga in, a l low the player to m ake another se lec t ion . Otherwise , the player has successfully m ade a choice , and we re turn the

se lec ted m onste r as a result .Now le t’s work on our m onste rs.

Page 195: Land of Lisp - Barski M.D., Conrad

M onster M anagement F unctions

W e’ll use the init-monsters func tion to ini t ia l ize a l l the bad guys stored in the *monsters* a rray. T his func tion wil l random lypick func tions out of the *monster-builders* l ist and ca ll them with funcall to build the m onste rs:(defun init-monsters ()

(setf *monsters*

(map 'vector(lambda (x)

(funcall (nth (random (length *monster-builders*))*monster-builders*)))

(make-array *monster-num*))))

First , the init-monsters func tion builds an em pty a rray to hold the m onste rs . T hen i t maps ac ross this a rray to fi l l i t up

. In the lambda func tion, you can see how random m onste rs a re c rea ted by funcalling random func tions in our l ist of m onste r

builders .Next, we need som e sim ple func tions for checking if the m onste rs a re dead. Notice how we use the every com m and on the

*monsters* a rray to see if the func tion monster-dead is t rue for every m onste r. T his wil l te l l us whe ther the entire m onste rpopula t ion is dead.(defun monster-dead (m)

(<= (monster-health m) 0))

(defun monsters-dead ()(every #'monster-dead *monsters*))

W e’ll use the show-monsters func tion to display a l ist ing of a l l the m onste rs. T his func tion wil l , in turn, de fe r pa rt of the workto another func tion, so i t doesn’t ac tua lly need to know a lot about the diffe rent m onste r types:(defun show-monsters ()

(fresh-line)(princ "Your foes:")

(let ((x 0))

(map 'list(lambda (m)(fresh-line)(princ " ")

(princ (incf x))(princ ". ")(if (monster-dead m)

(princ "**dead**")(progn (princ "(Health=")

(princ (monster-health m))(princ ") ")

(monster-show m))))*monsters*)))

Since our player wil l need to choose m onste rs with a num ber, we wil l m ainta in a count as we loop through m onste rs in our l ist ,

in the variable x . T hen we map through our m onste r l ist , ca l l ing a lambda func tion on each m onste r, which wil l print out

som e pre t ty text for each m onste r . W e use our x va riable to print out the num ber for each m onste r in our num bered l ist

. As we do this, we use the incf func tion, which wil l inc rem ent x a s we work through the l ist .

For dead m onste rs, we won’t print m uch about them , just a m essage showing tha t they a re dead . For l iving m onste rs, we

ca ll generic m onste r func tions, ca lcula t ing the hea lth and genera t ing the m onste r desc ript ion in a spec ia l ized way foreach diffe rent type of foe .

Page 196: Land of Lisp - Barski M.D., Conrad

The M onsters

So fa r, we haven’t seen any func tions tha t rea l ly give l i fe to the m onste rs. L e t’s fix tha t .First , we’l l desc ribe a generic m onste r.

The G ener ic M onster

As you would expec t , orcs, hydras, and other bad guys a l l have one thing in com m on: a hea lth m ete r tha t de te rm ines how m anyhits they can take before they die . W e can capture this behavior in a monster struc ture :(defstruct monster (health (randval 10)))T his use of the defstruct func tion takes advantage of a spec ia l fea ture : W hen we dec la re each slot in the struc ture (in this case ,

health) we can put pa rentheses a round the nam e and add a default va lue for tha t slot . But m ore im portant , we can dec la re a formtha t wil l be eva lua ted when a new monster is c rea ted. Since this form ca lls randval, every m onste r wil l sta rt the ba tt le with adiffe rent , random , hea lth.

L e t’s t ry c rea ting som e m onste rs:> (make-monster)

#S(MONSTER :HEALTH 7)> (make-monster)#S(MONSTER :HEALTH 2)> (make-monster)#S(MONSTER :HEALTH 5)

W e a lso need a func tion tha t takes away a m onste r’s hea lth when i t’s a t tacked. W e’ll have this func tion output a m essageexpla ining what happened, inc luding a m essage to be displayed when the m onste r dies. However, instead of c rea ting this func tionwi t h defun, we’l l use the generic defmethod, which wil l le t us display spec ia l m essages when the knight bea ts on part icula rm onste rs:(defmethod monster-hit (m x)

(decf (monster-health m) x)(if (monster-dead m)(progn (princ "You killed the ")

(princ (type-of m))(princ "! "))(progn (princ "You hit the ")

(princ (type-of m))(princ ", knocking off ")(princ x)(princ " health points! "))))

T h e decf func tion is a va riant of setf tha t le ts us subtrac t an am ount from a variable . T he type-of func tion le ts

monster-hit pre tend i t knows the type of the m onste r tha t was hi t . T his func tion can be used to find the type of anyL isp va lue :> (type-of 'foo)

SYMBOL> (type-of 5)INTEGER> (type-of "foo")ARRAY> (type-of (make-monster))MONSTER

Currently, the type of a m onste r wil l a lways be monster, but soon we’l l have this va lue change for each m onste r type .W e can a lso use two m ore generic m ethods to c rea te m onste rs: monster-show and monster-attack.T he monster-attack func tion doesn’t ac tua lly do anything. T his is because a l l our m onste r a t tacks wil l be so unique tha t the re ’s

no point in defining a generic a t tack. T his func tion is sim ply a placeholder.(defmethod monster-show (m)

(princ "A fierce ")(princ (type-of m)))

(defmethod monster-attack (m))Now tha t we have som e generic m onste r code , we can fina lly c rea te som e ac tua l bad guys!

The Wicked O rc

T he orc is a sim ple foe . He can de liver a strong a t tack with his c lub, but otherwise he is pre t ty harm less. E very orc has a c lubwith a unique a t tack leve l . Orcs a re best ignored, unless the re a re orcs with an unusua lly powerful c lub a t tack tha t you want to cullfrom the herd a t the beginning of a ba tt le .

T o c rea te the orc , we define an orc da ta type with defstruct. Here , we wil l use another advanced fea ture of defstruct todec la re tha t the orc inc ludes a l l the fie lds of monster.

By inc luding the fie lds from our monster type in our orc type , the orc wil l be able to inheri t the fie lds tha t apply to a l lm onste rs, such as the health fie ld. T his is sim ila r to wha t you can accom plish in popula r languages such as C++ or Java by defininga generic c lass and then c rea ting other, m ore spec ia l ized, c lasses tha t inheri t from this generic c lass.

Once the struc ture is dec la red, we push the make-orc func tion (autom atica l ly genera ted by the defstruct) onto our l ist of*monster-builders*:(defstruct (orc (:include monster)) (club-level (randval 8)))

(push #'make-orc *monster-builders*)

Page 197: Land of Lisp - Barski M.D., Conrad

Note

Notice how powerful this approach is. W e can c rea te as m any new m onste r types as we want, ye t we’l l never need to change ourbasic Orc Ba tt le code . T his is possible only in languages l ike L isp, which a re dynam ica lly typed and support func tions as first-c lassva lues. In sta t ica l ly typed program m ing languages, the m ain Orc Ba tt le code would need som e hardwired way of ca l l ing theconstruc tor for each new type of m onste r. W ith first-c lass func tions, we don’t need to worry about this.

Now le t’s spec ia l ize our monster-show and monster-attack func tions for orcs. Notice these a re defined in the sam e way as theearl ie r ve rsions of these func tions, except tha t we explic i t ly dec la re tha t these func tions a re orc -spec ific in the a rgum ent l ists:(defmethod monster-show ((m orc))

(princ "A wicked orc with a level ")

(princ (orc-club-level m))(princ " club"))

(defmethod monster-attack ((m orc))

(let ((x (randval (orc-club-level m))))

Page 198: Land of Lisp - Barski M.D., Conrad

(princ "An orc swings his club at you and knocks off ")(princ x)(princ " of your health points. ")(decf *player-health* x)))

T he one unique thing about our orc type is tha t each orc has an orc-club-level fie ld. T hese orc -spec ific ve rsions of monster-

show and monster-attack take this fie ld into account. In the monster-show func tion, we display this c lub leve l , so tha t theplayer can gauge the danger posed by each orc .

In the monster-attack func tion, we use the leve l of the c lub to dec ide how badly the player is hi t by the c lub .

The M alic ious H ydra

T he hydra is a ve ry nasty enem y. It wil l a t tack you with i ts m any heads, which you’l l need to chop off to defea t i t . T he hydra ’sspec ia l power is tha t i t can grow a new head during each round of ba tt le , which m eans you want to defea t i t a s ea rly as possible .(defstruct (hydra (:include monster)))

(push #'make-hydra *monster-builders*)

(defmethod monster-show ((m hydra))(princ "A malicious hydra with ")

(princ (monster-health m))(princ " heads."))

(defmethod monster-hit ((m hydra) x)

(decf (monster-health m) x)(if (monster-dead m)(princ "The corpse of the fully decapitated and decapacitatedhydra falls to the floor!")

(progn (princ "You lop off ")(princ x)(princ " of the hydra's heads! "))))

(defmethod monster-attack ((m hydra))(let ((x (randval (ash (monster-health m) −1))))(princ "A hydra attacks you with ")(princ x)(princ " of its heads! It also grows back one more head! ")

(incf (monster-health m))

Page 199: Land of Lisp - Barski M.D., Conrad

(decf *player-health* x)))T he code for handling the hydra is sim ila r to the code for handling the orc . T he m ain diffe rence is tha t a hydra ’s hea lth a lso ac ts

as a stand-in for the num ber of hydra heads. In other words, a hydra with three hea lth points wil l have three heads, as well .T here fore , when we write our hydra -spec ific monster-show func tion, we use the m onste r’s hea lth to print a pre t ty m essage about

the num ber of heads on the hydra .Another diffe rence be tween the orc and the hydra is tha t an orc doesn’t do anything part icula rly inte rest ing when i t is hi t by the

player. Because of this, we didn’t need to write a custom monster-hit func tion for the orc ; the orc sim ply used the genericmonster-hit func tion we c rea ted for a generic monster.

A hydra , on the other hand, does som ething inte rest ing when i t is hi t : It loses heads! W e there fore c rea te a hydra -spec ific

monster-hit func tion, where heads a re rem oved with every blow, which am ounts to lowering the hydra ’s hea lth . Also, we can

now print a dram atic m essage about how the knight lopped off sa id heads .T he hydra ’s monster-attack func tion is aga in sim ila r to tha t for the orc . T he one inte rest ing diffe rence is tha t we increm ent the

hea lth with every a t tack, so tha t the hydra grows a new head every turn .

The Slimy Slime M old

T he sl im e m old is a unique m onste r. W hen i t a t tacks you, i t wil l wrap i tse lf a round your legs and im m obil ize you, le t t ing theother bad guys finish you off. It can a lso squirt goo in your face . You m ust think quickly in ba tt le to dec ide if i t ’s be tte r to finishthe sl im e off ea rly in order to m ainta in your agil i ty, or ignore i t to focus on m ore vic ious foes first . (Rem em ber tha t by loweringyour agil i ty, the sl im e m old wil l decrease the num ber of a t tacks you can de liver in la te r rounds of ba tt le . )

(defstruct (slime-mold (:include monster)) (sliminess (randval 5)))(push #'make-slime-mold *monster-builders*)

(defmethod monster-show ((m slime-mold))(princ "A slime mold with a sliminess of ")(princ (slime-mold-sliminess m)))

(defmethod monster-attack ((m slime-mold))

(let ((x (randval (slime-mold-sliminess m))))(princ "A slime mold wraps around your legs and decreases your agility by ")(princ x)(princ "! ")

(decf *player-agility* x)

(when (zerop (random 2))(princ "It also squirts in your face, taking away a health point! ")

(decf *player-health*))))T he monster-attack func tion for the sl im e m old m ust do som e spec ia l things, which a l low i t to im m obil ize the player. First , i t

uses the sl im e m old’s sl im iness (which is genera ted when each sl im e m old is buil t) to genera te a random a ttack aga inst the player,

Page 200: Land of Lisp - Barski M.D., Conrad

stored in the variable x . Unlike m ost other a t tacks in the gam e, this sl im e m old a t tack a ffec ts the agil i ty of players, ra ther

than the ir hea lth .However, i t would be pointless if the sl im e m old couldn’t a t tack the player’s hea lth a t least a l i t t le , or the ba tt le could end

awkwardly, with the player and sl im e m old frozen in place for a l l t im e . T here fore , the sl im e m old a lso has a superwim py squirt

a t tack tha t happens during ha lf of a l l a t tacks , but subtrac ts only a single hea lth point from the player .

The Cunning Brigand

T he brigand is the sm artest of a l l your foes. He can use his whip or sl ingshot and wil l t ry to neutra l ize your best asse ts. Hisa t tacks a re not powerful , but they a re a consistent two points for every round.(defstruct (brigand (:include monster)))

(push #'make-brigand *monster-builders*)

(defmethod monster-attack ((m brigand))

(let ((x (max *player-health* *player-agility* *player-strength*)))

(cond ((= x *player-health*)(princ "A brigand hits you with his slingshot,taking off 2 health points! ")(decf *player-health* 2))

((= x *player-agility*)(princ "A brigand catches your leg with his whip,taking off 2 agility points! ")(decf *player-agility* 2))

((= x *player-strength*)(princ "A brigand cuts your arm with his whip,taking off 2 strength points! ")(decf *player-strength* 2)))))

Page 201: Land of Lisp - Barski M.D., Conrad
Page 202: Land of Lisp - Barski M.D., Conrad

T he first thing the wily brigand does when perform ing an a t tack is to look a t the player’s hea lth, agil i ty, and strength, and

choose the max of those three as the focus of his a t tack . If severa l of the a t tributes a re equa lly la rge , the brigand wil l choosehea lth over agil i ty and agil i ty over strength as the focus of a t tack. If hea lth is the la rgest va lue , the player is hi t with a sl ingshot

. If agil i ty is the la rgest , the brigand wil l whip the player’s leg . If strength is the la rgest , the brigand wil l whip the

player’s a rm .W e have now com ple te ly defined a l l of our m onste rs for our gam e!

Page 203: Land of Lisp - Barski M.D., Conrad

To Battle !

T o sta rt the gam e, ca l l orc-battle from the RE PL :> (orc-battle)

You are a valiant knight with a health of 30, an agility of 30, and a strength of 30Your foes:1. (Health=10) A wicked orc with a level 5 club2. (Health=3) A malicious hydra with 3 heads.3. (Health=9) A fierce BRIGAND4. (Health=3) A malicious hydra with 3 heads.5. (Health=3) A wicked orc with a level 2 club6. (Health=7) A malicious hydra with 7 heads.7. (Health=6) A slime mold with a sliminess of 28. (Health=5) A wicked orc with a level 2 club9. (Health=9) A fierce BRIGAND10. (Health=2) A wicked orc with a level 6 club11. (Health=7) A wicked orc with a level 4 club12. (Health=8) A slime mold with a sliminess of 2

T hat hydra with seven heads looks pre t ty gnarly—le t’s finish i t off first with a stab:Attack style: [s]tab [d]ouble swing [r]oundhouse:s

Monster #:6The corpse of the fully decapitated and decapacitated hydra falls to the floor!Your foes:1. (Health=10) A wicked orc with a level 5 club2. (Health=3) A malicious hydra with 3 heads.3. (Health=9) A fierce BRIGAND4. (Health=3) A malicious hydra with 3 heads.5. (Health=3) A wicked orc with a level 2 club6. **dead**7. (Health=6) A slime mold with a sliminess of 28. (Health=5) A wicked orc with a level 2 club9. (Health=9) A fierce BRIGAND10. (Health=2) A wicked orc with a level 6 club11. (Health=7) A wicked orc with a level 4 club12. (Health=8) A slime mold with a sliminess of 2

No other bad guy rea l ly stands out , so we’l l t ry a roundhouse to bring down som e of those hea lth num bers overa l l :Attack style: [s]tab [d]ouble swing [r]oundhouse:r

You hit the SLIME-MOLD,knocking off 1 health points! You hit the SLIME-MOLD, knocking off 1 health points!You hit the ORC, knocking off 1 health points! You lop off 1 of the hydra's heads!You lop off 1 of the hydra's heads! You lop off 1 of the hydra's heads! You hit theORC, knocking off 1 health points! The corpse of the fully decapitated and decapacitated hydra falls to the floor! You hit the ORC, knocking off 1 health points! You hitthe ORC, knocking off 1 health points! You hit the ORC, knocking off 1 health points!Your foes:1. (Health=9) A wicked orc with a level 5 club2. (Health=2) A malicious hydra with 2 heads.3. (Health=9) A fierce BRIGAND4. **dead**5. (Health=2) A wicked orc with a level 2 club6. **dead**7. (Health=4) A slime mold with a sliminess of 28. (Health=3) A wicked orc with a level 2 club9. (Health=9) A fierce BRIGAND10. (Health=2) A wicked orc with a level 6 club11. (Health=6) A wicked orc with a level 4 club12. (Health=8) A slime mold with a sliminess of 2

Grea t! T ha t even kil led one of the weaker enem ies. Now, with ful l agil i ty, we have three a t tacks per round. T his m eans weshould use our last a t tack to stra tegica lly take out som e of the m ore powerful bad guys. L e t’s use the double swing:Attack style: [s]tab [d]ouble swing [r]oundhouse:d

Your double swing has a strength of 3Monster #:8You killed the ORC!Monster #:10You killed the ORC!An orc swings his club at you and knocks off 5 of your health points. A hydraattacks you with 1 of its heads! It also grows back one more head! Abrigand catches your leg with his whip, taking off 2 agility points! An orcswings his club at you and knocks off 1 of your health points. A slime mold wrapsaround your legs and decreases your agility by 2! It also squirts in your face,taking away a health point! A brigand cuts your arm with his whip, taking off 2strength points! An orc swings his club at you and knocks off 1 of your healthpoints. A slime mold wraps around your legs and decreases your agility by 1!You are a valiant knight with a health of 21, an agility of 25, and a strength of 28Your foes:1. (Health=9) A wicked orc with a level 5 club2. (Health=3) A malicious hydra with 3 heads.3. (Health=9) A fierce BRIGAND4. **dead**5. (Health=2) A wicked orc with a level 2 club6. **dead**7. (Health=4) A slime mold with a sliminess of 28. **dead**9. (Health=9) A fierce BRIGAND10. **dead**

Page 204: Land of Lisp - Barski M.D., Conrad

11. (Health=6) A wicked orc with a level 4 club12. (Health=8) A slime mold with a sliminess of 2

T hey got us pre t ty good, but we st i l l have plenty of fight le ft . T his ba tt le isn’t over ye t!As you can see , ca re ful stra tegy is needed if you want to survive Orc Ba tt le . I hope you enjoy this new gam e!

Page 205: Land of Lisp - Barski M.D., Conrad

What You've Learned

In this chapte r, we discussed the m ore advanced da ta struc tures in Com m on L isp. W e then used this to c rea te a m onste r-fightinggam e. Along the way, you lea rned the fol lowing:

Arrays a re sim ila r to l ists, but a l low you to access an i tem a t a spec ific offse t m ore e ffic iently.Hash tables a re sim ila r to a l ists, but le t you look up the va lue assoc ia ted with a key m ore e ffic iently.Using a rrays and hash tables in the appropria te places wil l usua lly m ake your code m uch faste r.T he only true way to te l l i f changing a da ta struc ture or a lgori thm m akes your program faste r is to t im e your code with the

time com m and.Com m on L isp has generic func tions tha t can be used aga inst m ult iple da ta types. T he m ost use ful of these a re sequence

func tions tha t can transparently handle l ists, a rrays, and strings.You can c rea te objec ts with propert ies in l ist using the defstruct com m and.

Page 206: Land of Lisp - Barski M.D., Conrad

P art III. Lisp is H acking

Page 207: Land of Lisp - Barski M.D., Conrad

loop and format: The Seedy Underbe lly of Lisp

Previously, we looked a t the core of the Com m on L isp language and adm ired i ts succ inc tness and e legance . However, the re a rea lso som e darker, seedie r pa rts of L isp buil t a round this core tha t have a ce rta in charm of the ir own. T hey m ay lack the beauty ofthe L isp core , but they easi ly m ake up for i t with the ir power. T hese parts of the language a re a rea l de light for any budding L isphacker.

T he extensions we’l l cover in this sec t ion, loop and format, place a strong em phasis on power over m athem atica l e legance . T hishas led to occasiona l controversy am ong L isp program m ers, som e of whom question whether the power provided by these com m andsis worth the trade-off in e legance . T hese program m ers be lieve tha t loop and format should be avoided when writ ing any se riouscode .

But the re is one grea t reason to lea rn and use these com m ands: T hey em body the flexibil i ty and extensibil i ty of L isp. Since L ispis (a rguably) the m ost flexible program m ing language ava ilable , hackers have been extending i t with thousands of the ir own hacksfor decades. loop and format, which a re am ong the m ost successful of these extensions, had to be rea l ly spec tacula r to survive inthe Darwinian ba tt le fie ld.

Page 208: Land of Lisp - Barski M.D., Conrad

Chapter 10. Looping with the loop Command

T h e loop and format com m ands a re powerful and hacker-friendly. T hough m ost of the func tiona li ty they offe r is ava ilablee lsewhere in the L isp language , these highly spec ia l ized com m ands a re worth lea rning if you l ike te rse code . W e’ll look a t loop inthis chapte r. T he next chapte r covers format.

Page 209: Land of Lisp - Barski M.D., Conrad

The loop M acro

Any type of looping you would ever want to do inside a com pute r program can be accom plished with the loop m acro. Here ’s asim ple exam ple :> (loop for i

below 5sum i)10

T his code adds toge ther the na tura l num bers be low 5, l ike this:

0 + 1 + 2 + 3 + 4 = 10You can see tha t this loop com m and doesn’t work in the way a proper L isp com m and should. First of a l l , i t ’s pa renthe tica l ly

cha llenged. Never before have we had seven tokens in a row without pa rentheses!

W hat m akes i t even less L ispy is tha t som e of these extra tokens (for, below, and sum) appear to have spec ia l m eanings. Reca llfrom Chapte r 3 tha t the first token in a form (the one im m edia te ly a fte r the opening parenthesis) is typica lly wha t dec ides thebasic behavior of the code , while the rest of the form conta ins param ete rs. W ithin the loop m acro, severa l of these “m agic tokens”fundam enta l ly a ffec t the loop’s behavior. Here ’s wha t they m ean:

for a l lows you to dec la re a variable (in this case , nam ed i) tha t i te ra tes through a range of va lues. By default , i twil l count through the integers sta rt ing a t ze ro.below te l ls the for construc t to ha lt when i t reaches the spec ified va lue (in this case , 5), exc luding the va lue

itse lf.sum adds toge ther a l l va lues of a given expression (in this case , the expression is just i) and m akes the loop re turn

tha t num ber.

Page 210: Land of Lisp - Barski M.D., Conrad

Some loop Tricks

T he loop m acro has a veri table cornucopia of spec ia l tokens tha t m ake just about any kind of behavior possible . L e t’s look a tsom e of the possibi l i t ie s.

Counting from a Starting P oint to an Ending P oint

By using from and to c lauses, you can m ake the for construc t count through any spec ific range of integers:> (loop for i

from 5to 10sum i)45

Iterating Through Values in a List

In the fol lowing exam ple , we i te ra te through va lues in a l ist using the in token:> (loop for i

in '(100 20 3)sum i)123

doing Stuff in a Loop

T he do token takes an a rbitra ry expression and executes i t inside the loop:> (loop for i

below 5do (print i))01234

Doing Stuff Under Certain Conditions

T he when token le ts you run the fol lowing part of the loop only as needed:> (loop for i

below 10when (oddp i)sum i)25

Notice tha t only the sum of the odd num bers is re turned.

Breaking out of a Loop Ear ly

T he following loop uses severa l new tricks:

Page 211: Land of Lisp - Barski M.D., Conrad

> (loop for ifrom 0do (print i)when (= i 5)return 'falafel)012345

FALAFELNotice tha t the re ’s nothing in the for pa rt of the loop tha t te l ls i t to stop counting num bers—it goes from zero off to infini ty.

However, once we reach 5, the when c lause triggers the loop to im m edia te ly re turn the va lue 'falafel.

Collec ting a List of Values

Page 212: Land of Lisp - Barski M.D., Conrad

T he collect c lause le ts you re turn m ore than one i tem from the loop, in the form of a l ist . T his com m and is use ful when youneed to m odify each i tem in a l ist , a s in the fol lowing exam ple :> (loop for i

in '(2 3 4 5 6)collect (* i i))(4 9 16 25 36)

Using M ultiple for Clauses

It’s possible for a loop m acro to have m ore than one for c lause . Consider the fol lowing exam ple :(loop for x below 10

for y below 10collect (+ x y))

How m any num bers do you think wil l be re turned as a result? T here a re two possibil i t ie s: E ither i t inc rem ents x and y a t thesam e t im e and re turns a l ist of 10 i tem s, or i t i te ra tes x and y in a nested fashion and re turns 100 num bers. T he answer is theform er:> (loop for x below 10

for y below 10collect (+ x y))(0 2 4 6 8 10 12 14 16 18)

As you can see , both num bers inc rem ented a t the sam e t im e be tween 0 and 9.If the re a re m ult iple for c lauses in a Com m on L isp loop, each one wil l be checked, and the loop wil l stop when any one of the

c lauses runs out of va lues. T his m eans tha t for c lauses do not loop independently ac ross m ult iple looping variables, so if you loopon two ranges of 10 va lues each, i t wil l st i l l just loop 10 t im es.

However, som etim es you want to genera te the Cartesian produc t be tween m ult iple ranges. In other words, you want a loop to runonce for every possible com bina tion of two or m ore ranges. T o accom plish this, you need to use nested loops for x and y:> (loop for x below 10

collect (loop for y below 10collect (+ x y)))((0 1 2 3 4 5 6 7 8 9) (1 2 3 4 5 6 7 8 9 10) (2 3 4 5 6 7 8 9 10 11)(3 4 5 6 7 8 9 10 11 12) (4 5 6 7 8 9 10 11 12 13) (5 6 7 8 9 10 11 12 13 14)(6 7 8 9 10 11 12 13 14 15) (7 8 9 10 11 12 13 14 15 16)(8 9 10 11 12 13 14 15 16 17) (9 10 11 12 13 14 15 16 17 18))

In this case , we’ve c rea ted 10 l ists of 10 i tem s each, looping for a tota l of 100 i tem s.Also, notice tha t using a for va riable sta rt ing a t ze ro, such as the i va riable in the fol lowing exam ple , provides a c lean way to

track the index num ber of i tem s in a l ist :> (loop for i

from 0for day

Page 213: Land of Lisp - Barski M.D., Conrad

in '(monday tuesday wednesday thursday friday saturday sunday)collect (cons i day))((0 . MONDAY) (1 . TUESDAY) (2 . WEDNESDAY)(3 . THURSDAY) (4 . FRIDAY) (5 . SATURDAY) (6 . SUNDAY))

You m ight think we’ve covered every conce ivable varia t ion of looping a t this point . If so, you a re grave ly m istaken. Behold!T he Periodic T able of the L oop Macro!

Page 214: Land of Lisp - Barski M.D., Conrad
Page 215: Land of Lisp - Barski M.D., Conrad
Page 216: Land of Lisp - Barski M.D., Conrad

Everything You Ever Wanted to K now About loop

T he individua l exam ples we’ve discussed so fa r give only the brie fest hint of the ful l capabil i t ie s of loop. But fea r not! You nowhave the world’s first and only Periodic T able of the L oop Macro. Just tape i t to your m onitor, glue i t to your walle t , or la se r-e tchit direc t ly into your re t ina , and you’l l be guaranteed to reach loop profic iency in no t im e!

Alm ost every lega l com m and tha t can be used in a loop m acro is covered by the periodic table . It shows how to m anipula te hashtables and a rrays, and perform spec ia l looping opera t ions. E ach square in the periodic table conta ins an exam ple . If you run theexam ple , you should be able to figure out the behavior of the given com m and.

Page 217: Land of Lisp - Barski M.D., Conrad

Using loop to Evolve!

L et’s c rea te another gam e, m aking full use of loop. But this won’t be a gam e tha t we play. Instead, i t wil l be a gam e world tha tevolves as we watch i t! W e’re going to c rea te an environm ent of steppes and jungles, fi l led with anim als running a round, foraging,ea ting, and reproduc ing. And a fte r a few m ill ion units of t im e , we’l l see tha t they’ve evolved into diffe rent spec ies!

Note

T his exam ple is adapted from A.K. Dewdney’s a rt ic le “Sim ula ted evolution: where in bugs lea rn to hunt bac te ria , ” in the“Com pute r Recrea tions” colum n of Sc ienti f ic A merican (May 1989: 138-141).

Our gam e world is extrem ely sim ple . It consists of a sim ple rec tangula r plane , with edges tha t wrap a round to the opposite side .(Mathem atica l ly speaking, i t has a toroida l topology. ) Most of this world is covered in steppes, m eaning tha t ve ry few plants growfor the anim als to ea t . In the cente r of the world is a sm all jungle , where plants grow m uch faste r. Our anim als, who a reherbivores, wil l forage this world in sea rch for food.

L e t’s c rea te som e variables describing the extent of our world:(defparameter *width* 100)

(defparameter *height* 30)(defparameter *jungle* '(45 10 10 10))(defparameter *plant-energy* 80)

W e’re giving the world a width of 100 units and a he ight of 30 units. Using these dim ensions should m ake i t easy to display theworld in our L isp RE PL . T he *jungle* l ist de fines the rec tangle in the world m ap tha t conta ins the jungle . T he first two num bersin the l ist a re the x-and y-coordina tes of the jungle ’s top-le ft corner, and the last two num bers a re i ts width and he ight . Fina lly, wegive the am ount of energy conta ined in each plant , which is se t to 80. T his m eans tha t i f an anim al finds a plant , i t wil l ga in 80days’ worth of food by ea ting i t .

Note

Page 218: Land of Lisp - Barski M.D., Conrad

If your te rm ina l window isn’t la rge enough to display the entire world, change the va lues of the *width* and *height* va riables.Se t the *width* va riable to the width of your te rm ina l window m inus two, and the *height* va riable to the he ight of your te rm ina lwindow m inus one .

Page 219: Land of Lisp - Barski M.D., Conrad

G rowing P lants in O ur World

As you m ight im agine , sim ula ting evolution on a com pute r is a slow process. In order to see the c rea tures evolve , we need tosim ula te la rge stre tches of t im e , which m eans we’l l want our code for this projec t to be very e ffic ient . As anim als wander a roundour world, they wil l need to be able to check if the re is a plant a t a given x,y loca tion. T he m ost e ffic ient way to enable this is tostore a l l of our plants in a hash table , indexed based on each plant’s x-and y-coordina tes.(defparameter *plants* (make-hash-table :test #'equal))By default , a Com m on L isp hash table uses eq when test ing for the equa li ty of keys. For this hash table , however, we’re defining

:test to use equal instead of eq, which wil l le t us use cons pa irs of x-and y-coordina tes as keys. If you rem em ber our rule of thum bfor checking equa li ty, cons pa irs should be com pared using equal. If we didn’t m ake this change , every check for a key would fa i l ,since two diffe rent cons ce l ls, even with the sam e contents, te st a s be ing diffe rent when using eq.

Plants wil l grow random ly ac ross the world, though a higher concentra t ion of plants wil l grow in the jungle a rea than in thesteppes. L e t’s write som e func tions to grow new plants:(defun random-plant (left top width height)

(let ((pos (cons (+ left (random width)) (+ top (random height)))))

(setf (gethash pos *plants*) t)))

(defun add-plants ()

(apply #'random-plant *jungle*)

(random-plant 0 0 *width* *height*))T he random-plant func tion c rea tes a new plant within a spec ified region of the world. It uses the random func tion to construc t a

random loca tion and stores i t in the loca l va riable pos . T hen i t uses setf to indica te the existence of the plant within the

hash table . T he only i tem ac tua lly stored in the hash table is t. For this *plants* table , the keys of the table (the x,yposit ion of each plant) a re ac tua lly m ore than the va lues stored in the table .

It m ay seem a bit we ird to go through the trouble of c rea ting a hash table to do nothing m ore than store t in every slot .However, Com m on L isp does not , by default , have a da ta struc ture designed for holding m athem atica l se ts. In our gam e, we wantto keep track of the se t of a l l world posit ions tha t have a plant in them . It turns out tha t hash tables a re a perfec tly acceptable wayof expressing this. You sim ply use each se t i tem as a key and store t a s the va lue . Indeed, doing this is a bi t of a hack, but i t is areasonably sim ple and e ffic ient hack. (Other L isp dia lec ts, such as Clojure , have a se t da ta struc ture buil t right into them , m akingthis hack unnecessa ry. )

E very day our sim ula tion runs, the add-plants func tion wil l c rea te two new plants: one in the jungle and one in the rest

of the m ap . Because the jungle is so sm all , i t wil l have dense vege ta t ion com pared to the rest of the world.

Page 220: Land of Lisp - Barski M.D., Conrad

Creating Animals

T he plants in our world a re very sim ple , but the anim als a re a bi t m ore com plica ted. Because of this, we’l l need to define astruc ture tha t stores the propert ies of each anim al in our gam e:(defstruct animal x y energy dir genes)L et’s take a look a t each of these fie lds in de ta i l .

Anatomy of an Animal

Page 221: Land of Lisp - Barski M.D., Conrad

W e need to track severa l propert ies for each anim al. First , we need to know its x-and y-coordina tes. T his indica tes where theanim al is loca ted on the world m ap.

Next, we need to know how m uch energy an anim al has. T his is a Darwinian gam e of surviva l , so if an anim al can’t forageenough food, i t wil l sta rve and die . T he energy fie ld tracks how m any days of energy an anim al has rem aining. It is c ruc ia l tha t ananim al find m ore food before i ts energy supply is exhausted.

W e a lso need to track which direc t ion the anim al is fac ing. T his is im portant because an anim al wil l wa lk to a ne ighboringsquare in the world m ap each day. T he dir fie ld wil l spec ify the direc t ion of the anim al’s next x,y posit ion as a num ber from 0 to7:

Page 222: Land of Lisp - Barski M.D., Conrad

For exam ple , an orienta t ion of 0 would cause the anim al to m ove up and to the le ft by the next day.Fina lly, we need to track the anim al’s genes. E ach anim al has exac tly e ight genes, consist ing of posit ive integers. T hese

integers represent e ight “slots, ” which enc irc le the anim al as fol lows:

E very day, an anim al wil l dec ide whe ther to continue fac ing the sam e direc t ion as the day before or to turn and face a newdirec tion. It wil l do this by consult ing these e ight slots and random ly choosing a new direc t ion. T he chance of a gene be ing chosenwill be proport iona l to the num ber stored in the gene slot .

For exam ple , an anim al m ight have the fol lowing genes:(1 1 10 1 1 1 1 1)L et’s represent these genes as a table , showing each slot num ber and how la rge of a va lue is stored in i t :

Page 223: Land of Lisp - Barski M.D., Conrad

In this exam ple , an anim al has a la rge num ber (10) stored in slot 2. L ooking a t our pic ture of the e ight slots a round the anim al,you can see tha t slot 2 points to the right . T here fore , this anim al wil l m ake a lot of right-hand turns and run in a c irc le . Of course ,since the other slots st i l l conta in va lues la rger than ze ro, the anim al wil l occasiona lly m ove in another direc t ion.

L e t’s c rea te an *animals* va riable , popula ted with a single sta rt ing anim al. You can think of this anim al as “Adam ” (or “E ve” ,depending on what gender you pre fe r for our asexua l anim als).(defparameter *animals*

(list (make-animal :x (ash *width* −1):y (ash *height* −1):energy 1000:dir 0:genes (loop repeat 8collecting (1+ (random 10))))))

W e m ake the anim al’s sta rt ing point the cente r of the world by se t t ing the x and y posi t ions to ha lf of the m ap’s width andhe ight , respec tive ly. W e se t i ts ini t ia l energy to 1000, since i t hasn’t evolved m uch ye t and we want i t to have a fighting chancea t surviva l . It sta rts off fac ing the upper le ft , with i ts dir fie ld se t to 0. For i ts genes, we just use random num bers.

Note tha t unlike the *plants* struc ture , which was a hash table , the *animals* struc ture is just a pla in l ist (currently conta iningonly a single m em ber). T his is because , for the core of our sim ula tion, we never need to sea rch our l ist of anim als. Instead, we’l ljust be traversing *animals* once every sim ula ted day, to le t our c ri t te rs do the ir da i ly ac t ivi t ie s. L ists a lready support e ffic ientl inear traversa ls, so using another, m ore com plex da ta struc ture (such as a table ) would have no significant e ffec t on theperform ance of our sim ula tion.

H andling Animal M otion

Page 224: Land of Lisp - Barski M.D., Conrad

T he move func tion accepts an anim al as an a rgum ent and m oves i t , orthogona lly or diagona lly, based on the direc t ion grid wehave described:(defun move (animal)

(let ((dir (animal-dir animal))

(x (animal-x animal))

(y (animaly animal)))

(setf (animal-x animal) (mod (+ x

(cond ((and (>= dir 2) (< dir 5)) 1)

((or (= dir 1) (= dir 5)) 0)

(t −1))*width*)*width*))

(setf (animaly animal) (mod (+ y(cond ((and (>= dir 0) (< dir 3)) −1)((and (>= dir 4) (< dir 7)) 1)(t 0))*height*)*height*))(decf (animal-energy animal))))

T h e move func tion m odifies the x and y fie lds, using the animal-x and animaly accessors. As we’ve discussed, these a reautom atica l ly genera ted through the defstruct m acro, based on the fie ld nam es. At the top of this func tion, we use the accessors

to re trieve the x-and y-coordina tes for the anim al . T hen we use the sam e accessors to se t the sam e va lues, with the a id

of setf .

T o ca lcula te the new x-coordina te , we use a cond com m and to first check if the direc t ion is 2, 3, or 4 . T hese a re thedirec t ions the anim al m ay face tha t point east in the world, so we want to add one to the x-coordina te . If the direc t ion instead is 1

or 5, i t m eans the anim al is fac ing direc t ly north or south . In those cases, the x-coordina te shouldn’t be changed. In a l l other

Page 225: Land of Lisp - Barski M.D., Conrad

cases, the anim al is fac ing west and we need to subtrac t one . T he y-coordina te is adjusted in an ana logous way .Since the world needs to wrap a round a t the edges, we do som e extra m ath using the mod (rem ainder) func tion to ca lcula te the

m odulus of the coordina tes and enable wrapping ac ross the m ap . If an anim al would have ended up with an x-coordina te of *width*, the mod func tion puts i t back to ze ro, and i t does the sam e for the y-coordina te and *height*. So, forexam ple , i f our func tion m akes the anim al m ove east unti l x equa ls 100, this wil l m ean tha t (mod 100 *width*) equa ls ze ro, andthe anim al wil l have wrapped a round back to the fa r west side of the gam e world.

T he fina l thing the move func tion needs to do is decrease the am ount of energy the anim al possesses by one . Motion, a fte r a l l ,requires energy.

H andling Animal Turning

Next, we’l l wri te the turn func tion. T his func tion wil l use the anim al’s genes to dec ide if and how m uch i t wil l turn on a givenday.(defun turn (animal)

(let ((x (random (apply #'+ (animal-genes animal)))))

(labels ((angle (genes x)(let ((xnu (- x (car genes))))

(if (< xnu 0)0(1+ (angle (cdr genes) xnu))))))(setf (animal-dir animal)

(mod (+ (animal-dir animal) (angle (animal-genes animal) x))8)))))

T his func tion needs to m ake sure tha t the am ount the anim al turns is proport iona l to the gene num ber in the given slot . It does

this by first sum m ing the am ount of a l l genes, and then picking a random num ber within tha t sum . Afte r tha t , i t uses a

recursive func tion nam ed angle , which traverses the genes and finds the gene tha t corresponds to the chosen num ber, based onthe respec tive contributions of each gene to the sum . It subtrac ts the running count in the a rgum ent x from the num ber stored a t the

current gene . If the running count has hi t or exceeded ze ro, the func tion has reached the chosen num ber and stops recursing

. Fina lly, i t adds the am ount of turning to the current direc t ion and, i f needed, wraps the num ber a round back to ze ro, once

aga in by using mod .

H andling Animal Eating

Page 226: Land of Lisp - Barski M.D., Conrad

E ating is a sim ple process. W e just need to check if the re ’s a plant a t the anim al’s current loca tion, and if the re is, consum e i t :(defun eat (animal)

(let ((pos (cons (animal-x animal) (animaly animal))))(when (gethash pos *plants*)(incf (animal-energy animal) *plant-energy*)(remhash pos *plants*))))

T he anim al’s energy is inc reased by the am ount of energy tha t was be ing stored by the plant . W e then rem ove the plant from theworld using the remhash func tion.

H andling Animal Reproduction

Page 227: Land of Lisp - Barski M.D., Conrad

Reproduc tion is usua lly the m ost inte rest ing part in any anim al sim ula tion. W e’ll keep things sim ple by having our anim alsreproduce asexua lly, but i t should st i l l be inte rest ing, because e rrors wil l c reep into the ir genes as they ge t copied, causingm uta tions.(defparameter *reproduction-energy* 200)

(defun reproduce (animal)(let ((e (animal-energy animal)))

(when (>= e *reproduction-energy*)

(setf (animal-energy animal) (ash e −1))

(let ((animal-nu (copy-structure animal))

Page 228: Land of Lisp - Barski M.D., Conrad

(genes (copy-list (animal-genes animal)))(mutation (random 8)))(setf (nth mutation genes) (max 1 (+ (nth mutation genes) (random 3) −1)))(setf (animal-genes animal-nu) genes)(push animal-nu *animals*)))))

It takes a hea lthy parent to produce hea lthy offspring, so our anim als wil l reproduce only if they have a t least 200 days’ worth of

energy . W e use the globa l constant *reproduction-energy* to dec ide wha t this cutoff num ber should be . If the anim al

dec ides to reproduce , i t wil l lose ha lf i ts energy to i ts child .

T o c rea te the new anim al, we sim ply copy the struc ture of the parent with the copy-structure func tion . W e need to becare ful though, since copy-structure pe rform s only a shallow copy of a struc ture . T his m eans tha t i f the re a re any fie lds in thestruc ture tha t conta in va lues tha t a re m ore com plica ted than just num bers or sym bols, the va lues in those fie lds wil l be shared withthe parent . An anim al’s genes, which a re stored in a l ist , represent the only such com plex va lue in our anim al struc tures. If wearen’t ca re ful , m uta t ions in the genes of an anim al would sim ultaneously a ffec t a l l i ts pa rents and children. In order to avoid this,

we need to c rea te an explic i t copy of our gene l ist using the copy-list func tion .Here is an exam ple tha t shows what horrible things could happen if we just re l ied on the sha llow copy from the copy-structure

func tion:> (defparameter *parent* (make-animal :x 0

:y 0:energy 0:dir 0

:genes '(1 1 1 1 1 1 1 1)))*PARENT*

> (defparameter *child* (copy-structure *parent*))*CHILD*

> (setf (nth 2 (animal-genes *parent*)) 10)10

> *parent*#S(ANIMAL :X 0 :Y 0 :ENERGY 0 :DIR 0 :GENES (1 1 10 1 1 1 1 1))

> *child*#S(ANIMAL :X 0 :Y 0 :ENERGY 0 :DIR 0 :GENES (1 1 10 1 1 1 1 1))

Here , we’ve c rea ted a parent anim al with a l l i ts genes se t to 1 . Next, we use copy-structure to c rea te a child .

T hen we se t the third (second counting from zero) gene equa l to 10 . Our parent now looks correc t . Unfortuna te ly,

since we neglec ted to use copy-list to c rea te a separa te l ist of genes for the child, the child genes were a lso changed whenthe parent m uta ted. Any t im e you have da ta struc tures tha t go beyond sim ple a tom ic sym bols or num bers, you need to be verycare ful when using setf so tha t these kinds of bugs don’t c reep into your code . In future chapte rs (espec ia l ly Chapte r 14), you’l llea rn how to avoid these issues by not using func tions tha t m uta te da ta direc t ly, in the m anner tha t setf does.

T o m uta te an anim al in our reproduce func tion, we random ly pick one of i ts e ight genes and place i t in the mutation va riable .T hen we use setf to twiddle tha t va lue a bi t , aga in using a random num ber. W e did this twiddling on the fol lowing l ine :(setf (nth mutation genes) (max 1 (+ (nth mutation genes) (random 3) −1)))In this l ine , we’re sl ightly changing a random slot in the gene l ist . T he num ber of the slot is stored in the loca l va riable

mutation. W e add a random num ber less than three to the va lue in this slot , and then subtrac t one from the tota l . T his m eans thegene va lue wil l change plus or m inus one , or stay the sam e. Since we don’t want a gene va lue to be sm alle r than one , we use themax func tion to m ake sure i t is a t least one .

W e then use push to inse rt this new cri t te r into our globa l *animal* l ist , which adds i t to the sim ula tion.

Page 229: Land of Lisp - Barski M.D., Conrad

Simulating a Day in O ur World

Now tha t we have func tions tha t handle every de ta i l of an anim al’s routine , le t’s write one tha t sim ula tes a day in our world.(defun update-world ()

(setf *animals* (remove-if (lambda (animal)(<= (animal-energy animal) 0))*animals*))

(mapc (lambda (animal)(turn animal)(move animal)(eat animal)(reproduce animal))*animals*)

(add-plants))

First , this func tion rem oves a l l dead anim als from the world . (An anim al is dead if i ts energy is le ss than or equa l to ze ro. )

Next, i t m aps ac ross the l ist , handling each of the anim al’s possible da ily ac t ivi t ie s: turning, m oving, ea t ing, and reproduc ing . Since a l l these func tions have side e ffec ts (they m odify the individua l anim al struc tures direc t ly, using setf), we use the mapcfunc tion, which does not waste t im e genera t ing a result l ist from the m apping process.

Fina lly, we ca ll the add-plants func tion , which adds two new plants to the world every day (one in the jungle and one inthe steppe). Since the re a re a lways new plants growing on the landscape , our sim ula ted world should eventua lly reach anequil ibrium , a l lowing a reasonably la rge popula t ion of anim als to survive throughout the spans of t im e we sim ula te .

Page 230: Land of Lisp - Barski M.D., Conrad

Drawing O ur World

A sim ula ted world isn’t any fun unless we can ac tua lly see our c ri t te rs running a round, sea rching for food, reproduc ing, anddying. T he draw-world func tion handles this by using the *animals* and *plants* da ta struc tures to draw a snapshot of the currentworld to the RE PL .(defun draw-world ()

(loop for ybelow *height*do (progn (fresh-line)(princ "|")

(loop for xbelow *width*

do (princ (cond ((some (lambda (animal)(and (= (animal-x animal) x)(= (animaly animal) y)))*animals*)

#\M)

((gethash (cons x y) *plants*) #\*)

(t #\space))))

(princ "|"))))

First , the func tion uses a loop to i te ra te through each of the world’s rows . E very row sta rts with a new l ine (c rea ted withfresh-line) fol lowed by a vert ica l ba r, which shows us where the le ft edge of the world is. Next, we i te ra te ac ross the colum ns of

the current row , checking for an anim al a t every loca tion. W e perform this check using the some func tion , which le tsus de te rm ine if a t least one i tem in a l ist obeys a ce rta in condit ion. In this case , the condit ion we’re checking is whe ther the re ’s an

anim al a t the current x-and y-coordina tes. If so, we draw the le t te r M a t tha t spot . (T he capita l le t te r M looks a l i t t le l ike ananim al, i f you use your im agina tion. )

Otherwise , we check for a plant , which we’l l indica te with an aste risk (*) charac te r . And if the re isn’t a plant or an

anim al, we draw a space charac te r . L ast ly, we draw another vert ica l ba r to cap off the end of each l ine .Notice tha t in this func tion, we need to sea rch through our entire *animals* l ist , which wil l cause a perform ance pena lty.

However, draw-world is not a core routine in our sim ula tion. As you’l l see short ly, the use r inte rface for our gam e wil l a l low us torun thousands of days of the sim ula tion a t a t im e , without drawing the world to the sc reen unti l the end. Since the re ’s no need todraw the sc reen on every single day when we do this, the perform ance of draw-world has no im pac t on the overa l l pe rform ance ofthe sim ula tion.

Page 231: Land of Lisp - Barski M.D., Conrad

Creating a User Interface

Fina lly, we’l l c rea te a use r inte rface func tion for our sim ula tion, ca l led evolution.(defun evolution ()

(draw-world)(fresh-line)

(let ((str (read-line)))

(cond ((equal str "quit") ())

(t (let ((x (parse-integer str :junk-allowed t)))(if x

(loop for ibelow xdo (update-world)if (zerop (mod i 1000))do (princ #\.))(update-world))

(evolution))))))

First , this func tion draws the world in the RE PL . T hen i t wa its for the use r to ente r a com m and a t the RE PL using read-

line . If the use r ente rs quit, the sim ula tion ends . Otherwise , i t wil l a t tem pt to parse the use r’s com m and using parse-

integer . W e se t :junk-allowed to true for parse-integer, which le ts the inte rface accept a string even if i t isn’t a va lidinteger.

If the use r ente rs a va lid integer n, the program will run the sim ula tion for n sim ula ted days, using a loop . It wil l a lso printa dot to the sc reen for every 1000 days, so the use r can see tha t the com pute r hasn’t frozen while running the sim ula tion.

If the input isn’t a va lid integer, we run update-world to sim ula te one m ore day. Since read-line a l lows for an em pty va lue ,the use r can just tap the ente r key and watch the anim als m ove a round the ir world.

Fina lly, the evolution func tion recursive ly ca l ls i tse lf to redraw the world and await m ore use r input . Our sim ula tion isnow com ple te .

Page 232: Land of Lisp - Barski M.D., Conrad

Let's Watch Some Evolution!

T o sta rt the sim ula tion, execute evolution a s fol lows:> (evolution)

||||||||||||||||||||||||||||||| M|||||||||||||||||||||||||||||

Our world is currently em pty, except for the Adam /E ve anim al in the cente r. Hit ente r a few t im es to cyc le through a few days:|

||||||||||||||||||||

Page 233: Land of Lisp - Barski M.D., Conrad

|||||||| *|||| * M|||||||||||||||||||||||||||[enter]||||||| *|||||||||||||||||||||| *|| *|| * M|||||||||||||

Page 234: Land of Lisp - Barski M.D., Conrad

||||||||||||||

Our under-evolved anim al is stum bling a round random ly, and a few plants a re sta rt ing to grow.Next, ente r 100 to see wha t the world looks l ike a fte r 100 days:100

| ** ||* * || * ** *|| * * ** * || M *M* * || * M *|| * * *|| * M M *|| M M M* || * M M * *|| * M M MM ** || M* ** || M M M* || * M MM|| * * * M M * MM * * * || * * * M MM * M* * || M M M* ** * || * * M M M|| * * M* * * ||M * || * * M M|| * * * M M|| * * * M M MM || * * M M* * * || ** || * ** || ** || * * M* || ** || * * M ** * * |

Our anim al has a lready m ult ipl ied quite a bi t , a l though this has less to do with the am ount of food i t has ea ten than with thela rge am ount of “sta rte r energy” we gave i t .

Now le t’s go a l l out and run the sim ula tion for five m il l ion days! Since we’re using CL ISP, this wil l be kind of slow, and youm ay want to sta rt i t up in the evening and le t i t run overnight . W ith a higher-perform ance L isp, such as SBCL , i t could take only acouple of m inutes.5000000

| *M M |

Page 235: Land of Lisp - Barski M.D., Conrad

| * * *M || M* || M ** M M || * M ** || *** || M|| M * M M* || * * M M M MM M || MM || M* * M M MMM MM || * * MMM * M MM || * M * M M *MM* MMM M* || M MMMMMM M MM * || M MMMM MMM M * MM || M * M MMM* * || M M M M M MM * || M M M MMM MM M || M M M MMM * * || * MMM MMM M M || M MM *M || MMM M M M|| * M M|| M M M *M *M M || M MMM M || M * M * MM || MM M M MM || M M M|| M * M * ** || M M M|

Our world doesn’t look m uch diffe rent a fte r five m il l ion days than i t did a fte r a hundred days. Of course , the re a re m ore anim als,both trave ling ac ross the steppes and enjoying the lush vege ta t ion of the jungle .

But appearances a re deceptive . T hese anim als a re dist inc tly diffe rent from the ir ea rly ancestors. If you observe them c lose ly (bytapping ente r), you’l l see tha t som e of the c rea tures m ove in stra ight l ines and others just j i t te r a round in a sm all a rea , nevertaking m ore than a single step in any direc t ion. (As an exerc ise , you could tweak the code to use diffe rent le t te rs for each anim al,in order to m ake the ir m otion even easie r to observe . ) You can see this contrast even m ore c lea rly by typing quit to exit thesim ula tion, then checking the contents of the *animals* va riable a t the RE PL :>*animals*

#S(ANIMAL :X 6 :Y 24 :ENERGY 65 :DIR 3 :GENES (67 35 13 14 1 3 11 74))#S(ANIMAL :X 72 :Y 11 :ENERGY 78 :DIR 6 :GENES (68 36 13 12 2 4 11 72))#S(ANIMAL :X 16 :Y 26 :ENERGY 78 :DIR 0 :GENES (71 36 9 16 1 6 5 77))#S(ANIMAL :X 50 :Y 25 :ENERGY 76 :DIR 4 :GENES (2 2 7 5 21 208 33 9))#S(ANIMAL :X 53 :Y 13 :ENERGY 34 :DIR 4 :GENES (1 2 8 5 21 208 33 8))#S(ANIMAL :X 58 :Y 10 :ENERGY 66 :DIR 6 :GENES (5 2 7 2 22 206 29 3))#S(ANIMAL :X 74 :Y 3 :ENERGY 77 :DIR 0 :GENES (68 35 11 12 1 3 11 74))#S(ANIMAL :X 47 :Y 19 :ENERGY 47 :DIR 2 :GENES (5 1 8 4 21 207 30 3))#S(ANIMAL :X 27 :Y 22 :ENERGY 121 :DIR 1 :GENES (69 36 11 12 1 2 11 74))#S(ANIMAL :X 96 :Y 14 :ENERGY 78 :DIR 5 :GENES (71 37 9 17 2 5 5 77))#S(ANIMAL :X 44 :Y 19 :ENERGY 28 :DIR 1 :GENES (1 3 7 5 22 208 34 8))#S(ANIMAL :X 55 :Y 22 :ENERGY 18 :DIR 7 :GENES (1 3 8 5 22 208 34 7))#S(ANIMAL :X 52 :Y 10 :ENERGY 63 :DIR 0 :GENES (1 2 7 5 23 208 34 7))#S(ANIMAL :X 49 :Y 14 :ENERGY 104 :DIR 4 :GENES (4 1 9 2 22 203 28 1))#S(ANIMAL :X 39 :Y 23 :ENERGY 62 :DIR 7 :GENES (70 37 9 15 2 6 5 77))#S(ANIMAL :X 97 :Y 11 :ENERGY 48 :DIR 0 :GENES (69 36 13 12 2 5 12 72))...

If you look c lose ly a t a l l the anim als in the l ist , you’l l notice tha t they have two dist inc t types of genom es. One group ofanim als has a high num ber toward the front of the l ist , which causes them to m ove m ostly in a stra ight l ine . T he other group has a

Page 236: Land of Lisp - Barski M.D., Conrad

la rge num ber toward the back of the l ist , which causes them to j i t te r about within a sm all a rea . T here a re no anim als with agenom e be tween those two extrem es. Have we evolved two diffe rent spec ies?

If you were to c rea te a func tion tha t m easured how fa r these evolved anim als trave l in a fixed am ount of t im e , the histogram ofthe distance would appear as fol lows:

T his is a c lea r bim oda l distribution, showing tha t the behavior of these anim als appears to fa l l into two popula t ions. T hink aboutthe environm ent these anim als l ive in, and try to reason why this bim oda l distribution would evolve . W e wil l discuss the solution tothis conundrum next.

Page 237: Land of Lisp - Barski M.D., Conrad

Explaining the Evolution

T he solution to the evolution puzz le is pre t ty stra ightforward. T here a re two possible surviva l stra tegies an anim al can adopt inthis im aginary world:

Focus on the rich food supply in the jungle . Any anim al adopting this stra tegy needs to be conserva tive in i ts m otion. It can’tstray too fa r over t im e , or i t m ight fa l l out of the jungle . Of course , these types of anim als do need to evolve a t least a bi t ofj i t te ry m otion, or they wil l never find any food a t a l l . L e t’s ca l l these conserva tive , j i t te ry, jungle -dwell ing anim als the e lephantspec ies.

Forage the sparse vege ta t ion of the steppes. Here , the m ost c ri t ica l t ra i t for surviva l is to cover la rge distances. Such an anim alneeds to be open-m inded, and m ust constantly m igra te to new areas of the m ap to find food. (It can’t t rave l in too stra ight a l inehowever, or i t m ay end up com peting for resources with i ts own offspring. ) T his stra tegy requires a bi t of na ïve optim ism , and cana t t im es lead to doom . L e t’s ca l l these l ibe ra l ly m inded, risk-taking anim als the donkey spec ies.

Page 238: Land of Lisp - Barski M.D., Conrad

E xpanding the sim ula tion to evolve the three branches of governm ent is le ft a s an exerc ise to the reader.

Page 239: Land of Lisp - Barski M.D., Conrad

What You've Learned

In this chapte r, we discussed the loop com m and in de ta i l . Along the way, you lea rned the fol lowing:T he loop com m and is a one-stop looping shop—it can do anything you need a loop to do.T o count through num bers in a loop, use the for phrase .T o count through i tem s in a l ist within a loop, use the for in phrase .You can collec t i tem s inside a l ist and re turn them as a l ist with the collect phrase .Use the Periodic T able of the L oop Macro to find other use ful phrases supported by loop.

Page 240: Land of Lisp - Barski M.D., Conrad

Chapter 11. P r inting Text with the format F unction

E ven in this m odern e ra of program m ing, i t ’s extrem ely im portant to be able to m anipula te text , and Com m on L isp has som e ofthe fanc iest text-print ing func tions ava ilable . W hether you need to m anipula te XML , HT ML , L inux configura t ion fi le s, or anyother da ta in a textua l form at, L isp wil l m ake your work easy.

T he m ost im portant advanced text print ing func tion in Com m on L isp is the format func tion, which is the subjec t of this chapte r.

Page 241: Land of Lisp - Barski M.D., Conrad

Anatomy of the format F unction

Here is an exam ple of the format func tion in use :> (format t "Add onion rings for only ˜$ dollars more!" 1.5)

Add onion rings for only 1.50 dollars more!NIL

L et’s take a look a t wha t each part of this func tion m eans.

Page 242: Land of Lisp - Barski M.D., Conrad

The Destination P arameter

T he first pa ram ete r to the format func tion is the destination pa ram ete r, which te l ls format where to send the text i t genera tes.Here a re i ts possible va lues: nil

Don’t print anything; just re turn the va lue as a string.t

Print the va lue to the console . In this case , the func tion just re turns ni l a s a va lue (as in our exam ple).stream

W rite the da ta to an output stream (covered in Chapte r 12).In the fol lowing exam ple , we se t the first pa ram ete r to nil so i t sim ply re turns the va lue as a string:> (princ (reverse

(format nil "Add onion rings for only ˜$ dollars more!" 1.5)))

!erom srallod 05.1 ylno rof sgnir noino ddA

"!erom srallod 05.1 ylno rof sgnir noino ddA"T he result ing string va lue ("Add onion rings for only 1.50 dollars more!") is passed to the reverse func tion, and then tha t

reversed string is printed to the sc reen with the princ com m and .In this exam ple , the RE PL will a lso print the va lue of the ente red expression, a long with the inform ation output by the princ

com m and. T his is why you see the va lue displayed a second t im e . For the rem ainder of this chapte r, the exam ples wil l om itthese va lues printed by the RE PL , and show only the inform ation explic i t ly printed by our code .

Page 243: Land of Lisp - Barski M.D., Conrad

The Control Str ing P arameter

T he second param ete r to the format func tion is a control string, which controls the text form att ing. T he format func tion’spower l ie s in the control string. In our current exam ple , the control string is "Add onion rings for only ˜$ dollars more!".

By default , the text in this string is sim ply printed as output . However, you can place control sequences into this string to a ffec tthe form at of the output , a s desc ribed in the rem ainder of this chapte r. Our current exam ple conta ins the control sequence ˜$,which indica tes a monetary f loating-point va lue . E very control sequence recognized by the format func tion begins with the t i lde(˜) charac te r.

Page 244: Land of Lisp - Barski M.D., Conrad

Value P arameters

T he format pa ram ete rs fol lowing the control string conta in va lues, or the ac tua l da ta to be displayed and form atted. As you’l lsee , the control string inte rac ts with these param ete rs and controls the ir form att ing.

Page 245: Land of Lisp - Barski M.D., Conrad

Control Sequences for P r inting Lisp Values

Any L isp va lue can be printed with the print or prin1 com m and. T o print a va lue for hum ans, without any de lim ite rs, we canuse the princ com m and:> (prin1 "foo")

"foo"> (princ "foo")foo

W e can use the ˜s and ˜a control sequences with format to produce the sam e behavior as prin1 and princ. W hen used withformat, the ˜s control sequence inc ludes appropria te de lim ite rs. T he ˜a shows the va lue , without de lim ite rs, for hum ans to read:> (format t "I am printing ˜s in the middle of this sentence." "foo")

I am printing "foo" in the middle of this sentence.> (format t "I am printing ˜a in the middle of this sentence." "foo")I am printing foo in the middle of this sentence.

W e can adjust the behavior of these control sequences even further by ente ring param ete rs within the control sequence . Forinstance , we can place a num ber n in front of the a or s to indica te tha t the va lue should be padded with blank spaces on the right .T he format com m and wil l then add spaces unti l the tota l width of the va lue is n.

For exam ple , by writ ing ˜10a in the fol lowing exam ple , we add seven spaces to the right of foo, m aking the tota l width of theform atted va lue 10 charac te rs:> (format t "I am printing ˜10a within ten spaces of room." "foo")

I am printing foo within ten spaces of room.W e can a lso add spaces on the le ft side of the va lue by adding the @ sym bol, a s fol lows:> (format t "I am printing ˜10@a within ten spaces of room." "foo")

I am printing foo within ten spaces of room.In this case , the tota l width of the added spaces a long with the va lue foo equa ls 10 charac te rs.Control sequences can accept m ore than just one param ete r. In the preceding exam ples, we se t only the first pa ram ete r, which

controls the fina l width of the fina l form atted string. L e t’s look a t an exam ple tha t se ts the second param ete r of the ˜a controlsequence as well :> (format t "I am printing ˜10,3a within ten (or more) spaces of room." "foo")

I am printing foo within ten (or more) spaces of room.As you can see , addit iona l pa ram ete rs to a control sequence a re separa ted with a com m a. In this case , the second param ete r is

se t to 3, which te l ls the format com m and to add spaces in groups of three (instead of just one a t a t im e) unti l the goa l width of 10is reached. In this exam ple , a tota l of nine spaces a re added to the form atted va lue . T his m eans i t overshot our goa l width of 10(by design), leading instead to a tota l width of 12 (nine spaces plus the le t te rs foo). Padding strings in m ult iples l ike this is not acom m only needed fea ture , so the second param ete r to the ˜a control sequence is ra re ly used.

Som etim es we need to control the exac t num ber of spaces to add to our string, regardless of the length of the fina l va lue . W ecan do this by se t t ing the third param ete r in the ˜a control sequence . For exam ple , suppose we want to print exac tly four spacesafte r the fina l form atted va lue . T o se t the third control sequence param ete r equa l to four, we place two com m as in front of theparam ete r to indica te tha t the first two param ete rs a re blank, then follow this with a 4:> (format t "I am printing ˜,,4a in the middle of this sentence." "foo")

I am printing foo in the middle of this sentence.Notice tha t the re a re exac tly four extra spaces inse rted in the results. Since the first and second param ete rs were not spec ified

before the com m as, the ir de fault va lues wil l be used.T he fourth control sequence param ete r spec ifies which charac te r wil l be used for padding. For exam ple , in the fol lowing l ist ing,

we pad the printed va lue with four exc lam ation points:> (format t "The word ˜,,4,'!a feels very important." "foo")

The word foo!!!! feels very important.T hese control sequence param ete rs can a lso be com bined. For exam ple , we can add the @ sym bol to our code to indica te tha t the

exc lam ation m arks should appear in front of the va lue , l ike this:> (format t "The word ˜,,4,'!@a feels very important." "foo")

The word !!!!foo feels very important.Now tha t you have an overview of format com m and control sequences, le t’s look a t how to use them for form att ing, beginning

with num bers.

Page 246: Land of Lisp - Barski M.D., Conrad

Control Sequences for F ormatting Numbers

T he format com m and has m any options designed spec ifica l ly for controll ing the appearance of num bers. L e t’s look a t som e ofthe m ore use ful ones.

Page 247: Land of Lisp - Barski M.D., Conrad

Control Sequences for F ormatting Integers

First , we can use format to display a num ber using a diffe rent base . For instance , we can display a num ber in hexadec im al (base-16) with the ˜x control sequence :> (format t "The number 1000 in hexadecimal is ˜x" 1000)

The number 1000 in hexadecimal is 3E8Sim ila rly, we can display a num ber in binary (base-2) using the ˜b control sequence :> (format t "The number 1000 in binary is ˜b" 1000)

The number 1000 in binary is 1111101000W e can even explic i t ly dec la re tha t a va lue wil l be displayed as a dec im al (base-10) num ber, using the ˜d control sequence :> (format t "The number 1000 in decimal is ˜d" 1000)

The number 1000 in decimal is 1000In this case , we would have gotten the sam e result i f we had just used the m ore generic ˜a control sequence . T he diffe rence is

tha t ˜d supports spec ia l pa ram ete rs and flags tha t a re spec ific to print ing dec im al num bers. For exam ple , we can place a coloninside the control sequence to enable com m as as digit group separa tors:> (format t "Numbers with commas in them are ˜:d times better." 1000000)

Numbers with commas in them are 1,000,000 times better.T o control the width of the num ber, we can se t the padding param ete r, just a s we did with the ˜a and ˜s control sequences:> (format t "I am printing ˜10d within ten spaces of room" 1000000)

I am printing 1000000 within ten spaces of roomT o change the charac te r used for padding, pass in the desired charac te r (in this case , the x charac te r) as the second param ete r:> (format t "I am printing ˜10,'xd within ten spaces of room" 1000000)

I am printing xxx1000000 within ten spaces of room

Page 248: Land of Lisp - Barski M.D., Conrad

Control Sequences for F ormatting F loating-P oint Numbers

Floa ting-point va lues a re handled with the ˜f control sequence . As with a l l of the previously discussed control sequences, we canchange the va lue’s display width by changing the first pa ram ete r. W hen used with floa ting-point num bers, the format com m andwill autom atica l ly round the va lue to fi t within the requested num ber of charac te rs (inc luding the dec im al point):> (format t "PI can be estimated as ˜4f" 3.141593)

PI can be estimated as 3.14As you can see , the fina l width of 3.14 is four charac te rs wide , as spec ified by the control sequence .T he second param ete r of the ˜f control sequence controls the num ber of digi ts displayed a fte r the dec im al point . For exam ple , i f

we pass 4 a s the second param ete r in the preceding exam ple , we ge t the fol lowing output:> (format t "PI can be estimated as ˜,4f" 3.141593)

PI can be estimated as 3.1416Note tha t Com m on L isp ac tua lly inc ludes the constant pi a s pa rt of the standard, so you could a lso rewrite the com m and l ike

this:> (format t "PI can be estimated as ˜,4f" pi)

PI can be estimated as 3.1416T he third param ete r of the ˜f control sequence causes the num ber to be sca led by fac tors of ten. For exam ple , we can pass 2 a s

the third param ete r, which we can use to m ult iply a frac t ion by 102 to turn i t into a percentage :> (format t "Percentages are ˜,,2f percent better than fractions" 0.77)

Percentages are 77.0 percent better than fractionsIn addit ion to ˜f, we can use the control sequence ˜$, which is used for form att ing currenc ies:> (format t "I wish I had ˜$ dollars in my bank account." 1000000.2)

I wish I had 1000000.20 dollars in my bank account.You saw an exam ple tha t used ˜$ a t the beginning of this chapte r.

Page 249: Land of Lisp - Barski M.D., Conrad

P rinting M ultiple Lines of O utput

Com m on L isp has two diffe rent com m ands for sta rt ing a new l ine during print ing. T he first , terpri, sim ply te l ls L isp tote rm ina te the current l ine and sta rt a new one for print ing subsequent output . For exam ple , we can print two num bers on diffe rentl ines l ike so:> (progn (princ 22)

(terpri)(princ 33))2233

W e can a lso sta rt a new l ine with fresh-line. T his com m and wil l sta rt a new l ine , but only if the cursor posit ion in the RE PLisn’t a lready a t the very front of a l ine . L e t’s look a t som e exam ples:> (progn (princ 22)

(fresh-line)(princ 33))2233> (progn (princ 22)(fresh-line)(fresh-line)(princ 33))2233

As you can see , plac ing two fresh-line sta tem ents be tween the two princ ca l ls resulted in L isp print ing only one l ine be tweenthe outputted num bers. T he first fresh-line sta rts a new l ine ; the second fresh-line is sim ply ignored.

E ssentia l ly, the terpri com m and says “sta rt a new l ine , ” whereas the fresh-line com m and says “sta rt a new l ine , i f needed. ”Any code using the terpri com m and needs to “know” what was printed before . Otherwise , unsightly em pty l ines m ay result . Sinceit’s a lways be tte r i f diffe rent pa rts of a program know as l i t t le about each other as possible , m ost L ispers pre fe r using fresh-lineover terpri, because i t a l lows them to decouple the print ing of one piece of da ta from the next .

T he format com m and has two control sequences tha t a re ana logous to terpri and fresh-line:˜%

causes a new l ine to be c rea ted in a l l cases (l ike terpri)˜&

crea tes new l ines only as needed (l ike fresh-line).T hese exam ples i l lustra te this diffe rence :> (progn (format t "this is on one line ˜%")

(format t "˜%this is on another line"))this is on one line

this is on another line> (progn (format t "this is on one line ˜&")(format t "˜&this is on another line"))this is on one linethis is on another line

As you can see , using an extra ˜% prints an unsightly em pty l ine , and using ˜& in the sam e places does not .T hese two l ine -te rm ina tion sequences can a lso have an addit iona l pa ram ete r in front of them to indica te the num ber of new l ines

to be c rea ted. T his is use ful in cases where we want to use em pty l ines to space out our output . For exam ple , the addit ion of 5 inthe fol lowing exam ple adds five em pty l ines to our output:> (format t "this will print ˜5%on two lines spread far apart")

this will print

on two lines spread far apart

Page 250: Land of Lisp - Barski M.D., Conrad

Justifying O utput

T he format com m and a lso gives us a lot of control over text just ifica t ion. Control sequences a l low us to form at tables, cente rtext , and perform other use ful just ifica t ion fea ts.

T o he lp you understand the various just ifica t ion rules, we’l l c rea te a sim ple func tion tha t re turns diffe rent anim al nam es withvarying charac te r lengths:> (defun random-animal ()

(nth (random 5) '("dog" "tick" "tiger" "walrus" "kangaroo")))RANDOM-ANIMAL> (random-animal)"walrus"

Now suppose we want to display a bunch of random anim als in a table . W e can do this by using the ˜t control sequence . ˜t cantake a param ete r tha t spec ifies the colum n posit ion a t which the form atted va lue should appear. For exam ple , to have our table ofanim als appear in three colum ns a t the fifth, fi fteenth, and twenty-fifth charac te r posi t ions, we could c rea te this table :> (loop repeat 10

do (format t "˜5t˜a ˜15t˜a ˜25t˜a˜%"(random-animal)(random-animal)(random-animal)))kangaroo tick dogdog walrus walruswalrus tiger tigerwalrus kangaroo dogkangaroo tiger dogtiger walrus kangarootick dog tigerkangaroo tick kangarootiger dog walruskangaroo kangaroo tick

Rem em ber tha t a loop com m and with a repeat 10 c lause executes the body of the loop 10 t im es. As you can see , use of the ˜tcontrol sequence caused the anim als to be la id out in a nea tly form atted table .

Now suppose we want a l l the anim als be spaced equa lly apart on a single l ine . T o do so, we can use the ˜< and ˜> controlsequences, a s fol lows:> (loop repeat 10

do (format t "˜30<˜a˜;˜a˜;˜a˜>˜%"(random-animal)(random-animal)(random-animal)))tick tiger ticktick tiger dogtick dog dogkangaroo kangaroo tigertiger tiger kangaroowalrus kangaroo dogdog dog walruskangaroo dog walruswalrus dog walruskangaroo tiger tick

L et’s deconstruc t this control string to understand how it works:

Page 251: Land of Lisp - Barski M.D., Conrad

First , the ˜30< te l ls the func tion tha t we’re ini t ia t ing a block of just ified text . T he param ete r 30 indica tes tha t the block shouldbe 30 charac te rs wide . Next, we have three ˜a control sequences in a row, one for each anim al. E ach ˜a is separa ted by ;, whichte l ls format tha t we’re sta rt ing a new va lue to be just ified by ˜<. (T he ˜; sequences indica te where extra spaces should be inse rtedto just ify the va lues. ) W e then end the just ified sec tion with the ˜> com m and sequence .

Because the equa l spac ing of the anim als in each l ine doesn’t guarantee tha t the colum ns c rea ted by print ing m ult iple l ines wil lbe properly a l igned, we add the :@ flag to our just ifica t ion ˜< com m and sequence . For exam ple , we can c rea te a single , nea tlycente red colum n as fol lows:> (loop repeat 10 do (format t "˜30:@<˜a˜>˜%" (random-animal)))

dogwalruskangarootickticktigerdogkangarookangaroodog

In the sam e way, we can use :@ with m ult iple just ified va lues, cente ring them on the l ine with addit iona l space a t the ir le ft andright ends:> (loop repeat 10

do (format t "˜30:@<˜a˜;˜a˜;˜a˜>˜%"(random-animal)(random-animal)(random-animal)))walrus tick tickwalrus tiger ticktick dog tickwalrus tiger tigerkangaroo dog kangarootiger kangaroo walrustiger kangaroo kangarookangaroo tiger ticktick tiger walruswalrus tiger tick

T his step brings us c lose r to having three nea tly cente red colum ns, but our colum ns a re st i l l a bi t wavy because we’re a l igningthe va lues within a single l ine , without te l l ing format to a rrange the va lues using three cente red colum ns.

T o produce nea t colum ns, we’l l st i l l use the :@ flag, but we’l l desc ribe our rows using three separa te 10-charac te r just ifica t ionsec tions:> (loop repeat 10

do (format t "˜10:@<˜a˜>˜10:@<˜a˜>˜10:@<˜a˜>˜%"(random-animal)(random-animal)(random-animal)))tiger kangaroo kangarookangaroo kangaroo walrustick tick tickdog dog dogtiger dog walrusdog tiger kangaroowalrus dog ticktick walrus kangaroodog tick walrustiger tiger tiger

At last , we have the nice ly cente red random anim al colum ns of our dream s!As you can see , the layout options for format a re quite flexible . Since we often need to c rea te com plex l ists and tables of da ta

when debugging applica t ions, these tricks a re very he lpful when you need to ge t a handle on your da ta , even with m ore com plexprogram s.

Page 252: Land of Lisp - Barski M.D., Conrad

Iterating Through Lists Using Control Sequences

T he format func tion with i ts m any control sequences is prac tica l ly a program m ing language in i ts own right . (In fac t , m anyL ispers would ca l l i t a domain-spec if ic language , a concept we wil l revisi t in Chapte r 17. ) And, l ike m ost program m ing languages,format can loop through da ta . It does this using the ˜{ and ˜} control sequences.

T o achieve this looping, pass the format func tion a control string conta ining ˜{ and ˜}, and a l ist to i te ra te through. T he part ofthe control string be tween the ˜{ and ˜} sequences is t rea ted a lm ost l ike the body of a loop. It wil l be executed a num ber of t im es,depending on the length of the l ist tha t fol lows i t . T he format func tion wil l i te ra te through this l ist , applying each of i ts i tem s tothe spec ified sec tion of the control string.

For exam ple , le t’s c rea te a l ist of anim als tha t we can use for test ing:> (defparameter *animals* (loop repeat 10 collect (random-animal)))

*ANIMALS*> *animals*("dog" "kangaroo" "walrus" "kangaroo" "kangaroo" "walrus" "kangaroo""dog" "tick" "tick")

Now we use the ˜{ ˜} control sequences to to loop through this l ist :> (format t "˜{I see a ˜a! ˜}" *animals*)

I see a dog! I see a kangaroo! I see a walrus! I see a kangaroo! I seea kangaroo! I see a walrus! I see a kangaroo!I see a dog! I see a tick! I see a tick!

T o produce this loop, we sim ply pass the single variable *animals*, a l ist of i tem s, to the format func tion. T he control stringite ra tes through the l ist , construc ting the sentence "I see a ˜a" for each m em ber of *animals*.

A single i te ra t ion construc t can a lso grab m ore than one i tem from the l ist , a s in this exam ple :> (format t "˜{I see a ˜a... or was it a ˜a?˜%˜}" *animals*)

I see a dog... or was it a kangaroo?I see a walrus... or was it a kangaroo?I see a kangaroo... or was it a walrus?I see a kangaroo... or was it a dog?I see a tick... or was it a tick?

Here , we have two ˜a control sequences within a single looping construc t . E ach ˜a pulls a single anim al from the l ist , so twoanim als print for every i te ra t ion of the loop.

Page 253: Land of Lisp - Barski M.D., Conrad

A Crazy F ormatting Tr ick for Creating P retty Tables of Data

L et’s look a t one last format exam ple tha t uses som e of the control sequences you’ve a lready seen, as well a s som e new ones.T his exam ple wil l i l lustra te how the varied control sequences can be com bined for com plex behavior.> (format t "|˜{˜<|˜%|˜,33:;˜2d ˜>˜}|" (loop for x below 100 collect x))

| 0 1 2 3 4 5 6 7 8 9 ||10 11 12 13 14 15 16 17 18 19 ||20 21 22 23 24 25 26 27 28 29 ||30 31 32 33 34 35 36 37 38 39 ||40 41 42 43 44 45 46 47 48 49 ||50 51 52 53 54 55 56 57 58 59 ||60 61 62 63 64 65 66 67 68 69 ||70 71 72 73 74 75 76 77 78 79 ||80 81 82 83 84 85 86 87 88 89 ||90 91 92 93 94 95 96 97 98 99 |

T o c rea te this nice ly form atted table of num bers, we first use the looping control sequences ˜{ ˜} to i te ra te through a l ist ofnum bers c rea ted by the loop com m and. W ithin the i te ra t ion, we place just ifica t ion control sequences ˜< ˜>, which we’ve usedearl ie r. In this case , we don’t use them to just ify our text , but instead use them to divide the result ing text into pieces. T his is howwe break our 100 num bers into nice c lean rows of 10. W e place the ˜:; control sequence inside our just ifica t ion control sequences˜< ˜>, which causes text to be broken into pieces of equa l length.

W hen used inside a just ifica t ion, the control string preceding this sequence ˜:; (which in this case happens to be |˜%|) wil l betriggered only if the current cursor posit ion is beyond a ce rta in point , a s spec ified by the second param ete r, 33. In other words,we’re te l l ing the form at func tion “Hey, once you have 33 charac te rs’ worth of text , sta rt a fresh l ine . ”

T he |˜%| control string causes the l ine break and vert ica l ba rs to be printed. T he num ber to be displayed is form atted using ˜2d,which prints a le ft-just ified num ber, two charac te rs wide .

Note

For ful l de ta i ls on every single control sequence , see the Common Lisp HyperSpec a tht tp: / /www. lispworks. com /docum enta t ion/HyperSpec /Front/ index.htm .

Page 254: Land of Lisp - Barski M.D., Conrad

Attack of the Robots!

Here , we look a t a gam e so horrifying tha t i t ’s sure to give you nightm ares: Attack of the Robots! In this gam e, robots have takenover the world, and i t’s your job to destroy them . T hough the plot m ay sound scary, the part of this gam e tha t wil l really give aL isp program m er nightm ares is the way i t abuses the loop and format com m ands in order to squeeze a ful ly func tiona l robot-fighting gam e into a single page of code! (T his program uses the “c razy form att ing trick” discussed in the previous sec tion. )

I have annota ted the code with som e basic explana tions. If you want to understand how the gam e works in de ta i l , you’l l need toreview m ost of the inform ation from the previous couple of chapte rs. Also, you can visi t ht tp: / / landofl isp. com / to download thesource code for the gam e and read a m ore thorough explana tion of the code .

T o win the gam e, you need to stra tegica lly walk a round the fie ld to cause a l l robots to coll ide with each other. T he m ovem entkeys a re QW E /ASD/Z XC. T hese charac te rs form a grid on the le ft side of your keyboard, le t t ing you m ove up, down, le ft , right , a swell a s diagona lly. You can a lso te leport with the T key.

E njoy!

Page 255: Land of Lisp - Barski M.D., Conrad
Page 256: Land of Lisp - Barski M.D., Conrad

What You've Learned

T his chapte r didn’t rea l ly even com e c lose to covering a l l of the fea tures of the format func tion. However, i t did provide anintroduc tion, in which you lea rned the fol lowing:

T he first pa ram ete r of the format com m and de te rm ines whe ther the output is sent to the RE PL , a stream , or re turned as astring.

T he second param ete r of the format com m and is a control string tha t le ts you change the way your da ta is printed. T he controlstring has a sophist ica ted syntax, ac t ing a lm ost l ike a program m ing language in i ts own right .

T he rem aining format pa ram ete rs a re va lues tha t can be re fe renced from the control string to em bed va lues into the form attedoutput .

T o em bed a L isp va lue into a form atted string, use the ˜s or ˜a control sequences.Many control sequences a re ava ilable for print ing and custom iz ing the appearance of num bers.T he format com m and a lso has com plex looping abil i t ie s tha t can be used, for exam ple , to form at tables la id out in m any

diffe rent styles.

Page 257: Land of Lisp - Barski M.D., Conrad

Chapter 12. Working with Streams

Nearly every com pute r program you write wil l need to inte rac t with the outside world a t som e point . Perhaps your program justneeds to com m unica te with the use r through the RE PL , print ing out inform ation and capturing the use r’s input from the keyboard.Other program s you write m ay need to read or write fi le s on a hard drive . Addit iona lly, you m ay want to write program s tha tinte rac t with other com pute rs, e i the r over a loca l ne twork or the Inte rne t . In Com m on L isp, these kinds of inte rac tions happenthrough stream s.Streams a re da ta types in Com m on L isp tha t a l low you to take som e exte rna l resource and m ake i t look l ike just another sim ple

piece of da ta you can m anipula te with your code . T he exte rna l resource could be a varie ty of things: a fi le on a disk, anothercom pute r on a ne twork, or text in a console window on the sc reen. As you’l l lea rn in this chapte r, through the use of a stream , aL isp program can inte rac t with this outside resource just a s easi ly as i t m ight inte rac t with a l ist or a hash table .

Page 258: Land of Lisp - Barski M.D., Conrad

Types of Streams

W hen we com m unica te with an exte rna l resource from a Com m on L isp program , we do so by using a stream . Diffe rent types ofstream s a re ava ilable for diffe rent types of resources. Another fac tor is the direc t ion of the stream —som etim es you wil l want towrite da ta to a resource , and som etim es you wil l want to read da ta from a resource .

Page 259: Land of Lisp - Barski M.D., Conrad

Streams by Type of Resource

W hen organized by the type of resource on which they opera te , the fol lowing a re the m ost com m only used stream types: Consolestreams

W hat we’ve been using so fa r when com m unica ting with the RE PL .F ile streams

L et us read and write to fi le s on our hard drive .Socket streams

L et us com m unica te with other com pute rs on a ne twork.Str ing streams

L et us send and rece ive text from a L isp string.Of these stream types, string stream s a re the black sheep of the fam ily. Ra ther than le t t ing you com m unica te with the outside

world, string stream s a l low you to m anipula te strings in new and inte rest ing ways.

Page 260: Land of Lisp - Barski M.D., Conrad

Streams by Direction

W hen you write da ta to a resource , you use output streams. For reading da ta from a resource , you use input streams.

O utput Streams

Output stream s a re used for tasks such as writ ing to the RE PL , writ ing to a fi le , or sending inform ation over a socke t . At the m ostprim it ive leve l , you can do two things with an output stream :

Check whether the stream is va lid.Push a new i tem onto the stream .

Page 261: Land of Lisp - Barski M.D., Conrad
Page 262: Land of Lisp - Barski M.D., Conrad

As you can see , a stream is m ore restric t ive than a true da ta struc ture in L isp. For instance , a l ist supports a l l of the sam efea tures as a stream (we can push a new i tem onto a l ist with push and check if a l ist is va l id with listp), and we a lso can docerta in tasks with a l ist tha t we can’t do with an output stream (such as changing i tem s in the l ist with setf). But this l im itedfunc tiona li ty of stream s ac tua lly m akes them useful in m any cases.

T o see if we have a va lid output stream , we can use the output-stream-p func tion. For exam ple , the RE PL has an output streamassoc ia ted with i t ca l led *standard-output*. W e can see if this is a va lid output stream with the fol lowing code :> (output-stream-p *standard-output*)

TA L isp charac te r is one i tem tha t can be pushed onto an output stream using the basic com m and write-char. For exam ple , to

write the charac te r #\x to the *standard-output* stream , we can run the fol lowing com m and:> (write-char #\x *standard-output*)

xNILT his code prints an x to the standard output (which, in this case , is the sam e as the RE PL ). Note tha t this func tion a lso re turns

nil, causing the x and the re turn va lue to be printed on the sam e l ine . As you saw in Chapte r 6, this extra nil is just a side e ffec tof running the code in the RE PL . If we ran this com m and as part of a la rger program , only the x would have printed out .

Note

In this chapte r, we’l l discuss only stream s based on text charac te rs. In Com m on L isp, you can a lso c rea te stream s based on otherda ta types. For instance , i f you’re working with binary da ta , you m ay want to send or rece ive raw bytes instead of charac te rs. Butfor our purposes, m anipula t ing textua l da ta (and hence using stream s tha t work with text charac te rs) is the m ost convenient .

Input Streams

Input stream s a re used for reading da ta . As with output stream s, the ac t ions tha t you can perform with an input stream arel im ited. At the m ost prim it ive leve l , you can do two things with an input stream :

Check whether the stream is va lid.Pop an i tem off of the stream .

W e can see if we have a va lid stream with the input-stream-p com m and. For instance , as with standard output , the RE PL hasan assoc ia ted input stream ca lled *standard-input*, which we can va lida te as fol lows:> (input-stream-p *standard-input*)

T

Page 263: Land of Lisp - Barski M.D., Conrad
Page 264: Land of Lisp - Barski M.D., Conrad

W e can pop an i tem off the stream with the read-char com m and. Since we’re reading from the RE PL , we need to type som echarac te rs and press ente r to send the da ta into the standard input stream :> (read-char *standard-input*)

123#\1

As you can see , the 1 a t the front of the stream was popped off and re turned by read-char.

USING O TH ER CO M M ANDS TO INTERACT WITH STREAM S

In addit ion to write-char and read-char, Com m on L isp has m any other com m ands for inte rac ting with stream s. In fac t , a l l thecom m ands for print ing and reading introduced in Chapte r 6 can accept a stream as an extra param ete r, which le ts us use L isp'spowerful input/output abil i t ie s with any stream . For instance , we can explic i t ly te l l the print com m and to print to *standard-output*, a s fol lows:> (print 'foo *standard-output*)

FOOT his can be use ful when working with stream s other than *standard-output*, a s you'l l see short ly.

Page 265: Land of Lisp - Barski M.D., Conrad

Working with F iles

In addit ion to using stream s to write to and read from the RE PL , we can a lso use stream s to write to and read from fi les.You can c rea te a fi le stream in Com m on L isp in severa l ways. T he best way is to use the with-open-file com m and. As you’l l

see short ly, this com m and conta ins spec ia l bug-prevention fea tures tha t m ake i t sa fe r to use than other ava ilable fi le com m ands.T he following exam ple uses with-open-file to write the string "my data" to a fi le nam ed data.txt:

> (with-open-file ( my-stream "data.txt" :direction :output)

(print "my data" my-stream))

In this exam ple , the with-open-file com m and binds the output stream to the nam e my-stream . T his causes a fi le outputstream to be c rea ted with the nam e my-stream. T his stream will be ava ilable within the body of the with-open-file com m and

(unti l the fina l c losing bracke t ), and any da ta we send to this stream will end up in the fi le nam ed data.txt on the disk. T he

print com m and re fe rences my-stream a s the dest ina tion for i ts output . T here fore , a fte r running this exam ple , you should finda new fi le nam ed data.txt in the folder from which you launched CL ISP. T his fi le has the text "my data" as i ts content .

Spec ifying :output a s the direc t ion for with-open-file c rea tes an output stream . T o m ake this an input stream instead, wecould change the direc t ion to :input, a s fol lows:> (with-open-file (my-stream "data.txt" :direction :input)

(read my-stream))"my data"

As you can see , this causes the da ta—the sam e da ta writ ten to the fi le in the previous exam ple—to be read in from the fi le .As you lea rned in Chapte r 6, the print and read com m ands can print and read any of the basic Com m on L isp da ta types. T his

func tiona li ty m akes i t easy to use stream s to store da ta from your program s to the hard drive . Here is a m ore com plica ted exam pletha t writes an assoc ia t ion l ist (a l ist) to a fi le :

> (let ((animal-noises '((dog . woof)(cat . meow))))

(with-open-file (my-stream "animal-noises.txt" :direction :output)(print animal-noises my-stream)))((DOG . WOOF) (CAT . MEOW))

> (with-open-file (my-stream "animal-noises.txt" :direction :input)(read my-stream))((DOG . WOOF) (CAT . MEOW))

In this exam ple , we’re c rea ting an assoc ia t ion table of anim als and the sounds they m ake . W e c rea te a new a list nam ed animal-

noises . W e put keys for dog and cat into this l ist . Now we can write this a l ist to a new fi le ca l led animal-noises.txt

. L a te r, we can easi ly reconsti tute this a l ist from the fi le .T he with-open-file com m and can take keyword param ete rs tha t m odify i ts behavior. For instance , you can te l l the com m and

what to do if a fi le with the given nam e a lready exists. In the fol lowing exam ple , we’l l display an e rror m essage using the :if-exists keyword param ete r:> (with-open-file (my-stream "data.txt" :direction :output :if-exists :error)

(print "my data" my-stream))*** - OPEN: file #P"/home/user/data.txt" already exists

Alte rna tive ly, you m ay sim ply want the exist ing fi le to be overwrit ten. In tha t case , se t the :if-exists keyword param ete r to:supersede, a s fol lows:> (with-open-file (my-stream "data.txt" :direction :output

:if-exists :supersede)(print "my data" my-stream))"my data"

T he with-open-file com m and gives you a very succ inc t way to work with fi le s. Unlike m ost program m ing languages, whenusing this com m and, you don’t need to open and c lose fi le s m anua lly, and you don’t need to worry about potentia l ly m essing upyour fi le s by fa i l ing to properly c lose them . (Actua lly, Com m on L isp has lower-leve l com m ands for opening and c losing fi le s aswell , but with-open-file packages them in a c lean way tha t hides a l l the ugly de ta i ls. )

Page 266: Land of Lisp - Barski M.D., Conrad

T he m ain purpose of with-open-file is to acquire a fi le resource . It takes com m and of the fi le and assum es the responsibil i ty ofc losing i t . In fac t , even if the code inside the with-open-file throws an ugly e rror tha t stops the program dead, with-open-filewill st i l l c lose the fi le properly to m ake sure this resource stays intac t .

Note

Com m on L isp has m any com m ands tha t begin with with- tha t wil l sa fe ly a l loca te resources in this way. T hese with- com m ands,ava ilable in the core L isp l ibra ries, a re buil t with L isp’s awesom e m acro system . You’l l lea rn m ore about L isp m acros, and how tocrea te your own with- com m ands, in Chapte r 16.

Page 267: Land of Lisp - Barski M.D., Conrad

Working with Sockets

Now tha t we’ve used stream s to com m unica te with the RE PL and with fi le s, le t’s see how we can use them to com m unica te withanother com pute r.

If you want to write a program tha t can com m unica te with another com pute r e lsewhere on a standard ne twork (a lm ost a l lne tworks nowadays use the T CP/IP protocol), you’l l fi rst need to c rea te a socke t . A socke t is a m echanism for routing da ta over acom pute r ne twork be tween program s running on diffe rent com pute rs on tha t ne twork.

Unfortuna te ly, socke ts didn’t m ake i t into the ANSI Com m on L isp standard, which m eans the re ’s no standard way of inte rac tingwith socke ts a t this t im e . However, every version of Com m on L isp supports socke ts, even if i t doesn’t fol low any standard. Sincewe’ve been using CL ISP as our L isp of choice in this book, we’l l consider only CL ISP’s socke t com m ands.

Note

c l-socke ts (http: / /com m on-lisp.ne t/projec t/c l-socke ts/) and usocke t (http: / /com m on-lisp.ne t/projec t/usocke t/) a re two a t tem pts a tadding a standard socke t l ibra ry to Com m on L isp.

Page 268: Land of Lisp - Barski M.D., Conrad

Socket Addresses

E very socke t within a ne twork m ust have a socke t address. T his socke t address has two com ponents: IP addressA num ber tha t unique ly identifies a com pute r on the ne twork (typica lly shown as 4 bytes de lim ited by periods, such as

192.168.33.22).P ort number

Any program s tha t want to use the ne twork m ust choose a unique port num ber tha t no other program on the sam e com pute r isa lready using.

T he IP address and the port num ber com bine to m ake up the socke t address. Since the IP address is unique on a ne twork and theport num ber is unique for a given com pute r, every socke t address on a ne twork is unique to a spec ific program running on a spec ificcom pute r. Any m essages running over the ne twork (through chunks of da ta ca l led TCP packe ts) wil l be labe led with a socke taddress to indica te the ir dest ina tion.

Once a com pute r rece ives a packe t labe led with i ts IP address, the opera t ing system will look a t the port num ber in the socke taddress of the m essage to figure out which program should rece ive the m essage .

And how does the opera t ing system know which program rece ives m essages for a given port? It knows this because a program firstm ust c rea te a socke t for tha t port in order to use i t . In other words, a socke t is sim ply a way for a com pute r program to te l l theopera t ing system , “Hey, if you ge t any m essages on port 251, send them m y way!”

Page 269: Land of Lisp - Barski M.D., Conrad

Socket Connections

In order to ac tua lly send a m essage over a socke t be tween two program s, we first need to fol low som e steps to ini t ia l ize a socke tconnec tion. T he first step in c rea ting such a connec tion is to have one of the program s c rea te a socke t tha t sta rts in a l isteningsta te , wa it ing to see if other program s on the ne twork want to sta rt a com m unica tion. T he com pute r with the socke t in a l isteningsta te is ca l led the server. T hen the other program , ca l led a c lient , c rea tes a socke t on i ts end and uses i t to establish a connec tionwith the se rver. If a l l goes well , these two program s can now transm it m essages ac ross the socke t connec tion running be tween them .

But enough ta lk. L e t’s t ry connec ting two program s right now to see the m agic happen for ourse lves!

Page 270: Land of Lisp - Barski M.D., Conrad

Sending a M essage over a Socket

First , open two copies of CL ISP in two diffe rent console windows on your com pute r. W e’ll ca l l one the c l ient and one the se rver.(Or, i f you have two com pute rs on a ne twork and know the ir IP addresses, you can c rea te the two consoles on two separa tem achines, for the ful l ne twork experience . )

Note

You must use CL ISP to ge t the socke t code shown in this chapte r to run.

On the se rver, take control of a port by ca l l ing socket-server:> (defparameter my-socket (socket-server 4321)) ;ON THE SERVER

MY-SOCKETT his com m and acquires port 4321 and binds a socke t to i t using the opera t ing system . T he socke t is bound to the my-socket

variable so tha t we can inte rac t with i t .T his com m and is som ewhat dangerous, because the opera t ing system is expec ting us to give up the socke t once we’re finished

with i t . If we don’t , no one wil l be able to use this socke t anym ore . In fac t , i f you m ake any m istakes during this socke t exerc ise ,you could m ess up the socke t a t port 4321, and then you would need to switch to another port num ber unti l you resta rt yourcom pute r. (In the next chapte r, you’l l lea rn how to use the exception handling system in Com m on L isp to work a round these uglyproblem s. )

Next, le t’s m ake a stream from this socke t (st i l l on the se rver) tha t handles a connec tion from a single c l ient:> (defparameter my-stream (socket-accept my-socket)) ;ON THE SERVERAfte r running this com m and, the se rver wil l seem to lock up, and you won’t be re turned to the RE PL prom pt. Don’t be a la rm ed

—the socket-accept com m and is a block ing operation, which m eans the func tion won’t exit unti l a c l ient has connec ted.Now switch over to your c l ient CL ISP and use the socket-connect com m and to connec t to tha t socke t on the se rver:> (defparameter my-stream (socket-connect 4321 "127.0.0.1")) ;ON THE CLIENT

MY-STREAMT he IP address 127.0.0.1 is a spec ia l address tha t a lways points to the com pute r from which i t’s ca l led. If you a re using two

diffe rent com pute rs for this exerc ise , you should ente r the ac tua l IP address of your se rver.Afte r running this com m and, the se rver wil l unlock, and the va lue of the my-stream va riable wil l be se t . W e now have a stream

open in both copies of CL ISP, and we can use i t to com m unica te be tween them !T he stream CL ISP has c rea ted here is ca l led a bidirec tional stream . T his m eans i t can ac t both as an input stream and an output

stream , and we can use e i the r se t of com m ands on i t to com m unica te in both direc t ions. L e t’s send a cordia l gree ting be tween thec lient and the se rver.

E nte r the fol lowing on the c l ient:> (print "Yo Server!" my-stream)

"Yo Server!"And ente r the fol lowing on the se rver:> (read my-stream)

"Yo Server!"T hen, st i l l on the se rver, ente r this:> (print "What up, Client!" my-stream)

"What up, Client!"Back on the c l ient , run this com m and:> (read my-stream)

"What up, Client!"Here’s wha t your two CL ISP windows should look l ike when you’re finished:

Page 271: Land of Lisp - Barski M.D., Conrad

T he m essage we sent ac ross the socke t was a L isp string, but because of L isp’s e legant stream -handling capabil i t ie s, we couldsend a lm ost any standard L isp da ta struc ture in the sam e way, without any extra e ffort!

Page 272: Land of Lisp - Barski M.D., Conrad

Tidying Up After O urse lves

It’s c ruc ia l tha t we free up the resources we’ve c rea ted during this exerc ise . First , run the fol lowing com m and on both the c l ientand the se rver to c lose the stream on both ends:> (close my-stream)

TNext, run socket-server-close on the se rver to free up the port and disconnec t the socke t from it . If you don’t , port 4321 wil l

be unusable unti l you reboot.> (socket-server-close my-socket)

NIL

Page 273: Land of Lisp - Barski M.D., Conrad

Str ing Streams: The O ddball Type

Stream s a re usua lly used for com m unica ting with the outside world from within a L isp program . One exception to this is thestring stream , which sim ply m akes a string look l ike a stream . In the sam e way you can read or write to exte rna l resources withother types of stream s, a string stream will le t you read or write to a string.

You can c rea te string stream s with the make-string-output-stream and make-string-input-stream com m ands. Following is anexam ple tha t uses make-string-output-stream:> (defparameter foo (make-string-output-stream))

> (princ "This will go into foo. " foo)> (princ "This will also go into foo. " foo)> (get-output-stream-string foo)"This will go into foo. This will also go into foo. "

You m ay be wondering why anyone would want to do this, since we can a lready direc t ly m anipula te strings in L isp, without usingstream s. Ac tua lly, the re a re severa l good reasons for using string stream s in this way. T hey a re use ful for debugging, as well a s forc rea ting com plex strings e ffic iently.

Page 274: Land of Lisp - Barski M.D., Conrad

Sending Streams to F unctions

Using string stream s a l lows us to use func tions tha t require stream s as param ete rs. T his is grea t for debugging code tha t workswith fi le s or socke ts, using only strings for the input and output of da ta .

For exam ple , suppose we have a func tion write-to-log tha t writes log inform ation to a stream . Usua lly, we would want to sendthe log inform ation to a fi le stream , so i t can be writ ten to a fi le for sa fekeeping. However, i f we want to debug the func tion, wem ay want to send i t a string stream instead, so we can take a look a t the da ta i t wri tes and m ake sure i t ’s correc t . If we had hard-coded the write-to-log func tion to only write to a fi le , we wouldn’t have this flexibil i ty. T his is why i t m akes sense to writefunc tions to use the abstrac t concept of a stream whenever possible , instead of using other m ethods to access exte rna l resources.

Page 275: Land of Lisp - Barski M.D., Conrad

Working with Long Str ings

String stream s can lead to be tte r-perform ing code when dea ling with very long strings. For instance , conca tena ting two stringstoge ther can be a cost ly opera t ion—first , i t requires a new block of m em ory to be a l loca ted to hold both strings, and then thestrings need to be copied into this new loca tion. Because of this bott leneck, m any program m ing languages use devices ca l led stringbuilders to avoid this overhead. In L isp, we can ge t sim ila r pe rform ance benefi ts by using string stream s.

Page 276: Land of Lisp - Barski M.D., Conrad

Reading and Debugging

Another reason for using string stream s is tha t they can m ake our code easie r to read and debug, espec ia l ly when we use thewith-output-to-string m acro.

Here’s an exam ple of this com m and be ing used:

> (with-output-to-string (*standard-output*)

(princ "the sum of ")(princ 5)(princ " and ")(princ 2)(princ " is ")(princ (+ 2 5)))

"the sum of 5 and 2 is 7"

T he with-output-to-string m acro wil l inte rcept any text tha t would otherwise be output to the console , RE PL , or other

output stream , and capture i t a s a string. In the preceding exam ple , the output c rea ted by the princ func tions within the bodyof the with-output-to-string ca l l is redirec ted autom atica l ly into a string stream . Once the body of the with-output-to-string

com m and has com ple ted, the entire printed output tha t was put into the stream is re turned as a result .You can a lso use the with-output-to-string m acro to easi ly construc t com plex strings by “print ing” each part , and then

capturing the result a s a string. T his tends to be m uch m ore e legant and e ffic ient than using the concatenate com m and.

Note

Using with-output-to-string runs counte r to the tene ts of func tiona l program m ing (discussed in Chapte r 14). Som e L ispersconsider this func tion (and sim ila r func tions tha t inte rcept input or output intended for other dest ina tions) to be an ugly hack.You’ll see som e disagreem ent in the L isp com m unity about whe ther the use of with-output-to-string is e legant or ugly.

Page 277: Land of Lisp - Barski M.D., Conrad

What You've Learned

T his chapte r desc ribed how to use stream s to le t your L isp program s inte rac t with outside resources. You lea rned the fol lowing:Diffe rent types of stream s inte rac t with diffe rent types of resources. T hese inc lude console streams, f i le streams, socke t streams,

and string streams.Stream s can be ca tegorized based on the ir direc t ion. Output streams le t us write to a resource . Input streams le t us read from a

resource .Socke t stream s a l low com pute r program s to com m unica te over a ne twork. T o establish a socke t stream , we m ust first open

socke ts on both ends and open a socke t connec tion be tween the program s.String stream s a l low us to use func tions tha t require stream s without l inking to an outside resource , for debugging purposes.

T hey a lso a re use ful for construc ting com plex strings e ffic iently and e legantly through the use of with-output-to-string.

Page 278: Land of Lisp - Barski M.D., Conrad

Chapter 13. Let's Create a Web Server!

In Chapte r 6, you lea rned how to inte rac t with a use r by sending text to and from the RE PL . However, when people ta lk about“ inte rac ting with a use r” these days, they’re usua lly re fe rring to a use r on the W eb. In this chapte r, you’re going to lea rn how tointe rac t with use rs on the W eb by building a web se rver from sc ra tch. Since com m unica tions over a ne twork a re e rror prone bythe ir na ture , you’l l fi rst lea rn how errors a re handled in L isp.

Page 279: Land of Lisp - Barski M.D., Conrad

Error H andling in Common Lisp

Any t im e you’re inte rac ting with the outside world, a s our web se rver wil l , unexpec ted things can happen. No m atte r how sm art am odern com pute r ne twork m ay be , i t can never antic ipa te every possible exceptiona l si tua t ion. Afte r a l l , even the sm artestne twork can’t recover from som e fool t ripping over the wrong cable .

Com m on L isp has a very extensive se t of fea tures for dea ling with unexpec ted exceptiona l si tua t ions in your code . T hisexception handling system is ve ry flexible , and i t can be used to do things tha t a re im possible with exception system s in m ost otherlanguages.

Page 280: Land of Lisp - Barski M.D., Conrad

Signaling a Condition

If you’re writ ing a func tion and som ething goes horribly wrong, a L isp func tion can notify the L isp environm ent tha t a problemhas been encounte red. T his is done by signaling a condit ion. W hat sort of things could go wrong? Maybe a func tion tried to divideby ze ro. Or m aybe a l ibra ry func tion rece ived a param ete r of the wrong type . Or m aybe a socke t com m unica tion was inte rruptedbecause you tripped over your ne twork cable .

If you want to signa l a condit ion direc t ly, you can do so with the error com m and. You would do this i f a func tion you wrotede tec ted a problem on i ts own—a problem so se rious the program just could not continue norm ally. Using the error com m and wil linte rrupt your running L isp program , unless you inte rcept the e rror e lsewhere to prevent an inte rruption. L e t’s signa l a condit ionand print the m essage “ foo” to describe the e rror:> (error "foo")

*** - fooThe following restarts are available:ABORT :R1 Abort main loop>

As you can see , signa ling this condit ion causes L isp to inte rrupt our program , print the m essage “ foo, ” and show an e rror prom pta t the RE PL . (In CL ISP, you can type :a a t this point to abort the program and re turn to the norm al RE PL . ) Most of the t im e yourprogram signa ls a condit ion, i t wil l probably not be because you ca lled error yourse lf. Instead, i t wil l be because your program hasa bug, or because you ca lled a l ibra ry func tion, and tha t func tion signa ls a condit ion. However, any t im e som ething preventsnorm al execution in your program , leading to a condit ion, your program will stop and show an e rror prom pt such as in thepreceding exam ple .

Page 281: Land of Lisp - Barski M.D., Conrad

Creating Custom Conditions

In our first exam ple , we passed a string describing the condit ion to the error com m and. However, this text string just custom izesthe e rror m essage and doesn’t lead to a diffe rent “ type” of condit ion. Com m on L isp a lso a l lows you to have various types ofcondit ions tha t can be handled in diffe rent ways.

A m ore sophist ica ted way to signa l condit ions is to first de fine a custom condit ion using define-condition, a s in the fol lowingexam ple :

(define-condition foo () ()

(:report (lambda (condition stream)

(princ "Stop FOOing around, numbskull!" stream))))

T his is a typica l exam ple of c rea ting a new type of condit ion, which we’ve nam ed foo . W hen this condit ion is signa led,

we can supply a custom func tion tha t wil l be ca l led to report the e rror. Here , we dec la re a lam bda func tion for this purpose .

W ithin the lam bda func tion, we print a custom m essage to report the e rror .L e t’s see wha t happens when we trigger this new condit ion:> (error 'foo)

*** - Stop FOOing around, numbskull!The following restarts are available:ABORT :R1 Abort main loop>

As you can see , our custom m essage was printed. T his technique a l lows the program m er to ge t a m ore m eaningful e rror report ,custom ized for the spec ific condit ion tha t was triggered.

Page 282: Land of Lisp - Barski M.D., Conrad

Intercepting Conditions

W hen we c rea te a condit ion with define-condition, i t ’s given a nam e (such as foo). T his nam e can be used by the higher-leve lparts of our program to inte rcept and handle tha t condit ion, so i t won’t stop the program ’s execution. W e can do this with thehandler-case com m and, as fol lows:> (defun bad-function ()

(error 'foo))BAD-FUNCTION

> (handler-case (bad-function)

(foo () "somebody signaled foo!")(bar () "somebody signaled bar!"))

"somebody signaled foo!"T he first thing we put inside a handler-case com m and is the piece of code tha t m ay signa l condit ions tha t we want to handle

.In this exam ple , the code we’re watching is a ca l l to bad-function. T he rest of handler-case le ts us spec ify ac t ions to perform

if a pa rt icula r condit ion occurs . W hen this code is run, bad-function signa ls the foo condit ion by ca ll ing (error 'foo).Usua lly, this would cause our program to be inte rrupted and lead to a e rror prom pt a t the RE PL . However, our handler-case

com m and inte rcepts the foo condit ion . T his m eans tha t the program can keep running without inte rruption, with the

handler-case eva lua ting as “som ebody signa led foo!” .

Page 283: Land of Lisp - Barski M.D., Conrad

P rotecting Resources Against Unexpected Conditions

W hen an unexpec ted exception happens in a program , the re is a lways a risk tha t i t could bring down your program , or even causedam age to resources outside your program . E xceptions inte rrupt the regula r flow of your code , and they m ay stop your code dead ini ts t racks, even while i t ’s in the m iddle of a sensi t ive opera t ion.

For instance , your program m ay be writ ing to a fi le or to a socke t stream when an unexpec ted exception happens. In this case , i tis c ri t ica l ly im portant tha t your program has an opportunity to c lose the fi le /socke t stream and free the fi le handle or socke t;otherwise , tha t resource m ay becom e locked indefini te ly. If such resources a ren’t c leaned up properly, the use rs m ay need to rebootthe ir com pute r first be fore the resource becom es ava ilable aga in.

T he unwind-protect com m and can he lp us to avoid these problem s. W ith this com m and, we can te l l the L isp com pile r, “T hispiece of code m ust run no m atte r wha t happens. ” Consider the fol lowing exam ple :

> (unwind-protect (/ 1 0)

(princ "I need to say 'flubyduby' matter what"))*** - /: division by zeroThe following restarts are available:ABORT :R1 Abort main loop> :r1I need to say 'flubyduby' matter what>

W ithin the unwind-protect, we divide by 0, which signa ls a condit ion . But even a fte r we te l l CL ISP to abort , the

program st i l l prints i ts c ruc ia l m essage .W e can usua lly avoid ca l l ing unwind-protect direc t ly by re lying on Com m on L isp’s “with-” m acros; m any of these ca l l

unwind-protect them se lves, under the hood. In Chapte r 16, we’l l c rea te our own m acros to see how this is possible .

Note

In the com ic book epilogue a t the end of the book, you’l l lea rn about an addit iona l fea ture of the Com m on L isp signa ling systemca lled restarts.

Page 284: Land of Lisp - Barski M.D., Conrad

Writing a Web Server from Scratch

Now tha t you have a basic understanding of socke ts (covered in Chapte r 12) and e rror handling, you know enough to m ake a webserver tha t can se rve dynam ic web pages writ ten in L isp. Afte r a l l , why should Apache (the world’s m ost popula r web se rver) havea ll the fun?

Page 285: Land of Lisp - Barski M.D., Conrad

H ow a Web Server Works

Hypertext T ransfe r Protocol , or HT T P, is the Inte rne t protocol used for transfe rring web pages. It adds a layer on top of T CP/IPfor requesting pages once a socke t connec tion has been established. W hen a program running on a c l ient com pute r (usua lly a webbrowser) sends a properly encoded request , the se rver wil l re trieve the requested page and send i t over the socke t stream inresponse .

Note

T his web se rver is adapted from http. l isp, c rea ted by Ron Garre t .

For exam ple , suppose the c l ient is the Fire fox web browser, and i t a sks for the page lolcats. html . T he c l ient’s request m ight looklike this:GET /lolcats.html HTTP/1.1

Host: localhost:8080User-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.5)Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-us,en;q=0.5Accept-Encoding: gzip,deflateAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7Keep-Alive: 300Connection: keep-alive

For our web se rver, the m ost im portant pa rt of this request is the first l ine . T here we can see the type of request m ade (a GETrequest , which m eans we just want to look a t a page without m odifying i t), and the nam e of the page requested (lolcats. html). T hisda ta sent to the se rver is ca l led the request header. You’l l see la te r tha t addit iona l inform ation can be sent to the se rver be low therequest header, in a request body .

Note

T o readers from the distant future , lolcats was a vira l Inte rne t phenom enon from early in the third m il lennium . It involvedpic tures of ca ts with funny captions. If people of your t im e a re no longer fam ilia r with lolca ts, i t is of no grea t loss.

In response , the se rver wil l send an HT ML docum ent tha t represents the web page over the socke t stream . T his is ca l led theresponse body . Here is wha t a response body m ight look l ike :

Page 286: Land of Lisp - Barski M.D., Conrad

<html>

<body>

Sorry dudez, I don't have any L0LZ for you today :-(</body>

</html>

An HT ML docum ent is wrapped in html opening and c losing tags . W ithin these tags, you can dec la re a body sec tion

. In the body sec tion, you can write a text m essage tha t wil l be displayed in the web browser as the body of the web page

.For a ful ly HT ML -com pliant web page , other i tem s m ust exist in the docum ent, such as a DOCT YPE dec la ra t ion. However, our

exam ple wil l work just fine , and we can ignore these technica l de ta i ls for our sim ple dem onstra t ion.A web se rver wil l typica lly a lso genera te a response header. T his header can give a web browser addit iona l inform ation about the

docum ent i t has just rece ived, such as whe ther i t is in HT ML or another form at. However, the sim plified web se rver we’re going tocrea te does not genera te such a header and instead sim ply re turns a body.

Note

Since we’re using CL ISP-spec ific socke t com m ands, you m ust be running CL ISP for the sam ple web se rver presented in thischapte r to work.

Page 287: Land of Lisp - Barski M.D., Conrad

Request P arameters

W eb form s a re an essentia l e lem ent in powering websites. For instance , suppose we c rea te a sim ple login form for a website .

Afte r the visi tor to our website hi ts the Subm it button on this page , i t wil l send a POST request back to the website . A POSTrequest looks very sim ila r to the GET request in the preceding exam ple . However, a POST request usua lly ca rries the expec ta t iontha t i t m ay a l te r da ta on the se rver.

In our sam ple login form , we need to te l l the se rver the use r ID and password tha t the visi tor to our si te had ente red into the textfie lds on this form . T he va lues of these fie lds tha t a re sent to the se rver as part of the POST request a re ca l led request parameters.T hey a re sent within the POST request by appending them be low the request header, in the a rea tha t m akes up the request body.

T his is wha t the POST request m ight look l ike for our login exam ple :POST /login.html HTTP/1.1

Host: www.mywebsite.comUser-Agent: Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.5)Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8Accept-Language: en-us,en;q=0.5Accept-Encoding: gzip,deflateAccept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7Keep-Alive: 300Connection: keep-aliveContent-Length: 39

userid=foo&password=supersecretpasswordT he extra param ete r in the header of this POST request , Content-Length, indica tes the length of the param ete r da ta a t the

bottom of the request . Spec ifica l ly, Content-Length: 39 te l ls the se rver tha t the text conta ining the request pa ram ete rs is39 charac te rs long.

Request P arameters for G ET Requests

As we’ve discussed, the typica l purpose of request pa ram ete rs is to send web form da ta back to the se rver during a POST request .However, GET requests m ay a lso conta in request pa ram ete rs. Usua lly, with a GET request , we want to see wha t the param ete rs a re inthe URL of the request , whereas with a POST request , the param ete rs a re hidden in the body of the request .

For instance , suppose you go to Google and search for “dogs. ” In this case , the fol low-up page wil l have a URL tha t readssom ething l ike ht tp: / /www.google . com /search?q=dogs& hl=en& safe=off& . . . . T hese va lues in the URL (such as the one sta t ing tha tthe [q]uery=“dogs”) a re a lso request pa ram ete rs.

T he web se rver we’re c rea ting wil l need to give the se rver code access to both types of request pa ram ete rs: the ones in the bodyof the request (as is com m on with POST requests) as well a s the ones tha t appear in the URL (as is com m on with GET requests. )

Decoding the Values of Request P arameters

HT T P has a spec ia l way to represent the nona lphanum eric charac te rs tha t a use r m ight ente r into a form , using HTTP escapecodes. T hese escape codes le t you have charac te rs in the va lues of a request pa ram ete r tha t would not otherwise be ava ilable in theHT T P form at. For instance , i f a use r ente rs "foo?", i t wil l appear in the request as "foo%3F", since the quest ion m ark isrepresented with an escape code . Our web se rver wil l need to decode these escape charac te rs, so the first func tion we’l l wri te isdecode-param:

(defun http-char (c1 c2 &optional (default #\Space))

(let ((code (parse-integer(coerce (list c1 c2) 'string):radix 16:junk-allowed t)))(if code(code-char code)default)))

(defun decode-param (s)

(labels ((f (lst)(when lst(case (car lst)(#\% (cons (http-char (cadr lst) (caddr lst))

Page 288: Land of Lisp - Barski M.D., Conrad

(f (cdddr lst))))

(#\+ (cons #\space (f (cdr lst))))

(otherwise (cons (car lst) (f (cdr lst))))))))

(coerce (f (coerce s 'list)) 'string)))

Note

T he HT T P escape codes we a re discussing here a re unre la ted to the escape charac te rs in L isp strings we’ve discussed in otherparts of this book.

First , this func tion defines a loca l func tion nam ed f , which we’l l use to recursive ly process the charac te rs. T o m ake this

recursion work, we need to use coerce to turn the string into a l ist of charac te rs , and then pass this l ist to f.T he f func tion checks the first charac te r in the l ist to see if i t ’s a pe rcent sign (%) or a plus sign (+). If i t ’s a pe rcent sign, we

know tha t the next va lue in the l ist is an ASCII code , represented as a hexadec im al num ber. (ASCII codes a re a standard se t ofnum bers tha t correspond to text charac te rs, shared am ong m any com pute r system s and applica t ions. )

T o decode this ASCII code , we’ve c rea ted a func tion nam ed http-char . In this func tion, we use the parse-integer

func tion to convert this string to an integer . In this case , we’re using som e keyword param ete rs on parse-integer: the:radix pa ram ete r, which te l ls the func tion to parse a hexadec im al num ber, and the :junk-allowed pa ram ete r, which te l ls i t tojust re turn nil when an inva lid num ber is given, ra ther than signa ling an e rror.

W e then use the code-char func tion to convert this integer (which holds an ASCII code) into the ac tua l charac te r tha t the use rente red.

As per the rules of HT T P encoding, i f a va lue in a request pa ram ete r conta ins a plus sign, i t should be transla ted into a space

charac te r. W e m ake this conversion here .Any other charac te r passes through the f func tion unchanged. However, we st i l l need to ca l l f on the rem ainder of the l ist unti l

a l l the charac te rs have been processed .Here a re som e exam ples of decode-param in ac t ion:> (decode-param "foo")

"foo"> (decode-param "foo%3F")"foo?"> (decode-param "foo+bar")"foo bar"

Decoding Lists of Request P arameters

T he next thing our se rver needs to do is to decode a l ist of pa ram ete rs, which wil l be given as nam e/va lue pa irs in a string suchas "name=bob&age=25&gender=male". As we’ve discussed, URL s for web pages often conta in such nam e/va lue pa irs a t the end. Asyou can see , this string says tha t the person we’re looking for on the web page has a nam e of bob, an age of 25, and a gender ofm ale . T hese nam e/va lue pa irs a re separa ted by an am persand (&). T he struc ture of these strings is equiva lent to tha t of anassoc ia t ion l ist (a l ist), so we’l l store these param ete rs as an a l ist using the fol lowing func tion:(defun parse-params (s)

(let* ((i1 (position #\= s))(i2 (position #\& s)))

(cond (i1 (cons (cons (intern (string-upcase (subseq s 0 i1)))

(decode-param (subseq s (1+ i1) i2)))

(and i2 (parse-params (subseq s (1+ i2))))))((equal s "") nil)(t s))))

T he parse-params func tion finds the first occurrence of an am persand (&) and equa l sign (=) in the string, using the position

func tion . If a nam e/va lue pa ir is found (we know this is t rue if an equa l sign was found in the string and is stored in i1), we

use the intern func tion to convert the nam e into a L isp sym bol . W e cons this nam e to the va lue of the param ete r, which we

decode with our decode-param func tion . Fina lly, we recursive ly ca l l parse-params on the rem ainder of the string .L e t’s give our new parse-params func tion a try:> (parse-params "name=bob&age=25&gender=male")

((NAME . "bob") (AGE . "25") (GENDER . "male"))

Page 289: Land of Lisp - Barski M.D., Conrad

Putt ing this da ta into an a l ist wil l a l low our code to easi ly re fe rence a spec ific va riable whenever tha t’s necessa ry.

Note

Both decode-param and parse-params could achieve higher perform ance if they were writ ten using a ta i l ca l l , a s we’l l discuss inChapte r 14.

Page 290: Land of Lisp - Barski M.D., Conrad

P arsing the Request H eader

Next, we’l l wri te a func tion to process the first l ine of the request header. (T his is the l ine tha t wil l look som ething l ike GET/lolcats.html HTTP/1.1).

T he following parse-url func tion wil l process these strings:(defun parse-url (s)

(let* ((url (subseq s(+ 2 (position #\space s))

(position #\space s :from-end t)))

(x (position #\? url)))(if x

(cons (subseq url 0 x) (parse-params (subseq url (1+ x))))

(cons url '()))))

T his func tion first uses the string’s de lim it ing spaces to find and extrac t the URL . It then checks this URL for a quest ion

m ark, which m ay indica te tha t the re a re request pa ram ete rs tha t need to be handled . For instance , i f the URL is lolcats. html?ex tra-funny=yes, then the quest ion m ark le ts us know tha t the re is a pa ram ete r nam ed ex tra-funny in the URL . If such param ete rs

exist , we’l l need to extrac t them , and then parse them using our parse-params func tion . If the re a ren’t any request

param ete rs, we just re turn the URL . Note tha t this func tion skips over the request m ethod (m ost often GET or POST). A fanc ie rweb se rver would extrac t this da ta point as well .

L e t’s t ry out our new URL extrac tor:> (parse-url "GET /lolcats.html HTTP/1.1")

("lolcats.html")> (parse-url "GET /lolcats.html?extra-funny=yes HTTP/1.1")("lolcats.html" (EXTRA-FUNNY . "yes"))

Now tha t we can read the first l ine , we’l l process the rest of the request . T he following get-header func tion wil l convert therem aining l ines of the request into a nice a l ist :(defun get-header (stream)

(let* ((s (read-line stream))(h (let ((i (position #\: s)))(when i(cons (intern (string-upcase (subseq s 0 i)))

(subseq s (+ i 2)))))))(when h

(cons h (get-header stream)))))

T his func tion reads in a l ine from the stream , converts i t to a key/va lue pa ir based on the loca tion of a colon , and

then recurses to convert addit iona l l ines in the header . If i t encounte rs a l ine tha t doesn’t conform to a header l ine , i t m eanswe’ve reached the blank l ine a t the end of the header and a re finished. In this case , both i and h wil l be nil, and the func tionte rm ina tes.

T he intern com m and used when genera t ing the key above is a sim ple func tion tha t converts a string into a sym bol. W e could,instead, have used the read com m and for this purpose , as we have previously in this book. But rem em ber tha t the flexibil i ty of theread com m and a lso m akes i t a grea t ta rge t for hackers, who m ight try c rea ting m alform ed headers to c rack your web se rver. T ha t’swhy i t’s wise to use the m ore l im ited, spec ific intern func tion to process this da ta sent over the Inte rne t to our web se rver.

Page 291: Land of Lisp - Barski M.D., Conrad

Testing get-header with a Str ing Stream

Since the get-header func tion pulls i ts da ta direc t ly from a socke t stream , you m ight think we can’t te st i t direc t ly through theRE PL . However, a s you saw in the previous chapte r, the re a re ac tua lly severa l diffe rent types of resources besides socke ts tha t canbe accessed through the stream inte rface in Com m on L isp. Because of the com m on inte rface am ong stream s, we can test our get-header func tion by passing i t a string stream instead of a socke t stream :

> (get-header (make-string-input-stream "foo: 1bar: abc, 123

"))((FOO . "1") (BAR . "abc, 123"))

Using the make-string-input-stream func tion, we can c rea te an input stream from a l i te ra l string. In this exam ple , we’retaking a string defining two keys (foo and bar) and ending i t with an em pty l ine , just l ike a typica l HT T P header. Note tha t we

have a single l i te ra l string from to . Such strings a re perm itted in Com m on L isp. As you can see , the get-headerfunc tion appropria te ly pulled the two keys and the ir va lues out of this stream , in the sam e way i t would pull these va lues out of asocke t stream .

Using this t rick, you can test func tions tha t m anipula te stream s direc t ly from the RE PL . T o do this, sim ply substi tute stringstream s for other, m ore com plica ted stream types.

Page 292: Land of Lisp - Barski M.D., Conrad

P arsing the Request Body

In a POST request , the re wil l usua lly be param ete rs stored benea th the header, in an a rea known as the request body or requestcontent . T he fol lowing get-content-params func tion extrac ts these param ete rs:(defun get-content-params (stream header)

(let ((length (cdr (assoc 'content-length header))))

(when length

(let ((content (make-string (parse-integer length))))

(read-sequence content stream)

(parse-params content)))))

First , this func tion sea rches the header for a va lue ca l led content-length , which te l ls us the length of the string tha t

conta ins these content pa ram ete rs. If content-length exists, then we know there a re param ete rs to parse . T he func tion wil l

then c rea te a string with the given length using make-string , and use read-sequence to fi l l tha t string with charac te rs from

the stream . It then runs the result through our parse-params func tion to transla te the param ete rs into our c leaned-up a l ist

form at .

Page 293: Land of Lisp - Barski M.D., Conrad

O ur G rand F inale : The serve F unction!

Now a ll the pieces a re in place to write the heart of our web se rver: the serve func tion. Here i t is in a l l i ts glory:

(defun serve (request-handler)

(let ((socket (socket-server 8080)))

(unwind-protect

(loop (with-open-stream (stream (socket-accept socket))

(let* ((url (parse-url (read-line stream)))(path (car url))(header (get-header stream))(params (append (cdr url)(get-content-params stream header)))(*standard-output* stream))

(funcall request-handler path header params))))(socket-server-close socket))))

T he serve func tion takes a single param ete r: request-handler , which is supplied by the c rea tor of a website tha t wants touse this web se rver. W hen the se rver rece ives a request over the ne twork, i t pa rses the request into c lean L isp da ta struc tures (usingthe func tions we’ve discussed throughout this chapte r), and then passes this request inform ation to request-handler. T he request-handler then displays the correc t HT ML .

L et’s look a t our serve func tion in de ta i l to see how i t accom plishes this.

First , serve c rea tes a socke t bound to port 8080 . T his is one of severa l ports tha t is com m only used for se rving web pages,espec ia l ly when a si te is st i l l under deve lopm ent. (Port 80 is usua lly used for a produc tion website /web se rver. ) W e then ca ll

unwind-protect , which ensures tha t no m atte r wha t happens as the se rver runs, socket-server-close wil l be ca l led a t som epoint to free the socke t .

Next, we sta rt the m ain web-se rving loop. W ithin this loop, we open a stream for any c l ient tha t accesses our se rver . W ethen use the with-open-stream m acro to guarantee tha t , no m atte r wha t , tha t stream will be properly c losed. Now we’re ready toread and parse the website request tha t the c l ient has m ade to our se rver, using a l l of the reading and parsing func tions we c rea ted

Page 294: Land of Lisp - Barski M.D., Conrad

.

Fina lly, we ca ll the request-handler func tion, passing in the request de ta i ls . Note how we redefine the *standard-output* dynam ic variable beforehand. T his m eans tha t the request handle r can just wri te to standard output , and a l l the printedda ta wil l be redirec ted to the c l ient stream autom atica l ly. As you lea rned in Chapte r 12, capturing da ta from standard outputa l lows us to m inim ize string conca tena tion. Also, i t wil l m ake our request-handler func tion easie r to debug, as you’l l see short ly.

Note

One thing we did not do with our web se rver is prevent the web se rver from crashing if the request-handler t riggers anexception. Instead, we sim ply guarantee tha t no resources a re m angled in the case of an exception. W e could easi ly add extraexception handling to keep the se rver t icking even if horrible exceptions occur. However, since our goa l is to lea rn L isp anddeve lop gam es in a browser, i t ’s be tte r for us to know right away about any exceptions, even if tha t brings down our se rver.

Page 295: Land of Lisp - Barski M.D., Conrad

Building a Dynamic Website

T o try out our shiny new web se rver, le t’s build a sim ple si te tha t gree ts a visi tor, using the dirt-sim ple func tion hello-request-handler:(defun hello-request-handler (path header params)

(if (equal path "greeting")

(let ((name (assoc 'name params)))(if (not name)(princ "<html><form>What is your name?<input name='name' />

</form></html>")

(format t "<html>Nice to meet you, ˜a!</html>" (cdr name))))

(princ "Sorry... I don't know that page.")))T hi s hello-request-handler func tion supports only a single web page , ca l led greeting. T he first step in se rving up this

greeting page is to see if this page is indeed what the c l ient requested . If not , we print an apology to the use r for not

finding the spec ified page . Otherwise , we check the request pa ram ete rs to see if we know the use r’s nam e . If not , we ask

the use r to ente r a use rnam e using a web form . If we do know the use r’s nam e, we gree t the visi tor enthusiast ica l ly .

Note

W e’re taking a ton of shortcuts with our web se rver and this prim it ive website . For instance , any HT ML sent to a c l ient should bewrapped in a proper HT ML ske le ton, such as <html><body>...</body></html>. However, even then our page wouldn’t be ful lycom pliant with m odern HT ML standards. In addit ion, when a c l ient requests a nonexistent page , the appropria te response is todisplay a 404 e rror page , not just print a poli te apology. L uckily, web browsers a re very forgiving about such shortcuts, and theywill display our sim plified responses anyway.

Page 296: Land of Lisp - Barski M.D., Conrad

Testing the Request H andler

Before we launch our new website , le t’s te st our hello-request-handler in the RE PL by first viewing a page about lolca ts:> (hello-request-handler "lolcats" '() '())

Sorry... I don't know that page.Perfec t . As you can see , when we ask our request handle r for a page other than the greeting page , i t just prints an apology. Now

le t’s t ry viewing the correc t greeting page :> (hello-request-handler "greeting" '() '())

<html><form>What is your name?<input name='name' /></form></html>E xce llent! Our request handle r has genera ted an HT ML form asking the use r for a use rnam e. Now le t’s pass in a param ete r for

the use r’s nam e, as if the form had been processed and sent to the se rver:> (hello-request-handler "greeting" '() '((name . "Bob")))

<html>Nice to meet you, Bob!</html>Because of the way we designed our web se rver, i t ’s ve ry sim ple to debug a request handle r independently in the RE PL . W e were

able to see tha t hello-request-handler genera tes the correc t responses without ac tua lly firing up a web browser.

Page 297: Land of Lisp - Barski M.D., Conrad

Launching the Website

Now tha t we know tha t our new website is func tioning, le t’s launch i t! But first , we need to m ake sure tha t a l l of the func tionsdiscussed in this chapte r have been defined in an instance of CL ISP. If you haven’t been ente ring these func tions into the RE PL asyou’ve been reading, you can just save them a ll into a fi le ca l led webserver. l isp, and then load them with (load "webserve'").

Once you’ve defined your func tions in the CL ISP, sta rt the se rver by ente ring the fol lowing into the RE PL :> (serve #'hello-request-handler)T hat’s i t ! Now you should be able to visi t the si te in a web browser:

As you can see , when you visi t our greeting page from a browser (using 127.0.0.1:8080, which wil l point to port 8080 on thesam e m achine the web browser is running on), you a re asked for your nam e. T he se rver then shows a fol low-up page , which gree tsyou by nam e. T his shows tha t our web se rver was able to parse out the nam e from the request pa ram ete rs, and was able to pass thenam e to our hello-request-handler func tion.

W e now have a ful ly func tioning web se rver and request handling infrastruc ture . In future chapte rs, we’l l use these tools to c rea tean awesom e, graphica l , web-based gam e.

Page 298: Land of Lisp - Barski M.D., Conrad

What You've Learned

In this chapte r, you c rea ted a web se rver using Com m on L isp, and lea rned the fol lowing a long the way:You can signa l condit ions in Com m on L isp with the error func tion. You can ca tch such e rrors with the handle-case

com m and. If som e code absolute ly, posi t ive ly needs to be ca l led no m atte r wha t e rrors occur, you can place this code inside theunwind-protect com m and.

A web se rver processes HT T P requests. T he m ost com m on type of request is the GET request , used for viewing inform ation.Another com m on type is a POST request , which is used when subm itt ing web form s, for instance . You can te l l the type of request ,which page was requested, as well a s other inform ation, by looking a t the request header. Both GET and POST requests m ay haverequest pa ram ete rs, which can appear e i the r a t the end of the requested URL or a t the bottom of the request in the request body .

Page 299: Land of Lisp - Barski M.D., Conrad

Chapter 13.5. F unctional P rogramming Is Beautiful

Page 300: Land of Lisp - Barski M.D., Conrad
Page 301: Land of Lisp - Barski M.D., Conrad
Page 302: Land of Lisp - Barski M.D., Conrad
Page 303: Land of Lisp - Barski M.D., Conrad
Page 304: Land of Lisp - Barski M.D., Conrad
Page 305: Land of Lisp - Barski M.D., Conrad
Page 306: Land of Lisp - Barski M.D., Conrad
Page 307: Land of Lisp - Barski M.D., Conrad
Page 308: Land of Lisp - Barski M.D., Conrad
Page 309: Land of Lisp - Barski M.D., Conrad
Page 310: Land of Lisp - Barski M.D., Conrad
Page 311: Land of Lisp - Barski M.D., Conrad
Page 312: Land of Lisp - Barski M.D., Conrad
Page 313: Land of Lisp - Barski M.D., Conrad
Page 314: Land of Lisp - Barski M.D., Conrad
Page 315: Land of Lisp - Barski M.D., Conrad
Page 316: Land of Lisp - Barski M.D., Conrad
Page 317: Land of Lisp - Barski M.D., Conrad
Page 318: Land of Lisp - Barski M.D., Conrad
Page 319: Land of Lisp - Barski M.D., Conrad

P art IV. Lisp is Sc ience

Page 320: Land of Lisp - Barski M.D., Conrad

Chapter 14. Ramping Lisp Up a Notch with F unctional P rogramming

As you’ve seen in the preceding chapte rs, L isp m akes i t pre t ty easy to throw toge ther som e quick code and build som e sim plegam es in no t im e . However, L isp’s m ain c la im to fam e is as an academ ic tool , appropria te for tackling the m ost com plica tedsc ientific problem s. T he fac t tha t i t ’s a lso grea t for hacking is a rguably just a side benefi t .

In the rest of this book, we’re going to focus on the sc ientific side of the language , exploring som e advanced techniques to builda m ore sophist ica ted gam e tha t I hope wil l rea l ly blow your m ind. It wil l do things you m ay never have thought would be possiblein a com pute r program .

In this chapte r, you’re going to lea rn about the first advanced L isp concept , ca l led the func tional programming technique . In thenext chapte r, we’l l use this technique to build a sim ple dice wars gam e, as well a s a c rude a rt ific ia l ly inte l l igent opponent to playaga inst!

Page 321: Land of Lisp - Barski M.D., Conrad

What Is F unctional P rogramming?

W e’ve a lready discussed the concept of func tiona l program m ing a bi t in ea rl ie r chapte rs. T he glib answer is tha t func tiona lprogram m ing is “a style of program m ing where we write a l l of our code using func tions. ”

However, we m ean som ething very spec ific when using the te rm func tion in this context—exac tly the sam e thing tha tm athem atic ians m ean when they use the word func tion. So, wha t do m athem atic ians m ean when they use this word?

You probably a lready know the answer. T ry to rem em ber way, way back when you took pre -a lgebra . If you didn’t fa l l a sleepduring tha t pa rt icula r le sson, you m ight rem em ber your teacher drawing som ething l ike this on the cha lkboard:

T his pic ture shows tha t a func tion has a rgum ents tha t m ay go into i t , ca l led the domain of the func tion. T he func tion then takesthese a rgum ents and re turns a va lue . T his va lue is sa id to fa l l within the range of the func tion.

Note

Som e advanced L ispers wil l c ringe when som eone says tha t a func tion “ re turns a va lue . ” T his is because L isp derives from asom ething ca lled the lambda calculus, which is a fundam enta l program m ing-l ike a lgebra deve loped back in the 1930s by AlonzoChurch. In the lam bda ca lculus, you “ run” a program by perform ing substi tut ion rules on the sta rt ing program to de te rm ine theresult of a func tion. Hence , the result of a se t of func tions just sort of m agica lly appears by perform ing substi tut ions; never does afunc tion consc iously “dec ide” to re turn a va lue .

Because of this, L isp purists pre fe r to say tha t a func tion “eva lua tes to a result . ” However, a lm ost everyone e lse in theprogram m ing world l ikes to say tha t func tions re turn a va lue . It’s up to you to dec ide which way of thinking about func tions fee lsthe m ost na tura l .

Here a re som e im portant propert ies of m athem atica l func tions tha t we’l l want our L isp func tions to obey as well :

T he func tion a lways re turns the sam e result , a s long as the sam e a rgum ents a re passed into i t . (T his is oftenre fe rred to as re ferential transparency . )

T he func tion never re fe rences variables tha t a re defined outside the func tion, unless we a re ce rta in tha t thesevariables wil l rem ain constant .

No variables a re m odified (or mutated, a s func tiona l program m ers l ike to say) by the func tion.T he purpose of the func tion is to do nothing other than to re turn a result .T he func tion doesn’t do anything tha t is visible to the outside world, such as pop up a dia log box on the sc reen or

m ake your com pute r go “Bing!”T he func tion doesn’t take inform ation from an outside source , such as the keyboard or the hard drive .

If we obey these rules whenever possible , we can say tha t our code is writ ten in the func tional sty le .A grea t exam ple of a true m athem atica l func tion is the sine func tion. Sim ila rly, the sin func tion in L isp (which ca lcula tes the

m athem atica l sine ) is a grea t exam ple of a L isp func tion tha t obeys the rules of the func tiona l style :> (sin 0.5)

0.47942555

Page 322: Land of Lisp - Barski M.D., Conrad

T he sin func tion a lways re turns the sam e result , a s long as you a lways pass the sam e a rgum ent (in this case , 0.5) into i t . Itdoesn’t do anything to inte rac t with the outside world. Its entire purpose in l i fe is to re turn the sine as a va lue . It obeys a l l the rulesin the preceding l ist .

Clearly, i t would be im possible to write all the code in a com pute r program in the func tiona l style . For instance , one of the rulesst ipula tes tha t the com pute r isn’t a l lowed to go “Bing!”—who would want to use a com pute r if i t didn’t go “Bing!” once in awhile?

W henever a piece of code does som ething tha t is visible to the outside world, such as go “Bing!” or display a dia log box on thesc reen, we say tha t the code causes a side e f fec t . Func tiona l program m ers think of such side e ffec ts as m aking your code “dirty. ”

T he technica l te rm for such dirty code tha t conta ins side e ffec ts is imperative code . T he te rm imperative im plies tha t the code iswrit ten in a “cookbook” style , where you basica l ly say things l ike “ first do this, and then do tha t . ” L ike a cookbook, m ost l ines inim pera tive code perform side e ffec ts, such as writ ing to the sc reen or m odifying a globa l va riable . Im pera tive code is the oppositeof func tiona l code .

T his leads us to the centra l philosophy of func tiona l program m ing. It sta tes tha t you should break your program into two parts:

T he first , and biggest pa rt , should be com ple te ly func tiona l and free of side e ffec ts. T his is the c lean part of yourprogram .

T he second, sm alle r pa rt of your program is the part tha t has a l l the side e ffec ts, inte rac ting with the use r and therest of the outside world. T his code is dirty and should be kept as sm all a s possible .

If a piece of code pops up a dia log box, for exam ple , we deem it dirty and banish i t to the im pera tive sec tion of our code .T hings l ike dia log boxes a re not rea l ly m ath, and we shouldn’t le t them play with our m ath func tions and other c lean, func tiona lcode .

Page 323: Land of Lisp - Barski M.D., Conrad
Page 324: Land of Lisp - Barski M.D., Conrad

Anatomy of a P rogram Written in the F unctional Style

Now tha t we’ve discussed how func tiona l program m ing is done , le t’s write a sim ple program tha t fol lows this style . Since wewant this program to be a typica l exam ple of m ost software , we should figure out wha t m ost software in the world ac tua lly does. Sowhat do m ost program s in the world ac tua lly do? T hey keep track of widge ts!

Here’s our entire exam ple program , writ ten in the func tiona l style :;the clean, functional part

(defun add-widget (database widget)

(cons widget database))

;the dirty, nonfunctional part

(defparameter *database* nil)

(defun main-loop ()

(loop (princ "Please enter the name of a new widget:")

(setf *database* (add-widget *database* (read)))(format t "The database contains the following: ˜a˜%" *database*)))

As prom ised, i t is spl i t into two parts: the c lean part and the dirty part . I did say tha t the c lean part of the program should bem uch bigger than the dirty part . However, since this exam ple is so short , the dirty part ended up a bi t bigger. Usua lly, you canexpec t the c lean part to be a round 80 percent of the ac tua l code .

Note

Som e program m ing languages a re even m ore focused on func tiona l program m ing than L isp is. Haske ll , for instance , has powerfulfea tures tha t le t you write 99.9 percent of your code in a func tiona l style . In the end, however, your program will st i l l need to havesom e kind of side e ffec t ; otherwise , your code couldn’t accom plish anything useful .

So what does our exam ple program do? W ell , i t basica l ly does wha t m ost com pute r program s in the world a re designed to do: Itkeeps track of widge ts in a da tabase!

T he da tabase in this exam ple is ve ry prim it ive . It’s just a L isp l ist , stored in the globa l va riable *database*. Since the da tabase

is going to sta rt off em pty, we ini t ia l ize this va riable and se t i t to be em pty .

W e can ca ll the func tion main-loop to sta rt t racking som e widge ts . T his func tion just sta rts an infini te loop, asking the

user for a widge t nam e . T hen, a fte r i t reads in the widge t , i t ca l ls the add-widget func tion to add the new widge t to the

da tabase .

However, the add-widget func tion is in the c lean part of the code . T ha t m eans i t ’s func tiona l and isn’t a l lowed to m odifythe *database* va riable direc t ly. L ike a l l func tiona l code , the add-widget func tion is a l lowed to do nothing m ore than re turn anew va lue . T his m eans tha t the only way i t can “add” a widge t to a da tabase is to re turn a brand-new da tabase! It does this by

Page 325: Land of Lisp - Barski M.D., Conrad

sim ply taking the da tabase passed to i t and then consing the widge t to the da tabase to c rea te a new da tabase . T he newdatabase is identica l to the previous one , except tha t i t now conta ins a new widge t a t the front of the l ist .

T hink of how crazy this sounds on the face of i t . Im agine tha t we’re running an Orac le da tabase se rver, conta ining m ill ions ofwidge ts:

T hen, when we add a new widge t , the da tabase se rver accom plishes this by c rea ting a brand-new replica of the previousda tabase , which diffe rs only in tha t a single new i tem has been added:

T his would be horribly ine ffic ient . However, in our widge ts exam ple , things a re not as bad as they m ay first appear. It is t ruetha t the add-widgets func tion c rea tes a new l ist of widge ts every t im e i t is ca l led, and tha t repea ted ca l ls to this func tion wouldm ake the l ist longer and longer. However, since every new widge t is sim ply added to the front of the l ist , i t turns out tha t the ta i lend of the widge t l ist is identica l to the previous version of the l ist . Hence , the add-widget func tion can “chea t” whenever i tc rea tes a new l ist , by sim ply consing a single new widge t to the front of the l ist , and then repurposing the old l ist a s a ta i l to hold

the rest of the i tem s . T his a l lows the new l ist to be c rea ted in a way tha t is fast and a lso requires very l i t t le new m em ory tobe a l loca ted. In fac t , the only new m em ory a l loca ted by add-widget is a single new cons ce l l to l ink the new widge t to theprevious l ist .

T his type of chea ting when c rea ting new da ta struc tures is a key technique tha t m akes e ffic ient func tiona l program m ing possible .Furtherm ore , sharing of struc tures can be done sa fe ly, since one of the tene ts of func tiona l program m ing is to never m odify oldpieces of da ta .

So our add-widget func tion c rea tes a new da tabase for us with the addit iona l i tem added to i t . T he main-loop func tion, in thedirty part of the code , se ts the globa l *database* va riable equa l to this new da tabase . In this way, we have indirec t ly m odified theda tabase in two steps:

1. T he add-widget func tion, which is basica l ly the bra ins of this program , genera ted an upda ted da tabase for us.2. T he main-loop func tion, which was in charge of the dirty work, m odified the globa l *database* va riable to

Page 326: Land of Lisp - Barski M.D., Conrad

com ple te the opera t ion.

T his exam ple program il lustra tes the basic layout of a L isp program writ ten in the func tiona l style . L e t’s t ry out our newprogram to see i t in ac t ion:> (main-loop)

Please enter the name of a new widget: FrombulatorThe database contains the following: (FROMBULATOR)Please enter the name of a new widget: Double-ZingomatThe database contains the following: (DOUBLE-ZINGOMAT FROMBULATOR)...

Rem em ber tha t you can hit c trl-C to exit the infini te loop in this exam ple .

Page 327: Land of Lisp - Barski M.D., Conrad

H igher-O rder P rogramming

One com m on stum bling block for program m ers lea rning to write program s in the func tiona l style is tha t they find i t ha rd tocom bine diffe rent chunks of code to perform a single ac t ion. T his is ca l led code composit ion. A program m ing language shouldm ake code com posit ion easy. In other words, i t should m ake i t easy for you to take diffe rent pieces of code and use them toge therto solve a task. T he m ost powerful tool for code com posit ion when writ ing func tiona l code is higher-order programming, which le tsyou use func tions tha t accept other func tions as param ete rs.

L e t’s look a t an exam ple to understand why code com posit ion can be a cha llenge to a beginning func tiona l program m er. Supposewe want to add two to every num ber in the fol lowing l ist :> (defparameter *my-list* '(4 7 2 3))

*MY-LIST*T o do this, we wil l need to write code to traverse the l ist , a s we ll a s write code to add two to a num ber. T hese a re the two tasks

we need to com pose .

Page 328: Land of Lisp - Barski M.D., Conrad

Code Composition with Imperative Code

One possible na ïve (and im pera tive ) way to perform this ta sk is to use a loop:;For demonstration purposes only. A Lisper would not write code like this.

> (loop for n below (length *my-list*)

do (setf (nth n *my-list*) (+ (nth n *my-list*) 2)))NIL> *my-list*(6 9 4 5)

Here , we’re c rea ting a variable n tha t counts through a l l the i tem s in the l ist in a loop . W e then use setf to add two to

the num ber a t the loca tion n in the l ist . T his is sim ila r to the sort of code you m ight write if you were a C program m er.Although i t’s pre t ty ugly, the re a re posit ive things tha t can be sa id about i t :

Code struc tured l ike this is potentia l ly very e ffic ient . It ’s space-e ffic ient , since we don’t need to a l loca te anym em ory for storing a new l ist (we’re just m unging the old l ist to inc rease a l l the num bers in i t by two). And i t coulda lso be very t im e-effic ient , i f we rewrote this loop to work on an a rray instead of a l ist . (Rem em ber tha t finding thenth i tem in a l ist is slow. )

Code writ ten l ike this c lea rly com poses the task of looping and the task of adding two to a num ber .By putt ing our code for the addit ion inside the loop, we a re com posing these two ac tivi t ie s to com ple te a m orecom plica ted goa l: adding two to an entire l ist of num bers.

However, the re a re obvious downsides to the im pera tive approach:It destroys the origina l l ist . T his is a problem if we use the *my-list* va riable la te r, and m iss the fac t tha t this code has

m essed up the origina l va lues in this l ist . A L isper would say tha t a l lowing the *my-list* va riable to be m odified wil ly-nil lym akes this va riable a piece of hidden state in the program . Bugs re la ted to hidden sta te a re com m on in program m ing languagestha t encourage im pera tive -style program m ing.

W e needed to c rea te a variable n to keep track of our posit ion in the l ist . T his m akes the code m ore bulky and a lso addsm ore places where bugs could lurk. T here ’s a lways a risk tha t we give n a wrong va lue or use i t incorrec tly to access i tem s fromthe l ist .

Page 329: Land of Lisp - Barski M.D., Conrad

Using the F unctional Style

Now le t’s see wha t happens if we rewrite this code in a func tiona l style . L e t’s first wri te i t a s a beginning func tiona l program m erm ight, without using higher-order program m ing:

> (defun add-two (list)(when list(cons (+ 2 (car list)) (add-two (cdr list)))))ADD-TWO> (add-two '(4 7 2 3))(6 9 4 5)

Here , we’re c rea ting a func tion add-two , which adds two to the num ber a t the front of the l ist and then ca lls i tse lfrecursive ly to build the ta i l of the l ist .

T his code avoids m any of the downsides from the im pera tive solution. It does not destroy the origina l l ist , and i t does not requireus to use a num eric index. Unfortuna te ly, i t a lso lacks one of the c ri t ica l benefi ts of the im pera tive version: T here is no longer ac lea r de linea tion be tween the code tha t adds two to i tem s in the l ist and the code tha t t raverses the l ist . T hese two ac tivi t ie s a renow deeply inte rtwined, which is the reason we needed to c rea te a spec ia l func tion, add-two, to m ake this solution work. W e havelost our abil i ty to com pose these two tasks in a c lean way.

Page 330: Land of Lisp - Barski M.D., Conrad

H igher-O rder P rogramming to the Rescue

If we want to write code for this ta sk in a func tiona l style , but st i l l a l low our code to be com posable , we’l l need to m ake use ofhigher-order func tions. Here is how an experienced L isper would add two to every num ber in a l ist :> (mapcar (lambda (x)

(+ x 2))'(4 7 2 3))(6 9 4 5)

Now we fina lly have a version of the code tha t is func tiona l and a l lows us to com pose the traversa l code and the addit ion code .Here , the traversa l is pe rform ed by the mapcar func tion, which is a higher-order func tion since i t applies a supplied func tion toevery m em ber in a l ist . T he addit ion is pe rform ed by a lam bda func tion, which is responsible only for adding two to a num ber, andis oblivious to the fac t tha t the num bers a re in a l ist . T his exam ple shows tha t higher-order program m ing can le t us write c lea rlyde linea ted chunks of code and then com pose them , without needing to break from the func tiona l style .

Page 331: Land of Lisp - Barski M.D., Conrad

Why F unctional P rogramming Is Crazy

W e a lready know one reason why func tiona l program m ing is c razy: Func tiona l program s can’t rea l ly do anything, since theycan’t have side e f fec ts. As Sim on Peyton Jones, a we ll-known func tiona l program m er, l ikes to say, “All you can do without sideeffec ts is push a button and watch the box ge t hot for a while . ” (W hich isn’t technica lly true , since even the box ge tt ing hot is aside e ffec t . )

W e’ve seen tha t we can work a round this l im ita t ion of func tiona l program m ing by adding a dirty sec tion to our program s, whichis kept separa te from the rest of the code and conta ins a l l our code tha t is im pera tive and not in the func tiona l style . However,reca ll the problem with the func tiona l style : It can cause code to be extrem ely ine ffic ient .

Perform ance has a lways been a huge concern with func tiona l program s. Having to write code tha t isn’t a l lowed to m uta te theva lue of exist ing variables, but only c rea te new variables, can lead to a huge am ount of m em ory copying and m em ory a l loca tion,which can slow program s down to a c rawl. One way to m it iga te this copying and a l loca tion is by using shared struc tures be tweendiffe rent pieces of da ta in our program s.

None the less, code writ ten in the func tiona l style has other propert ies tha t a ffec t pe rform ance . For instance , func tiona l code usesa lot of recursion, instead of looping. Using recursion causes the L isp com pile r/ inte rpre te r to put a lot of i tem s on the programstack, which can be very slow.

Fortuna te ly, func tiona l program m ers have deve loped optim iza tion techniques tha t can solve the vast m ajori ty of pe rform anceproblem s. T hese inc lude m em oiza tion, ta i l ca l l optim iza tion, lazy eva lua tion, and higher-order program m ing, which we’l l coverin the next few chapte rs. Using these techniques and others, an experienced func tiona l program m er can write code tha t is usua llycom parable in perform ance to code writ ten in any other style .

However, som e types of program s just can’t be writ ten in a pure ly func tiona l way. For instance , you probably wouldn’t wri tesom ething l ike a ful l-on Orac le -style re la t iona l da tabase system in a func tiona l style . Ye t , sm alle r, m em ory-resident da tabasesystem s m ay be able to use pure ly func tiona l techniques (an exam ple is the HAppS-IxSe t ava ilable to Haske ll program m ers a tht tp: / /happs.org/). So the re is rea l ly no hard l im it as to when func tiona l program m ing can be used.

Page 332: Land of Lisp - Barski M.D., Conrad

Why F unctional P rogramming Is F antastic

Now tha t I’ve told you about a l l the headaches a func tiona l program m er m ust endure , you m ay be wondering, “W hy wouldanyone bother to program this way?” T he answer is tha t func tiona l program m ing has m any entic ing benefi ts tha t m ake up for theseheadaches.

Page 333: Land of Lisp - Barski M.D., Conrad

F unctional P rogramming Reduces Bugs

Bugs in com pute r program s usua lly happen because , under ce rta in c ircum stances, the code behaves in ways the program m erdidn’t expec t when the code was writ ten. In func tiona l program m ing, the behavior of your func tions depends on one and only onething: the a rgum ents explic i t ly passed into the func tion. T his m akes i t m uch easie r for a program m er to apprec ia te a l l thec ircum stances a program could possibly encounte r, inc luding c ircum stances tha t could lead to e rrors.

W rit ing func tions tha t depend on only the ir a rgum ents for the ir behavior a lso m akes bugs easy to duplica te . If you ca ll a func tionwith the sam e da ta passed in through i ts a rgum ents, i t should do the sam e exac t thing every t im e . T his is the property we ca lledre ferential transparency .

Page 334: Land of Lisp - Barski M.D., Conrad

F unctional P rograms Are M ore Compact

It turns out a lot of the work in run-of-the-m ill com pute r program s involves c rea ting, ini t ia l iz ing, and upda ting variables.Func tiona l program s don’t do any of this. As we discussed ea rl ie r, func tiona l program s m ake use of higher-order func tions, whichdon’t require us to c rea te tons of tem porary variables in our code , and tha t m akes our code m ore com pac t .

Page 335: Land of Lisp - Barski M.D., Conrad

F unctional Code Is M ore Elegant

T he biggest advantage of func tiona l program m ing is tha t i t brings a l l of com pute r program m ing back to the dom ain ofm athem atics. It wouldn’t m ake sense for a m ath equa tion to pop up a dia log box or write to the hard drive . It can be a rgued tha t i fwe ge t our com pute r code back to this sam e leve l of puri ty, i t wil l be fa r m ore e legant . Addit iona lly, i f our code is c lose r to theworld of m athem atics, we m ay be able to use tools in m athem atics to write be tte r com pute r code .

In fac t , a lot of research continues to be done in using m athem atica l proofs to check for the correc tness of func tiona l com pute rprogram s. Although this research st i l l isn’t to the point where a prac tica l program m er would use such techniques, they m ay be m orecom m on in the future . And, a lm ost ce rta inly, a func tiona l program m ing style wil l be essentia l in m aking correc tness proofs on yourcode possible .

Page 336: Land of Lisp - Barski M.D., Conrad

What You've Learned

In this chapte r, we discussed func tiona l program m ing. Along the way, you lea rned the fol lowing:Program s writ ten in the func tional sty le a lways give the sam e result when they a re given the sam e va lues in the ir a rgum ents.Func tiona l program s do not conta in side e f fec ts. T he ir whole purpose in l i fe is to just ca lcula te a va lue to re turn.Program s tha t a re not func tiona l usua lly read l ike a cookbook, with sta tem ents l ike , “First do this, and then do tha t . ” T his style

of program m ing is ca l led imperative programming.A good stra tegy for writ ing L isp program s is to break them into a c lean, func tiona l pa rt and a dirty, im pera tive part .Func tiona l program s can be writ ten quickly, a re m ore com pac t , and tend to have fewer bugs, pa rt icula rly in the hands of an

experienced func tiona l program m er.

Page 337: Land of Lisp - Barski M.D., Conrad

Chapter 15. Dice of Doom, a G ame Written in the F unctional Style

Now we’re fina lly ready to c rea te a m ore sophist ica ted (and fun) com pute r program in the func tiona l style . As we expand thisprogram throughout the rest of this book, you’l l lea rn about techniques for writ ing e legant func tiona l code , while a t the sam e t im em ainta ining strong perform ance in your program s.

Page 338: Land of Lisp - Barski M.D., Conrad

The Rules of Dice of Doom

Dice of Doom is a gam e in the sam e fam ily as Risk, Dice W ars (ht tp: / /www.gam edesign. jp/flash/dice /dice .htm l), and KDice(http: / /kdice . com /). In the beginning, we’re going to keep the rules of Dice of Doom m ind-num bingly sim ple . In la te r chapte rs,we’l l expand the rules, unti l eventua lly we’l l have a gam e very sim ila r to Dice W ars.

Here a re the sim plified rules we’l l sta rt with:

T wo players (nam ed A and B) occupy spaces on a hexagona l grid. E ach hexagon in the grid wil l have som e six-sided dice on i t , owned by the occupant.

During a turn, a player can perform any num ber of m oves, but m ust pe rform a t least one m ove . If the playercannot m ove , the gam e ends.

A m ove consists of a t tacking a ne ighboring hexagon owned by the opponent. T he player m ust have m ore dice inher hexagon than the ne ighboring hexagon in order to a t tack. For now, a l l a t tacks wil l autom atica l ly lead to a win.In future variants, we’l l ac tua lly rol l the dice for a ba tt le . But for now, the player with m ore dice just winsautom atica l ly.

Afte r winning a ba tt le , the losing player’s dice a re rem oved from the board, and a l l but one of the winningplayer’s dice a re m oved onto the newly won hexagon.

Afte r a player is finished m aking her m oves, re inforcem ents a re added to tha t player’s dice a rm ies.Re inforcem ents to the player’s occupied hexagons a re added one die a t a t im e , sta rt ing from the upper-le ft corner,m oving ac ross and down. T he m axim um num ber of dice added as re inforcem ents is one less than the player tookfrom the opponent in her com ple ted turn.

W hen a player can no longer take her turn, the gam e has ended. T he player who occupies the m ost hexagons a tthis point is the winner. (A t ie is a lso possible . )

Page 339: Land of Lisp - Barski M.D., Conrad

A Sample G ame of Dice of Doom

Since our im plem enta t ion of Dice of Doom will inc lude an AI player, we’re going to sta rt with an extrem ely hum ble size for ourgam e board. As you probably know, AI code can be very com puta tiona lly intensive . In our ea rly, ve ry na ive version of this gam e,any board la rger than a 2-by-2 grid of hexagons would bring CL ISP to i ts knees!

Here is a com ple te gam e, played on a puny 2-by-2 board:

At the beginning of the gam e, player A (indica ted with black hexagons) possesses the top two hexagons, with three dice on each.Player B occupies the bottom row (indica ted by the white hexagons), with three dice and one die , respec tive ly. Player A a t tacksthe lone die with one of his pi les. Afte r the a t tack, one of player A’s dice rem ains behind, while the others m ove to the conqueredspot. T hen player A passes the turn.

Player B now a ttacks player A’s two dice with a pi le of three . Player B then passes. At this point , player B rece ives a singlere inforcem ent die on her le ft hexagon. T his is because she kil led two of player A’s dice . T he re inforcem ents, a s pe r the rules,consist of the num ber of dice ki l led, m inus one .

Page 340: Land of Lisp - Barski M.D., Conrad

Player A now a ttacks with three of his dice and passes. Also, he ge ts a re inforcem ent die .

Player B now has only one lega l m ove , a t tacking two aga inst one .

Player A now has the upper hand, ki l l ing a l l of player B’s rem aining dice . As you can see , player A is pe rm itted to performm ultiple a t tacks on his turn before passing. T he gam e has ended with player A as the winner.

Page 341: Land of Lisp - Barski M.D., Conrad

Implementing Dice of Doom, Version 1

L et’s sta rt coding this gam e in L isp. As we discussed in the previous chapte r, this gam e wil l conta in both c lean, func tiona l codeand dirty, im pera tive code . You’l l be able to te l l in which ca tegory a block of code fi ts by the “c lean/func tiona l” or“dirty/ im pera tive” icon next to i t .

Page 342: Land of Lisp - Barski M.D., Conrad

Defining Some G lobal Var iables

First , we’l l c rea te som e globa l va riables tha t de fine the basic param ete rs for our gam e:

(defparameter *num-players* 2)

(defparameter *max-dice* 3)

(defparameter *board-size* 2)

(defparameter *board-hexnum* (* *board-size* *board-size*))

W e’re sta t ing tha t the re wil l be two players , tha t the m axim um num ber of dice on a square is three , and tha t the

board wil l be 2-by-2 . In la te r ve rsions of Dice of Doom , we’l l inc rease a l l of these param ete rs, to a l low for a m orecha llenging gam e.

Since i t’s use ful to know the tota l num ber or hexagons the re a re a t the current board size , we a lso define *board-hexnum* .Note tha t even though the grid is m ade of hexagons, i t is st i l l basica l ly a square grid, since the num ber of hexagons just equa ls thesquare of the side of the grid.

Note

In this chapte r, every code sam ple has an assoc ia ted icon to indica te whe ther i t is m ade of dirty, im pera tive or c lean, func tiona lcode . By the end of this chapte r, you should be able to easi ly te l l the diffe rence and have som e apprec ia t ion for the benefi ts ofeach style .

Page 343: Land of Lisp - Barski M.D., Conrad

Representing the G ame Board

W e’re going to represent the gam e board using a sim ple l ist . T he hexagons wil l be stored in this l ist , sta rt ing a t the top le ft , andthen m oving ac ross and down. For each hexagon, we’l l store a l ist of two i tem s: a num ber indica ting the current occupant of thehexagon and another num ber indica ting the num ber of dice a t tha t loca tion.

For instance , he re is an exam ple of a gam e board and the l ist tha t encodes i t :

((0 3) (0 3) (1 3) (1 1))Note tha t m ost L isp program m ers l ike to count sta rt ing a t ze ro. T here fore , players A and B a re represented with the num bers 0

and 1. T his l ist indica tes tha t player A has three dice on the first hexagon and three on the second. Player B has three dice on thethird hexagon and one on the fourth.

W hen we c rea te our AI player, i t wil l need to be able to look a t m any hexagons on the board very quickly. Because of this,we’re going to c rea te a second representa t ion of our board in the form of an a rray. Rem em ber tha t checking a num eric loca tion(for instance , hexagon 2) in a l ist requires the nth func tion, which is potentia l ly slow. Arrays, on the other hand, wil l a l low for ve ryfast lookup a t a spec ific loca tion, even with very la rge board sizes.

T he board-array func tion converts a board represented with a l ist to an a rray for us:

Page 344: Land of Lisp - Barski M.D., Conrad

(defun board-array (lst)(make-array *board-hexnum* :initial-contents lst))

W hen the gam e begins, we’l l sta rt with a random ized board. Here ’s the func tion tha t c rea tes a random board:

(defun gen-board ()

(board-array (loop for n below *board-hexnum*

collect (list (random *num-players*)(1+ (random *max-dice*))))))

T his func tion is not in the func tiona l style (as the icon indica tes), since i t wil l c rea te a diffe rent , random result every t im e i t isca l led. It genera tes the board as a l ist , but then converts the l ist to our speedie r a rray form at when i t’s done , using board-array

.It genera tes random va lues using the L isp func tion random. T his func tion produces a diffe rent random integer every t im e , grea te r

than or equa l to ze ro, but sm alle r than the num ber passed to i t . W e use our *num-players* and *max-dice* globa l va riables to

genera te random va lues for each hexagon .L e t’s t ry out the gen-board func tion:> (gen-board)

#((0 3) (1 2) (1 3) (0 1))Rem em ber tha t the hash m ark (#) indica tes tha t we’ve c rea ted an a rray, not a l ist .W e’ll nam e our players using le t te rs (just A and B, unti l we sta rt introduc ing m ore players). Here ’s a func tion tha t converts a

player num ber into a le t te r:

Page 345: Land of Lisp - Barski M.D., Conrad

(defun player-letter (n)(code-char (+ 97 n)))

T he code-char func tion converts an ASCII code into the appropria te charac te r. L e t’s ca l l i t for player 1 to see the result :> (player-letter 1)

#\bFina lly, le t’s c rea te a func tion tha t wil l take an encoded board and draw i t in a pre t ty way on the sc reen. It wil l t i l t the board in

the sam e way as our drawings, so i t ’s obvious which six hexagons ne ighbor any given hexagon.

Page 346: Land of Lisp - Barski M.D., Conrad

(defun draw-board (board)

(loop for y below *board-size*do (progn (fresh-line)

(loop repeat (- *board-size* y)do (princ " "))

(loop for x below *board-size*

for hex = (aref board (+ x (* *board-size* y)))do (format t "˜a-˜a " (player-letter (first hex))

(second hex))))))Since the whole purpose of this draw-board func tion is to write stuff to the console , i t ’s de fini te ly not func tiona l . L e t’s look a t

this func tion m ore c lose ly.

T he oute r loop runs through a l l the rows of the board, stored in the variable y . T here a re two inner loops. T he first inner

loop adds the indenta t ion to the le ft side to give the board tha t t i l ted look . T he second inner loop loops through the colum ns,

stored in the variable x . It then uses x and y to ca lcula te the appropria te hex num ber, and re trieves tha t hex from the board

array using aref . Fina lly, i t prints the da ta in the hex .Here ’s the output of the draw-board func tion, as well a s a drawing to com pare i t with:> (draw-board #((0 3) (0 3) (1 3) (1 1)))

a-3 a-3b-3 b-1

Page 347: Land of Lisp - Barski M.D., Conrad
Page 348: Land of Lisp - Barski M.D., Conrad

Decoupling Dice of Doom's Rules from the Rest of the G ame

Now we’re ready to write the code tha t takes ca re of the guts of our first Dice of Doom im plem enta t ion. In writ ing this code ,we’re going to em ploy a powerful func tiona l program m ing technique : a func tion pipe line . T his m eans tha t our gam e is going toconsist of a succession of func tions tha t opera te , one a fte r another, on a big chunk of da ta , which wil l hold a representa t ion of ourgam e board, m aking m odifica t ions to the struc ture a long the way. A func tion pipe line wil l a l low us to build a gam e rule enginetha t’s 100% decoupled from the rest of the game code . T o understand why this is so cool , le t’s first consider som e of wha t’s involvedin writ ing a board gam e with a sm art AI player.

For one thing, any com pute r im plem enta t ion of a board gam e wil l need code tha t handles the hum an player’s m oves. T his pa rtof the code wil l need to know the rules of the board gam e and m ake sure the hum an player’s m ove is lega l be fore le t t ing i thappen.

W e’ll a lso need to write the AI code . And in order for the AI player to pick a m ove , i t needs to know a ll the rules of the boardgam e.

Notice som ething? Both of these separa te parts of our gam e engine need to understand the rules of the gam e! Clearly, wha t wewant to do is break our gam e code into three big pieces:

T he handling of the hum an’s m ovesT he AI playerT he rule engine

One piece handles the player’s m oves. Another is the code for the AI player. Both of these then ta lk to som e code tha tunderstand the rules, sort of a “ rule engine . ” Is this kind of design possible?

In a tradit iona l , im pera tive program m ing style , i t would be very difficult to write a program like this. Most im pera tive gam eengines duplica te the code tha t “understands the rules, ” because of the com plexity of writ ing ful ly decoupled com ponents in anim pera tive language . T he reason for this is tha t a board gam e requires a lot of context—every m ove is dependent on what m ovespreceded i t . T his m eans tha t every t im e the AI m odule or player-handling m odule needs to check the rules, i t m ust te l l the “ rulecode” the current context in de ta i l . Both would need to te l l the rule code tha t “ It’s player so-and-so’s turn and the gam e boardlooks l ike such-and-such. ” W ithout this inform ation, the rule code can’t te l l whe ther or not a m ove is lega l .

Passing a round this context requires tons of tedious bookkeeping code everywhere , is e rror-prone , and is ine ffic ient . It ’sine ffic ient because , with a na ive design, the player-handling code m ay check the lega li ty of m oves the AI code had a lreadyexplored and found lega l .

Using func tiona l program m ing, however, we can decouple these three concerns entire ly in our program . W e wil l be able to dothis without bookkeeping code and in a way tha t avoids duplica t ion any lega li ty ca lcula t ions. W e wil l accom plish this by encodingour rule code in a lazy gam e tree !

Note

T he basic approach we’re using—program m ing a gam e in the func tiona l style using a lazy gam e tree and a func tion pipe line—isdescribed in the c lassic paper “W hy Func tiona l Program m ing Matte rs” by John Hughes (ht tp: / /www.scribd. com /doc /26902/whyfp/).

In this chapte r, we’l l be c rea ting a gam e tree tha t is not ye t lazy. You’l l need to wait unti l Chapte r 18 to understand lazyprogram m ing and what a lazy gam e tree wil l look l ike . T ha t’s a lso when you’l l be able to ful ly apprec ia te how cool thisa rchitec tura l design rea l ly is.

Page 349: Land of Lisp - Barski M.D., Conrad
Page 350: Land of Lisp - Barski M.D., Conrad

G enerating a G ame Tree

T he entire rule se t for our gam e is encoded in the fol lowing m aste r func tion:

(defun game-tree (board player spare-dice first-move)

(list playerboard

(add-passing-move boardplayerspare-dicefirst-move

(attacking-moves board player spare-dice))))T he game-tree func tion builds a tree of a l l possible m oves, given a ce rta in sta rt ing configura t ion. T his func tion wil l be ca l led

only a single t im e a t the beginning of the gam e. It wil l then recursive ly build a tree of a l l possible m oves for the gam e, down tothe fina l winning posit ions. T he other parts of our gam e wil l then e legantly traverse this t ree in order to conform to the rules of thegam e.

In order to ca lcula te the lega l possible m oves of the gam e tree from a given context , the func tion needs four pieces of da ta

passed to i t a s a rgum ents :

W hat the board looks l ikeT he current playerHow m any dice have been captured by the player in the player’s current turn, which is needed to ca lcula te any

future re inforcem ents, a s pe r our rulesW hether the current m ove is the first m ove for the current player, because a player can’t pass a turn without first

m aking a t least one m ove

As the game-tree func tion c rea tes the tree , i t wil l put inform ation about the current board and current player a t every branch

Page 351: Land of Lisp - Barski M.D., Conrad

. T he subbranches wil l then hold a l l the lega l fol low-up m oves from the current branch:

T here a re two types of lega l m oves possible for players: a t tack a hexagon or pass the ir turn to the next player (assum ing they’vea lready a t tacked a t least once a lready). T he passing m ove is added to the l ist of lega l m oves through the add-passing-move

func t ion . T he a t tacking m oves a re added to the l ist through the attacking-moves func tion . L e t’s look a t thesefunc tions next .

Page 352: Land of Lisp - Barski M.D., Conrad

Calculating P assing M oves

Here is the func tion tha t adds the passing m oves to the gam e tree :

(defun add-passing-move (board player spare-dice first-move moves)

(if first-move

moves

(cons (list nil

(game-tree (add-new-dice board player (1-spare-dice))

(mod (1+ player) *num-players*)0t))moves)))

T he job of this func tion is to add a passing m ove to the ta l ly of m oves, i f passing is pe rm itted. T he current l ist of m oves is

passed in to this func tion , and then the func tion wil l re turn the expanded l ist of m oves. If the m ove is the first m ove in a

player’s turn , no passing is a l lowed, and we just re turn the una lte red l ist . Otherwise , we add a new m ove to the l ist .E very m ove in our gam e tree consists of two parts:

T he first pa rt is a desc ript ion of the m ove . Since we’re just passing in this m ove , we’l l se t the descript ion to nil

.T he second part of the m ove is an entire ly new gam e tree , which holds the entire universe of m oves tha t exists

a fte r this m ove has been perform ed. W e c rea te this by recursive ly ca l l ing game-tree aga in . Since this is the

Page 353: Land of Lisp - Barski M.D., Conrad

end of the player’s turn, the player m ay rece ive dice as re inforcem ents. So, we upda te the board sent to this new

game-tree ca l l with the add-new-dice func tion .

Of course , we a lso wil l need to change the current player, since a new person’s turn is now sta rt ing. W e do this by adding one to

the current player num ber and taking the m odulus of the result , with the tota l num ber of players as the denom ina tor .Changing a player in this fancy way wil l a l low the code to work, even when we increase the num ber of players in the gam e infuture versions.

Page 354: Land of Lisp - Barski M.D., Conrad

Calculating Attacking M oves

Here is the func tion tha t adds the possible a t tacking m oves to the gam e tree :

(defun attacking-moves (board cur-player spare-dice)

(labels ((player (pos)(car (aref board pos)))

(dice (pos)(cadr (aref board pos))))

(mapcan (lambda (src)

(when (eq (player src) cur-player)

(mapcan (lambda (dst)(when (and (not (eq (player dst) cur-player))

(> (dice src) (dice dst)))(list

(list (list src dst)

(game-tree (board-attack board cur-player src dst (dice src))cur-player(+ spare-dice (dice dst))nil)))))

(neighbors src))))(loop for n below *board-hexnum*collect n))))

T he attacking-moves func tion is a bi t m ore com plica ted than the add-passing-move func tion. It’s responsible for scanning the

Page 355: Land of Lisp - Barski M.D., Conrad

current gam e board and figuring out wha t m oves the current player is lega lly a l lowed to perform .Since i t m ust spend a lot of t im e figuring out who the player is on a given hexagon, we first wri te a convenience func tion ca lled

player tha t re turns the player for a given board posit ion . W e write a sim ila r func tion to ge t the num ber of dice on a given

hexagon .Next, we need to scan the board top to bottom and find out which squares the current player occupies. For each occupied square ,

the re m ay be one or m ore lega l a t tacks sta rt ing a t tha t posi t ion. Since the num ber of a t tacks from any hexagon m ay vary, we use

mapcan to scan the board . Rem em ber tha t mapcan le ts each hexagon we scan re turn i ts results as a l ist . T hen mapcanconca tena tes these l ists toge ther. T his way, any scanned hexagon can contribute ze ro to n m oves to the l ist .

W ithin the lambda func tion used by the mapcan, which ge ts ca l led for every hexagon, we first want to check whether the current

player occupies this hexagon . T hen we want to check a l l of i ts ne ighbors to see if any of them present a viable a t tack. W e

do this with another mapcan . W e’ll figure out the ne ighbors to this hexagon by using the neighbors func tion, which we’l l

write short ly .How do we dec ide if a hexagon can be an a t tack dest ina tion? W ell , i t m ust be a hexagon we don’t a lready own, plus (as per the

rules) the source hexagon needs to have m ore dice than the dest ina tion hexagon . If we have found a lega l a t tack m ove , we

then describe the m ove . T he descript ion is sim ply a l ist of the source posit ion and the dest ina tion posit ion. W e then (as with

passing m oves) recursive ly genera te another gam e tree tha t desc ribes wha t happens if the m ove is executed .

Page 356: Land of Lisp - Barski M.D., Conrad

F inding the Neighbors

Next, le t’s c rea te the func tion tha t ca lcula tes the ne ighboring hexagons to a given hexagon:

(defun neighbors (pos)(let ((up (- pos *board-size*))(down (+ pos *board-size*)))

(loop for p in (append (list up down)

(unless (zerop (mod pos *board-size*))(list (1-up) (1-pos)))

(unless (zerop (mod (1+ pos) *board-size*))(list (1+ pos) (1+ down))))

when (and (>= p 0) (< p *board-hexnum*))collect p)))

E very hexagon on the board m ay have up to six ne ighbors, or fewer, i f the hexagon is on an edge of the board. W e build up a l ist

of possible ne ighbors in a loop , and then collec t the ones with posit ion num bers tha t a ren’t off the edge of the board .Also, since our posit ion num bers wrap from row to row, we need to m ake sure we don’t look to the le ft i f we’re on the le ft edge of

the board or look to the right i f we’re on the right edge of the board .T his func tion is m arked c lean (i t is in the func tiona l style ), but none the less conta ins a loop. Usua lly, looping goes aga inst the

tene ts of func tiona l program m ing. However, m any L ispers consider i t kosher to use a loop in func tiona l code if a l l i t does iscollec t som e va lues, since i t rea l ly isn’t m uta t ing any va lues or produc ing any other side e ffec ts. So, we wil l a l low ourse lves to usesuch loops in the func tiona l-style part of this gam e.

L e t’s t ry out our neighbors func tion:> (neighbors 2)

(0 3)

Page 357: Land of Lisp - Barski M.D., Conrad

As you can see , i t correc tly te l ls us tha t hexagon 2 ne ighbors hexagons 0 and 3.

Page 358: Land of Lisp - Barski M.D., Conrad

Attacking

Now le t’s write our board-attack func tion:

(defun board-attack (board player src dst dice)

(board-array (loop for pos

for hex across board

collect (cond ((eq pos src) (list player 1))

((eq pos dst) (list player (1-dice)))

(t hex)))))T his is a func tion tha t figures out wha t happens if the hexagon src a t tacks the hexagon dst. It works by looping ac ross the board,

keeping track of the current posi t ion and the contents in the hexagon a t tha t posi t ion . If the current hexagon is the

source hexagon, we just place a single die in tha t place ; as pe r our rules, a single die is le ft behind a fte r an a t tack . If the

current hexagon is the dest ina tion posit ion, we place the rem aining dice the re , subtrac t ing the one le ft behind . In other

cases, we just collec t the very sam e hex .L e t’s t ry out our board-attack func tion:> (board-attack #((0 3) (0 3) (1 3) (1 1)) 0 1 3 3)

#((0 3) (0 1) (1 3) (0 2))

Page 359: Land of Lisp - Barski M.D., Conrad

As you can see , a t tacking from hexagon 1 to 3 causes board-attack to properly upda te the gam e board, so tha t one die rem ainson the old square and two a re on the new, conquered square .

Note

Many of the func tions in this chapte r have ine ffic ienc ies to keep things sim ple . W e’ll fix m any of these in future versions of thegam e.

Page 360: Land of Lisp - Barski M.D., Conrad

Reinforcements

T o add the re inforcem ents to the board, we need to scan ac ross the gam e board, find occupied spots tha t can accom m odateanother die , and add the die the re . Of course , the num ber of re inforcem ents is l im ited based on how m any opponent dice the playercaptured in the last turn. Because of this, we’l l need to keep a running ta l ly of how m any re inforcem ent dice rem ain.

T he m ost obvious way to track the rem aining dice would be to have a remaining-dice va riable , and decrem ent this every t im ea die is placed. However, having a die tha t is decrem ented (m uta ted) would not be in l ine with the func tiona l style .

T here fore , instead, we’re going to write our add-new-dice func tion using a loca l recursive func tion, which wil l a lso m ainta inthis running count of dice .

Here is this add-new-dice func tion:

(defun add-new-dice (board player spare-dice)

(labels ((f (lst n)

(cond ((null lst) nil)

((zerop n) lst)(t (let ((cur-player (caar lst))(cur-dice (cadar lst)))

(if (and (eq cur-player player) (< cur-dice *max-dice*))(cons (list cur-player (1+ cur-dice))

(f (cdr lst) (1-n)))

(cons (car lst) (f (cdr lst) n))))))))

(board-array (f (coerce board 'list) spare-dice))))

T he first thing add-new-dice does is de fine a loca l func tion nam ed f . T his func tion wil l be our l ist-ea te r tha t goes throughthe hexagons of the board and spits out a new l ist tha t inc ludes the re inforcem ents. Since our board is ac tua lly stored in an a rray for

Page 361: Land of Lisp - Barski M.D., Conrad

effic iency reasons, we convert our a rray into a l ist with the coerce func tion before ca l l ing f .Inside the func tion f, we m ust consider three si tua tions:

T ha t we’re a t the end of the board. In this case , the re inforced board wil l a lso be com ple ted, so we just re turn

nil .T ha t we’re out of spare-dice to add to add as re inforcem ents. In this case , the rest of the board wil l just be the

sam e as before , so we can just re turn the rem ainder of the l ist a s the new board .Ne ither of the preceding si tua tions. In a l l other cases, we need to ana lyze the current hexagon and dec ide

whether a re inforcem ent should be added in i t . W e check whether the current player occupies tha t hexagon and

whether we have less than the m axim um num ber of dice on tha t square . If this is the case , we add a new die

on the hexagon and ca ll f aga inst the rest of the board, recursive ly . Otherwise , we leave the current hexagon

unchanged and proceed by recursive ly ca l l ing f aga inst the rest of the board .

L e t t ry adding re inforcem ents to a board:> (add-new-dice #((0 1) (1 3) (0 2) (1 1)) 0 2)

#((0 2) (1 3) (0 3) (1 1))As you can see , add-new-dice properly placed two re inforcem ent dice for player A (player 0).

Page 362: Land of Lisp - Barski M.D., Conrad

Trying O ut O ur New game-tree F unction

W e have now writ ten a l l the code needed to c rea te a com prehensive gam e tree of our sim plified version of Dice of Doom . Butbe ca re ful! A gam e tree of m ost board gam es is excruc ia t ingly la rge . E ven on a 2-by-2 board, our gam e m ay consist of hundreds ofpossible m oves. You’l l want to ca l l the game-tree func tion only on a gam e board tha t is near the end of play, or you’l l bewatching he lplessly as the CL ISP RE PL prints out a hum ongous tree showing a l l the possible ways in which a gam e m ay progress.

Here is a sa fe board posit ion for you to try out:

> (game-tree #((0 1) (1 1) (0 2) (1 1)) 0 0 t)

(0

#((0 1)(1 1) (0 2) (1 1))

(((2 3)(0#((0 1) (1 1) (0 1) (0 1))

((NIL(1#((0 1) (1 1) (0 1) (0 1))NIL)))))))

T he gam e tree first l ists the current player num ber , the layout of the board , and then the lega l m oves for tha tcontext . For the ini t ia l board posit ion, a t the beginning of player A’s turn, the re is only one possible m ove : T he player can m ove

from hexagon 2 to hexagon 3, capturing player B’s die in tha t spot . Afte r tha t , the player can pass. Player B now has no

m ove ava ilable . Since this player’s gam e tree has no ava ilable m oves l isted , the gam e has ended, with a win for player A.

Page 363: Land of Lisp - Barski M.D., Conrad

P laying Dice of Doom Against Another H uman

Now tha t we’ve com ple te ly captured the universe of Dice of Doom in our com prehensive game-tree func tion, i t ’s sim ple toc rea te a hum an versus hum an version of this gam e. All we need to do is c rea te som e func tions tha t t rave l down the gam e tree asplayers choose the ir m oves.

The M ain Loop

Here is the func tion tha t t rave ls down the gam e tree , a l lowing two hum ans to play Dice of Doom :

(defun play-vs-human (tree)

(print-info tree)

(if (caddr tree)

(play-vs-human (handle-human tree))

(announce-winner (cadr tree))))T his func tion, play-vs-human, is the m ain loop of our gam e. It accepts a tree describing the sta rt ing posit ion of the board.First , i t ca l ls a func tion nam ed print-info, which wil l draw the board on the sc reen, a long with other he lpful inform ation about

the current sta te of the gam e . Next, we need to check if any follow-up m oves exist . T hese fol low-up m oves would be l isted

sta rt ing a t the caddr posi t ion of the gam e tree .If fol low-up m oves a re ava ilable , we ca ll the func tion handle-human, which wil l inte rac t with the current player to he lp him

pick his new m ove . T his handle-human func tion wil l then re turn the subbranch of the tree tha t represents the player’s choice . W e

can then recursive ly pass this subbranch into play-vs-human to proceed with the gam e .If no fol low-up m oves a re ava ilable , the gam e has offic ia l ly ended. W e then ca ll the announce-winner func tion, which,

Page 364: Land of Lisp - Barski M.D., Conrad

appropria te ly, wil l announce the winner .

G iving Information About the State of the G ame

Here is the print-info func tion, which describes the sta tus of the current node in the gam e tree :

(defun print-info (tree)(fresh-line)

(format t "current player = ˜a" (player-letter (car tree)))

(draw-board (cadr tree)))

T his func tion displays two im portant pieces of inform ation on the RE PL . First , i t shows who the current player is . T hen i t

prints out a pre t ty version of the gam e board with the draw-board func tion .

H andling Input from H uman P layers

Next is the func tion tha t le ts hum ans choose the ir next m ove . It displays a very he lpful , num bered m enu of a l l currentlyava ilable m oves for the player to choose from .

Page 365: Land of Lisp - Barski M.D., Conrad

(defun handle-human (tree)(fresh-line)(princ "choose your move:")(let ((moves (caddr tree)))

(loop for move in moves

for n from 1do (let ((action (car move)))(fresh-line)

(format t "˜a. " n)

(if action

(format t "˜a -> ˜a" (car action) (cadr action))

(princ "end turn"))))(fresh-line)

(cadr (nth (1- (read)) moves))))T o display the l ist of ava ilable m oves, we use a loop tha t t raverses a l l the ava ilable m oves and prints a desc ript ion about each

one . T his loop is not func tiona l , since i t prints stuff on the sc reen for the player to read. W e print a counting num ber in

front of each m ove using the variable n, which counts from 1 inside our loop .

E ach m ove has an ac t ion va lue assoc ia ted with i t . If the ac t ion is non-nil , then the ac t ion is an a t tack, where the ac t ion

Page 366: Land of Lisp - Barski M.D., Conrad

va lue describes the source and dest ina tion hexagons of the a t tack. W e print such a t tacking ac tion using the format com m and .

W e use an em pty ac tion va lue to represent the passing m ove . In tha t case , we just princ “end turn” to describe this m ove .Afte r the ava ilable m oves have been displayed, we use read to read in the player’s choice . W ith the nth func tion, we can then

se lec t tha t branch of the gam e tree and re turn i t from our handle-human func tion .

Determining the Winner

T he task of announc ing the winner can be nice ly broken into a c lean/func tional and a dirty / imperative pa rt .T he c lean part concerns the task of ca lcula t ing the winning player. W e want to ca lcula te this in a way tha t can handle m ore

than just two players, since our gam e wil l a l low for m ore in the future . Also, the func tion m ust be cognizant of possible t ie s.T o accom plish this, we’l l wri te a func tion ca lled winners tha t re turns a l ist of one or m ore players who captured the m axim um

num ber of hexagons a t the end of the gam e. If the re is a t ie , i t wil l sim ply re turn a l l the players who share first place , in te rm s ofthe tota l count of occupied spaces for a l l players. W ith this design, the func tion wil l work for any number of players and wil le legantly handle t ie s. T his is wha t the winners func tion looks l ike :

(defun winners (board)

(let* ((tally (loop for hex across boardcollect (car hex)))(totals (mapcar (lambda (player)

(cons player (count player tally)))

(remove-duplicates tally)))

(best (apply #'max (mapcar #'cdr totals))))

(mapcar #'car

Page 367: Land of Lisp - Barski M.D., Conrad

(remove-if (lambda (x)(not (eq (cdr x) best)))totals))))

W e ca lcula te the winner for a given ending board posit ion in four steps.

First , we build up a ta l ly of who occupies each hexagon on the board . W ith the across loop construc t , wecan traverse the a rray of the ending board direc t ly and collec t the occupie r of each hexagon.

Second, we need to count the tota l num ber of squares each player has captured, using this ta l ly. T he tota lsvariable wil l be an a l ist of player->spaces pa irs. W e build this a l ist by finding a l l players who have a t least one

entry in the ta l ly with remove-duplicates . W e can m ap across this and then c rea te a count for each

occupie r .T hird, we want to find what the m axim um num ber of occupied hexagons for a single player is. W e do this by

stripping the counts from our a l ist by m apping cdr ac ross the l ist . W e then apply max to this l ist to find thela rgest num ber of occupied spaces for a single player.

Fina lly, we need c rea te a l ist of a l l the “best” players. W e do this by stripping out a l l but the best from our tota ls

using the remove-if func tion . W e then just pull out the player num bers for the best players by m apping car

across the l ist of bests .

Next, le t’s write the dirty announce-winner func tion:

(defun announce-winner (board)(fresh-line)

(let ((w (winners board)))

Page 368: Land of Lisp - Barski M.D., Conrad

(if (> (length w) 1)

(format t "The game is a tie between ˜a" (mapcar #'player-letter w))

(format t "The winner is ˜a" (player-letter (car w))))))

T his func tion is ra ther sim ple . First , we ca lcula te the winners by ca l l ing our ea rl ie r func tion . T hen we check if the re is

m ore than one winner (a t ie ). For t ie s, we print a spec ia l m essage . Otherwise , we just announce a single winner .

Trying O ut the H uman vs. H uman Version of Dice of Doom

W e now have a com ple te ly playable gam e of dice of doom . Here is an exam ple gam e from sta rt to finish:> (play-vs-human (game-tree (gen-board) 0 0 t))

current player = ab-2 b-2a-2 b-1choose your move:1. 2 -> 31current player = ab-2 b-2a-1 a-1choose your move:1. end turn1current player = bb-2 b-2a-1 a-1choose your move:1. 0 -> 22. 0 -> 33. 1 -> 31current player = bb-1 b-2b-1 a-1choose your move:1. end turn2. 1 -> 31current player = ab-1 b-2b-1 a-1The winner is b

Page 369: Land of Lisp - Barski M.D., Conrad

Creating an Inte ll igent Computer O pponent

As we discussed when we were designing the gam e tree code for Dice of Doom , having a separa te gam e tree genera tor m akes i teasy to add an AI player to a gam e engine . In fac t , we’re now going to add a com pute r player tha t can play an absolute ly perfec tgam e with only 23 addit iona l l ines of code!

So how does an AI player dec ide to m ake a m ove? W e’ll use the fol lowing stra tegy:

1. L ook a t each ava ilable m ove .2. Give a point ra t ing to the board posit ion result ing from each m ove .3. Pick the m ove with the m axim um point ra t ing.

T his sounds l ike a sim ple plan, but the re is one step in this a lgori thm tha t’s pre t ty tricky: ca lcula t ing the best point ra t ing for agiven board posit ion.

If a m ove leads im m edia te ly to a win, i t ’s easy to give a point ra t ing to tha t m ove—any winning m ove c lea rly deserves a veryhigh point ra t ing. However, m ost m oves in a gam e cannot lead to an im m edia te win. In those cases, in order to de te rm ine if theresult of a se t of m oves deserves a good point ra t ing, we need to figure out wha t the opponent player wil l do in response .

But how will we know what the opponent player wil l dec ide to do? If we’re not ca re ful , we’l l end up in an ugly im passe wherewe say, “He thinks tha t I think tha t he thinks tha t I think . . . ” in order to ca lcula te a m eaningful point va lue for a given boardposit ion. How do we account for the opponent’s behavior without giving ourse lves a headache?

Page 370: Land of Lisp - Barski M.D., Conrad
Page 371: Land of Lisp - Barski M.D., Conrad

The M inimax Algor ithm

It turns out tha t for a two-player board gam e, a sim ple m ethod exists to m ode l wha t an opponent wil l do. W e sim ply accept thetruism “W hat is good for m y opponent is bad for m e .” T his m eans we can use the fol lowing approach to m ode l a m ove for theopponent:

1. L ook a t each ava ilable m ove .2. Give a point ra t ing to the board posit ion result ing from each m ove .3. Pick the m ove with the m inim um point ra t ing.

T his a lgori thm for est im ating what an opponent wil l do is identica l to the one used for the prim ary player, except tha t in step 3,we pick the m ove with the minimum instead of maximum ra t ing. T he benefi t of this approach, ca l led the minimax algorithm, is tha twe use the sam e point ra t ings when working out the opponent’s m oves tha t we use for the prim ary AI player, but then just tweak thethird step a l i t t le to adjust .

T his is c ruc ia l : It turns out tha t i f we can avoid ca lcula t ing separa te ra t ings for ourse lves as for our opponent in the gam e, thensearching down the gam e tree for good m oves becom es dram atica l ly easie r and faste r.

Note

T he basic m inim ax a lgori thm works only in two-player gam es. W hen three or m ore players a re involved in a gam e, we can’trea l ly say tha t “W hat is good for m y opponent is bad for m e” is com ple te ly true any m ore . T his is because an addit iona l t ruismbecom es im portant: “T he enem y of m y enem y is m y friend. ” T his m eans tha t som e of m y opponents m ay, a t t im es, ac t as a friendby m aking m oves tha t ha rm a com m on enem y, while not a ffec t ing m e direc t ly. W e’ll discuss this issue m ore in Chapte r 20.

Page 372: Land of Lisp - Barski M.D., Conrad

Turning M inimax into Actual Code

Now we’re ready to put the m inim ax idea into prac tice , l ike so:

(defun rate-position (tree player)(let ((moves (caddr tree)))

(if moves(apply (if (eq (car tree) player)

#'max

#'min)

(get-ratings tree player))

(let ((w (winners (cadr tree))))(if (member player w)

(/ 1 (length w))

0)))))T he rate-position func tion genera tes a num eric point ra t ing for a given branch of the gam e tree . In order to do this, we first

need to figure out i f the re a re any m oves ava ilable from the given posit ion (tha t is, the current m ove is not an ending m ovein the gam e).

If m oves a re ava ilable , we’l l need to look a t a l l the subsequent m oves to dec ide how to ra te the current posi t ion. W e accom plish

this by ca l l ing get-ratings , a func tion tha t wil l re turn the point ra t ing of each follow-up m ove . As per m inim ax, we wil l

then pick e i the r the best (max) or worst (min) ra t ing of a l l the fol low-up m oves, depending on whether the m ove be ingra ted is for the AI player or i ts opponent.

Page 373: Land of Lisp - Barski M.D., Conrad

If, on the other hand, the re a re no follow-up m oves, we’l l need to check who the winner is for the current board posit ion . If

the player isn’t am ong the winners of this posi t ion, we can give the posit ion the m inim um ra t ing of 0 . Otherwise , we’l l

divide one by the num ber of winners to de te rm ine our ra t ing . By doing this, we a lso give a m eaningful ra t ing for t ie s. If theplayer is the sole winner, the ra t ing, using this form ula , wil l be the m axim um va lue of 1. For a two-player t ie , the ra t ing wil l be asensible 0.5.

Here is wha t the get-ratings func tion looks l ike :

(defun get-ratings (tree player)(mapcar (lambda (move)(rate-position (cadr move) player))(caddr tree)))

T his func tion sim ply m aps rate-position ac ross each ava ilable fol low-up m ove for the given branch of the tree .

Page 374: Land of Lisp - Barski M.D., Conrad

Creating a G ame Loop with an AI P layer

E arlie r, we wrote a func tion ca lled handle-human tha t inte rac ted with a hum an to dec ide on a m ove in the gam e. Here is anana logous func tion, handle-computer, tha t inte rac ts with our AI player to choose a m ove :

(defun handle-computer (tree)

(let ((ratings (get-ratings tree (car tree))))

(cadr (nth (position (apply #'max ratings) ratings) (caddr tree)))))

T his handle-computer func tion is quite stra ightforward. First , we ge t the ra t ings of each ava ilable m ove . T hen we pick

the m ove tha t is ra ted the highest .Fina lly, le t’s c rea te a func tion tha t handles the m ain loop for playing aga inst the com pute r. T his one is ana logous to our ea rl ie r

play-vs-human func tion:

Page 375: Land of Lisp - Barski M.D., Conrad

(defun play-vs-computer (tree)

(print-info tree)

(cond ((null (caddr tree)) (announce-winner (cadr tree)))

((zerop (car tree)) (play-vs-computer (handle-human tree)))

(t (play-vs-computer (handle-computer tree)))))

As with the play-vs-human func tion, play-vs-computer fi rst prints out inform ation about the current sta te of the gam e . If

no m ore m oves a re ava ilable , i t then ca lls the announce-winner func tion .Next, we need to check who the current player is. By convention, we’l l have the hum an be player A (player 0). If the player

num ber is 0, we ca ll our old handle-human func tion to le t the hum an dec ide on her m ove . Otherwise , we trea t the player as

an AI player and use the handle-computer func tion to dec ide on a m ove .W e have now writ ten a ful ly func tiona l AI engine for Dice of Doom !

Page 376: Land of Lisp - Barski M.D., Conrad

P laying O ur F irst H uman vs. Computer G ame

T he following is an exam ple gam e playing aga inst the com pute r AI. T he com pute r plays an optim al gam e and wins.> (play-vs-computer (game-tree (gen-board) 0 0 t))

current player = aa-3 b-3a-2 b-2choose your move:1. 0 -> 31current player = aa-1 b-3a-2 a-2choose your move:1. end turn1current player = ba-2 b-3a-2 a-2current player = bb-2 b-1a-2 a-2current player = ab-3 b-1a-2 a-2choose your move:1. 3 -> 11current player = ab-3 a-1a-2 a-1choose your move:1. end turn1current player = bb-3 a-1a-2 a-1current player = bb-1 a-1b-2 a-1current player = bb-1 a-1b-1 b-1current player = ab-2 a-1b-2 b-1The winner is b

Page 377: Land of Lisp - Barski M.D., Conrad

M aking Dice of Doom F aster

T he func tiona l program m ing style can lead to slow code , a t least in the hands of a novice program m er. W e used the func tiona lstyle to deve lop the core of Dice of Doom . Hence , this first ve rsion of our gam e is excruc ia t ingly ine ffic ient . W e had to l im it ourgam e to a 2-by-2 board to m ake i t playable . But now we can increase our board size to 3-by-3, as we optim ize our gam e engine .

L e t’s inc rease the param ete rs controll ing the board size to m ake this happen. You m ay not want to play a gam e a t this new sizeunti l you’ve im plem ented a l l the optim iza tions throughout the rest of this chapte r, unless you a re an extrem ely pa tient pe rson anddon’t m ind having the com pute r take m inutes building the ini t ia l gam e tree and dec iding on m oves.(defparameter *board-size* 3)

(defparameter *board-hexnum* (* *board-size* *board-size*))T here , we’ve upgraded the board size to 3 by 3.T he rest of this chapte r covers som e im portant techniques for optim iz ing func tiona l code . T hese techniques apply to a l l program s

writ ten in the func tiona l style , which inc ludes Dice of Doom . In la te r chapte rs, we’l l add other optim iza tions. E ventua lly, we’l l beable to play aga inst an AI player on m uch m ore spac ious boards, while st i l l having e legant code writ ten in the func tiona l style .

Page 378: Land of Lisp - Barski M.D., Conrad

Closures

Before we sta rt optim iz ing Dice of Doom , the re is an im portant L isp program m ing concept we need to discuss: c losures. Closuresa re extra bi ts of da ta from the outside world tha t a re captured whenever a lam bda func tion is c rea ted. T o understand the hows andwhys of capturing variables in a c losure , consider the fol lowing exam ple :> (defparameter *foo* (lambda ()

5))*FOO*

> (funcall *FOO*)5

In this exam ple , we’re c rea ting a new, unnam ed func tion , and then se t t ing *foo* equa l to this func tion. Next, we ca ll this

func tion using the funcall com m and . As you would expec t , the va lue re turned from this func tion is 5. All the lam bdafunc tion does is re turn this num ber.

Next, consider this m ore inte rest ing exam ple :

> (defparameter *foo* (let ((x 5))(lambda ()

x)))*FOO*

T his version of foo is exac tly the sam e as the previous version of *foo*, except tha t we first dec la re a loca l va riable x ,

which is se t to 5. T hen, in the body of the lambda, we re turn x . So, wha t do you think wil l happen if we ca ll this new versionof *foo*?

T he reason this is a tough question is tha t x is dec la red as a “ loca l” variable . However, x (apparently) no longer exists once weca ll *foo*, since we’re a lready long past the point where we’re eva lua ting the body of the let expression.

L e t’s t ry i t out and see wha t happens:> (funcall *foo*)

5Holy cow! Som ehow the lam bda expression we c rea ted rem em bered what x was a t the t im e i t was c rea ted. T he variable x, which

we previously thought of as a loca l va riable , has som ehow m anaged to l ive on past the block in which i t was c rea ted!W hen we first covered let expressions in Chapte r 2, you lea rned tha t advanced L ispers pre fe r to ca l l va riables c rea ted with a

let expression lex ical variables. Now you can see why: A variable c rea ted in this way does not need to be loca l , i f i t is captured ina c losure , by using the variable in a lam bda expression.

T o understand how c losures work, rem em ber tha t L isp uses garbage collec t ion. In fac t , i t was the first language to have thisfea ture . Garbage collec t ion m eans tha t you never have to “ free” variables (as you do in C program m ing). T he L ispcom pile r/ inte rpre te r is sm art enough to know when variables a re no longer in use and destroys them autom atica l ly.

Garbage collec t ion wil l happen a t som e a rbitra ry future t im e a fte r you’ve exited a let expression. Periodica lly, L isp wil l sea rchits m em ory for i tem s tha t a re no longer re fe renced anywhere and can the re fore be sa fe ly destroyed. If L isp notices tha t a va riabledefined in a let is no longer used by anything, i t wil l destroy tha t va riable .

However, i f you c rea te a lam bda expression within the let expression (as we did in the previously), i t ’s possible for thosevariables to l ive on, be ing re fe renced from within the lam bda expression. In tha t case , the garbage collec tor wil l leave thosevariables a lone . Basica l ly, you’ve c rea ted variables tha t a re perm anent—at least a s long as the lam bda expression doesn’t fa l l outof use and ge t ga rbage collec ted.

You can do a lot of cool things using c losures. T hey’re often used for caching sm all pieces of inform ation be tween uses of afunc tion. For instance , he re a func tion tha t rem em bers wha t l ine num ber is currently be ing printed:

> (let ((line-number 0))

(defun my-print (x)

(print line-number)(print x)

(incf line-number)nil))MY-PRINT> (my-print "this")0"this"nil> (my-print "is")1"is"nil> (my-print "a")2"a"nil

Page 379: Land of Lisp - Barski M.D., Conrad

> (my-print "test")3"test"nil

In order to keep track of the l ine num ber, we first c rea te a lexica l va riable nam ed line-number . Next, we dec la re our my-

print func tion using defun , in the body of the let. T his com m and wil l c rea te a lam bda func tion behind the scenes, the re forele t t ing us a lso genera te a c losure .

W ithin the body of the my-print func tion, we can then print the line-number , and even m uta te i t using incf .(incf just adds one to a variable . ) Because the line-number va riable is captured in the c losure , i t can “ l ive on” be tween ca lls tomy-print, a l lowing us to count l ine num bers.

Page 380: Land of Lisp - Barski M.D., Conrad

M emoization

T he first optim iza tion we’re going to perform is ca l led memoization. T his technique m akes use of c losures. Mem oiza tion worksonly for func tions writ ten in the func tiona l style . As you know, the behavior of a func tion in the func tiona l style depends only onthe a rgum ents passed into i t . Also, the only ac t ion of a func tion in the func tiona l style is to ca lcula te a va lue to re turn to theca lle r.

T his suggests an obvious optim iza tion: W hat if we rem em ber the a rgum ents and result of each ca ll of this func tion? T hen, i f thefunc tion ever ge ts ca l led aga in with the sam e a rgum ents, we won’t need to reca lcula te the result . Instead, we can sim ply re turn thepreca lcula ted result .

Severa l func tions in Dice of Doom can benefi t from m em oiza tion.

M emoiz ing the ne ighbors F unction

L et’s sta rt with the neighbors func tion, which le ts us know which hexagons on the board can be a t tacked from a given loca tion:> (neighbors 0)

(3 1 4)W hat neighbors is te l l ing us is tha t i f we want to a t tack other hexagons on the board from hexagon 0, we can reach only

hexagon 3, 1, or 4 (based on our new 3-by-3 board size ).As you m ay rem em ber, the neighbors func tion needed to do a l l kinds of ugly checking for the edges of the board, since

hexagons a long the edges a re l im ited in the hexagons they can a t tack. However, since the shape of the board never changes m id-gam e, these num bers never change for a given board posit ion. T his m akes neighbors a pe rfec t candida te for m em oiza tion! Here isthe code tha t accom plishes this:

(let ((old-neighbors (symbol-function 'neighbors))

(previous (make-hash-table)))

(defun neighbors (pos)

(or (gethash pos previous)

(setf (gethash pos previous) (funcall old-neighbors pos)))))L et’s dissec t this code to m ake sense of wha t’s happening. First , we save the old version of the neighbors func tion in a loca l

variable nam ed old-neighbors . T he symbol-function com m and sim ply re trieves the func tion bound to a sym bol. Usingsymbol-function he re a l lows us to re ta in access to the old va lue of neighbors, even if we define a new func tion with the sam enam e, as we’l l do short ly.

Next, we define a loca l va riable previous , which wil l hold a l l previous a rgum ents and results the func tion has ever seen.T his can be represented as a hash table , where the a rgum ents a re the hash key and the results a re the va lues.

Now we define a new neighbors func tion tha t wil l override the old defini t ion of neighbors . T his new defini t ion wil l addm em oiza tion to the old version of the func tion. T hen we look up the a rgum ent pos in the hash table and re turn i t , i f ava ilable

. Otherwise , we ca ll the old defini t ion of the func tion (tha t’s why we needed to c rea te the old-neighbors lexica l va riable )

and add this new argum ent/result pa ir to the hash table . Since setf re turns the va lue be ing se t , this com m and wil l a lso causethis newly ca lcula ted result to be re turned to the ca l le r of neighbors.

Note

Be care ful not to dec la re the m em oized version of the neighbors func tion m ore than once , without a lso redec la ring the origina lversion of the func tion. Otherwise , the neighbors func tion wil l be wrapped in m ult iple unsightly layers of m em oiza tion, sincethere a re no checks if the m em oiza tion has a lready been done .

M emoiz ing the G ame Tree

T he biggest payoff by fa r for m em oiza tion in our program will be in the game-tree func tion. T his m akes sense , i f you thinkabout how a board gam e works. Very often, you can ge t the sam e board posit ions in a board gam e by perform ing the sam e m oves ina sl ightly diffe rent order. In our na ive version of the game-tree func tion, every diffe rent m ove sequence leads to a com ple te lydiffe rent branch in the gam e tree tha t we need to build in a tota l ly repe ti t ive and ineffic ient way.

In the m em oized version of the game-tree code , the func tion can say to i tse lf, “Hey, I’ve seen tha t board posit ion before !” andcan then share branches of the gam e tree . Here is a m em oized version of game-tree tha t does this:(let ((old-game-tree (symbol-function 'game-tree))

(previous (make-hash-table :test #'equalp)))(defun game-tree (&rest rest)(or (gethash rest previous)(setf (gethash rest previous) (apply old-game-tree rest)))))

As you can see , this m em oiza tion is virtua lly identica l to the one we used for the neighbors func tion. T he only diffe rence is

Page 381: Land of Lisp - Barski M.D., Conrad

tha t we’re se t t ing the hash table to use equalp instead of eql (the default) for the test on the key .T his is because the key (tha t is, the a rgum ents to game-tree) conta ins the gam e board, in the form of an a rray. If we change the

test func tion to be equalp, then L isp wil l check every hexagon on the board and m ake sure i t m atches before using a previousca lcula t ion.

M emoiz ing the rate -position F unction

Another func tion tha t wil l benefi t grea tly from m em oiza tion is the rate-position func tion. Here i t is, m em oized:(let ((old-rate-position (symbol-function 'rate-position))

(previous (make-hash-table)))(defun rate-position (tree player)

(let ((tab (gethash player previous)))

(unless tab

(setf tab (setf (gethash player previous) (make-hash-table))))(or (gethash tree tab)

(setf (gethash tree tab)(funcall old-rate-position tree player))))))

W e need to do som ething a bi t spec ia l for the m em oiza tion on this func tion to work correc tly, because of the tree a rgum entpassed into ra te -posit ion. T he gam e tree is potentia l ly huge , so we need to m ake sure we never com pare a gam e tree objec t withequal (or a sim ila r com parison func tion tha t is slow with la rge l ists). Instead, we want to com pare i t with eql. Because of this, wehandle the m em oiza tion of each of the two param ete rs to rate-position (tree and player) separa te ly. W e accom plish this byhaving nested hash tables.

First , we c rea te an oute r hash table with the default eql te st . T hen, we define a tab va riable tha t looks up one of our

variables (player) in the oute r hash table , to re trieve an inner hash table . If tab is not found in the oute r hash table ,

we’l l c rea te a new, em pty inner hash table , storing i t in the oute r hash table with the sam e key . T he rest of the func tion is

sim ila r to our previous exam ples, except tha t we’re now using our inner hash table , with the tree a rgum ent as a key .T his m em oiza tion wil l bring us a step c lose r to having la rger, and m ore fun, boards for Dice of Doom .

Note

You use m em oiza tion for optim iz ing the perform ance of code writ ten in the func tiona l style . However, m em oiza tion code is not ,in i tse lf, wri t ten in the func tiona l style . It cannot be , since i t requires you to m ainta in and upda te a table of previous ca l ls to theta rge t func tion.

Page 382: Land of Lisp - Barski M.D., Conrad

Tail Call O ptimization

T he next technique we’re going to use to optim ize our func tiona l program is ca l led tai l call optimization. T o understand thisconcept , le t’s study a sim ple func tion tha t ca lcula tes the length of a l ist :> (defun my-length (lst)

(if lst

(1+ (my-length (cdr lst)))

0))MY-LENGTH> (my-length '(fie foh fum))3

T he my-length func tion should be pre t ty easy for you to understand a t this point . First , i t checks if the l ist is em pty . If

not , i t recursive ly ca l ls i tse lf aga inst the ta i l of the l ist and adds one to the tota l , using the 1+ func tion . If the l ist is em pty,

the func tion just re turns 0 .It turns out tha t this func tion is ac tua lly quite ine ffic ient . W e can easi ly see this by trying to use i t aga inst a rea l ly big l ist :> (defparameter *biglist* (loop for i below 100000 collect 'x))

*BIGLIST*> (my-length *biglist*)*** - Program stack overflow. RESET

Call ing this func tion in CL ISP ac tua lly causes the program to c rash! (Other Com m on L isp com pile rs/ inte rpre te rs m ay do be tte r,depending on whether the com pile r write rs use any spec ia l t ricks to antic ipa te this com m on pitfa l l in L isp code . )

T his happens because of the 1+ func tion. It te l ls L isp, “F irst , figure out the length of the shorte r l ist , then ca l l 1+ on the result . ”T he problem is tha t each t im e we ca ll my-length recursive ly, L isp m ust rem em ber tha t we need to add one to the result la te r

on, once the length of the ta i l of the l ist has been figured out . Since the l ist is 100,000 i tem s long, i t m ust rem em ber this 99,999tim es before i t can perform a single addit ion! T he CL ISP inte rpre te r places a rem inder for a l l of these addit ions on the programstack, which eventua lly overflows, c rashing the program .

So how do we avoid this problem ? W e do i t by rewrit ing our my-length func tion l ike so:> (defun my-length (lst)

(labels ((f (lst acc)

(if lst

(f (cdr lst) (1+ acc))

acc)))

(f lst 0)))MY-LENGTH> (my-length '(fie foh fum))3

Here , we define a loca l func tion f tha t wil l ac t as our l ist-ea te r. T his func tion takes an extra param ete r, often ca l led an

accumulator, he re shortened to acc . T his acc a rgum ent keeps a running count of how m any i tem s in the l ist we have

previously encounte red. W hen we ini t ia l ly ca l l the func tion f, we se t acc to 0 .

By m aking this accum ula tor ava ilable , i t m eans tha t when f ca l ls i tse lf recursive ly , i t now longer needs to add one to the

result . Instead, i t just adds one to the accum ula tor. Once we reach the end of the l ist (lst is nil ), then acc wil l equa l the

tota l num ber of i tem s in the l ist , so we can just re turn i t .W hat is im portant he re is tha t the very last thing the func tion f does, in the case where m ore i tem s a re on the l ist , is ca l l i tse lf

recursive ly . (T he addit iona l l ine in the if sta tem ent doesn’t count, since tha t pa rt won’t be ca l led if the expressioneva lua tes to true . ) W hen a func tion in L isp ca l ls i tse lf (or another func tion) as i ts ve ry last ac t ion, we ca ll this ac t ion a tai l call . Asm art L isp com pile r, when see ing a ta i l ca l l , can then say to i tse lf, “Hey, since I don’t need to do anything m ore a fte r ca l l ing faga in, I can just go stra ight to f, without needing to put the current program context on the stack. ”

T his is ac tua lly sim ila r to perform ing a GOTO in BASIC or a longjmp in C++. In a l l of these cases, we just “ forge t” where wecam e from , which is ve ry fast and doesn’t thrash the stack. However, in the case of a ta i l ca l l in L isp, i t is a lso perfec tly sa fe .Anyone who has used GOTO or longjmp knows they’re anything but sa fe !

Notice tha t the re a re two diffe rent de fini t ions for lst tha t exist in the preceding exam ple code . One is an a rgum ent to the my-

Page 383: Land of Lisp - Barski M.D., Conrad

length func tion, and the other is an a rgum ent to the func tion f . T he va lues of these two lst a rgum ents wil l devia te as theprogram runs and f is ca l led recursive ly. However, within the func tion f, the version in i ts own a rgum ent l ist wil l take precedence .T his process of hiding one variable with another through precedence is ca l led variable shadowing.

Note

I used variable shadowing in the my-length func tion so i t would be im possible for m e to acc identa l ly use the “wrong l ist” whenwrit ing the code inside of func tion f. Other program m ers disl ike this technique , since having sim ila rly nam ed variables withdiffe rent va lues can lead to confusion. You’l l need to dec ide which of these a rgum ents is m ost convinc ing to you and whetheryou’l l use variable shadowing in your own code .

Support for Tail Calls in Common Lisp

Unfortuna te ly, you can’t be 100 percent sure in Com m on L isp tha t a com pile r/ inte rpre te r wil l pe rform ta i l ca l l optim iza tions. Itis not required by the ANSI Com m on L isp standard. (T he si tua tion is ac tua lly diffe rent in the Schem e dia lec t , since Schem e has astric t requirem ent for ta i l ca l l optim iza tion. )

However, m ost Com m on L isp com pile rs support this fea ture , a l though CL ISP requires som e extra ca joling to m ake ta i l ca l loptim iza tion work for som e func tions, inc luding our exam ple func tion. T he reason for this is tha t ta i l ca l ls can ac tua lly lead toperform ance problem s them se lves, in som e esote ric cases. Also, when we debug a program , i t ’s nice to be able to look a t the ful lca l l stack; ta i l ca l l optim iza tions wil l prevent this, since , by the ir na ture , they wil l m inim ize the inform ation ava ilable on thestack.

Here’s the extra step we need to take to ge t CL ISP to ta i l ca l l optim ize the my-length func tion:(compile 'my-length)Call ing this func tion wil l te l l CL ISP to run the my-length func tion through i ts ful l com pile r, which inc ludes a ta i l code

optim iza tion step. Now we can run my-length aga inst our jum bo-sized l ist!> (my-length *biglist*)

100000

Tail Call O ptimization in Dice of Doom

One func tion in our gam e tha t could defini te ly benefi t from ta i l ca l l optim iza tion is the add-new-dice func tion. Here ’s the ful lyoptim ized version:(defun add-new-dice (board player spare-dice)

(labels ((f (lst n acc)(cond ((zerop n) (append (reverse acc) lst))

((null lst) (reverse acc))(t (let ((cur-player (caar lst))(cur-dice (cadar lst)))(if (and (eq cur-player player)(< cur-dice *max-dice*))(f (cdr lst)(1-n)

(cons (list cur-player (1+ cur-dice)) acc))

(f (cdr lst) n (cons (car lst) acc))))))))(board-array (f (coerce board 'list) spare-dice ()))))

As before , we’re perform ing the l ist-ea t ing in a func tion ca lled f , which a lso has an accum ula tor. However, this t im e theacc va riable wil l conta in a l ist of newly upda ted hexagons with extra dice . W e can now ca ll f in ta i l ca l l posi t ions in two places

, where we cons new hexagons to the acc va riable .Once we’ve processed the whole l ist of hexagons on the board, we can just re turn acc. However, since we’ve consed stuff to acc

as we went a long the l ist , acc wil l ac tua lly be reversed. T here fore , we need to perform an extra ca l l to reverse a t the very end

.W e have now explored som e basic techniques for optim iz ing com pute r program s writ ten in the func tiona l style .

Page 384: Land of Lisp - Barski M.D., Conrad

A Sample G ame on the 3-by-3 Board

Now le t’s enjoy the frui ts of our labor. T he following is a ful l gam e aga inst the AI player on a 3-by-3 board. As you can see , onan evenly m atched sta rt ing board, the com pute r is now prac tica l ly unbea table .> (play-vs-computer (game-tree (gen-board) 0 0 t))

current player = ab-1 a-2 a-3a-1 b-1 b-2b-2 a-2 b-3choose your move:1. 1 -> 42. 1 -> 03. 2 -> 54. 7 -> 43current player = ab-1 a-2 a-1a-1 b-1 a-2b-2 a-2 b-3choose your move:1. end turn2. 1 -> 43. 1 -> 04. 5 -> 45. 7 -> 41current player = bb-1 a-3 a-1a-1 b-1 a-2b-2 a-2 b-3current player = bb-1 a-3 a-1b-1 b-1 a-2b-1 a-2 b-3current player = ab-1 a-3 a-1b-1 b-1 a-2b-1 a-2 b-3choose your move:1. 1 -> 42. 1 -> 03. 5 -> 44. 7 -> 45. 7 -> 36. 7 -> 61current player = ab-1 a-1 a-1b-1 a-2 a-2b-1 a-2 b-3choose your move:1. end turn2. 4 -> 03. 4 -> 34. 7 -> 35. 7 -> 61current player = bb-1 a-1 a-1b-1 a-2 a-2b-1 a-2 b-3current player = bb-1 a-1 a-1b-1 a-2 b-2b-1 a-2 b-1current player = ab-2 a-1 a-1b-1 a-2 b-2b-1 a-2 b-1choose your move:1. 4 -> 32. 4 -> 83. 7 -> 34. 7 -> 65. 7 -> 82current player = ab-2 a-1 a-1b-1 a-1 b-2b-1 a-2 a-1choose your move:1. end turn2. 7 -> 33. 7 -> 61current player = b

Page 385: Land of Lisp - Barski M.D., Conrad

b-2 a-1 a-1b-1 a-1 b-2b-1 a-2 a-1current player = bb-1 b-1 a-1b-1 a-1 b-2b-1 a-2 a-1current player = ab-1 b-1 a-1b-1 a-1 b-2b-1 a-2 a-1choose your move:1. 7 -> 32. 7 -> 61current player = ab-1 b-1 a-1a-1 a-1 b-2b-1 a-1 a-1choose your move:1. end turn1current player = bb-1 b-1 a-1a-1 a-1 b-2b-1 a-1 a-1current player = bb-1 b-1 b-1a-1 a-1 b-1b-1 a-1 a-1current player = ab-1 b-1 b-1a-1 a-1 b-1b-1 a-1 a-1The winner is b

Page 386: Land of Lisp - Barski M.D., Conrad

What You've Learned

In this chapte r, we used our knowledge of func tiona l program m ing to deve lop a board gam e with AI. Along the way you lea rnedthe fol lowing:

Func tiona l program m ing techniques a l low you to write a gam e program with a “ rule engine” tha t is separa te from the rest ofthe code . You accom plish this by using func tion pipe lining and building a game tree tha t is independently traversed by other partsof your gam e code as the gam e progresses.

You can c rea te an AI player for a two-player gam e using the minimax algorithm. T his a lgori thm is based on the truism “W hatis good for m y enem y is bad for m e .” It a l lows you to e ffic iently ra te posit ions in a two-player board gam e.

L exica l va riables (which we’ve been ca ll ing local va riables) can l ive on past the form in which they were c rea ted if they a rere fe renced by a lam bda expression. Capturing variables in this way is ca l led creating a c losure .

Func tiona l program s can be optim ized using memoization, which requires you to cache previous results ca lcula ted by afunc tion.

You can a lso im prove func tiona l program s by using tai l call optimizations, which a l low you to m ake sure the ca l l stack isn’tabused. You do this by controll ing which func tion appears in the ta i l ca l l (fina l) posi t ion of your l ist-ea te r func tions.

Page 387: Land of Lisp - Barski M.D., Conrad

Chapter 16. The M agic of Lisp M acros

Macro programming a l lows you to m ess a round inside your L isp com pile r/ inte rpre te r to turn L isp into your own customprogram m ing language . W hen faced with a difficult program m ing cha llenge , m any experienced L ispers wil l fi rst a sk them se lves,“W hat program m ing language could I use to m ake this problem easy to solve?” T hen they’l l use m acros to convert L isp into thatlanguage!

No other program m ing language possesses such a sim ple and com prehensive m acro system . One can even a rgue tha t i t would beim possible to add this fea ture to other program m ing languages, for a sim ple reason: T he L isp languages a re the only ones in whichcom pute r code and program da ta a re m ade out of the sam e “stuff. ” As discussed m any t im es in this book, the fundam enta lstruc tures for storing da ta in L isp a re sym bols, num bers, and l ists, which a re m ade of cons ce l ls. Sim ila rly, the code of a L ispprogram is m ade out of these sam e basic building blocks. As you’l l see in this chapte r, this sym m etry be tween code and da ta inL isp is the m agic tha t m akes the L isp m acro system possible .

Note

You m ay have heard tha t other program m ing languages, such as C++, a lso have a fea ture ca l led m acros. For instance , in the C++language , you would c rea te these using the #define direc t ive . However, these a re not the sam e thing! L isp m acros work in anentire ly diffe rent and fa r m ore sophist ica ted way.

Page 388: Land of Lisp - Barski M.D., Conrad

A Simple Lisp M acro

Som etim es when you’re writ ing a com pute r program , you ge t a fee l ing of dé jà vu. I’m sure you know this fee l ing. You’re typingaway a t your com pute r, and you suddenly rea l ize , “Hey, this is the third t im e this week I’ve writ ten this sam e fragm ent of code!”

Suppose , for exam ple , tha t your program needs a spec ia l add func tion:(defun add (a b)

(let ((x (+ a b)))(format t "The sum is ˜a" x)x))

T his func tion adds toge ther two num bers and prints out the sum on the RE PL as a side e ffec t . You m ight find this func tion use fulin a program during debugging:> (add 2 3)

The sum is 55

T his add func tion seem s stra ightforward, but i ts code has an annoyance : W hy do you need so m any parentheses to dec la re your

variable x ? T he let com m and requires so m any parentheses tha t when you need only a single variable , the code ends uplooking espec ia l ly ludic rous.

T he parentheses required by let a re an exam ple of the kind of v isual noise a program m er m ust dea l with a lm ost every day.However, you can’t just wri te a regula r func tion to hide those parentheses, because the let com m and can do things a regula r L ispfunc tion can’t support . T he let com m and is a spec ial form. It ’s a core part of the language and has spec ia l powers beyond those ofa standard L isp func tion.

Macros le t us ge t rid of the superfluous parentheses. L e t’s c rea te a new m acro nam ed let1:

(defmacro let1 (var val &body body)`(let ((,var ,val)),@body))

As you can see , the defini t ion of a m acro looks sim ila r to the defini t ion of a func tion. However, instead of using defun, we use

defmacro to de fine i t . L ike a func tion, i t has a nam e (in this case , let1) and a rgum ents passed to i t .Once we’ve defined the m acro let1, i t can be used just l ike let, except tha t i t works with fewer parentheses:> (let ((foo (+ 2 3)))

(* foo foo))25> (let1 foo (+ 2 3)(* foo foo))25

Page 389: Land of Lisp - Barski M.D., Conrad

M acro Expansion

Although a m acro defini t ion looks very sim ila r to a func tion defini t ion, a m acro is ac tua lly very diffe rent from a func tion. T ounderstand why, im agine your L isp is ac tua lly a cute l i t t le blob, m erri ly running your L isp program s.

T his blob understands only standard L isp code . If i t were to see our let1 com m and, i t would have no idea wha t to do.

Now im agine tha t we have a m agic wand tha t t ransform s the appearance of our code just be fore L isp ge ts a peek a t i t . In ourexam ple , i t wil l t ransform let1 into a regula r let, so L isp wil l stay happy.

Page 390: Land of Lisp - Barski M.D., Conrad

T his m agic wand is ca l led macro expansion. T his is a spec ia l t ransform ation tha t your code is put through before the core of theL isp inte rpre te r/com pile r ge ts to see i t . T he job of the m acro expander is to find any m acros in your code (such as our le t1 m acro)and to convert them into regula r L isp code .

T his m eans a m acro is run a t a dif ferent t ime than a func tion is run. A regula r L isp func tion runs when you execute a programtha t conta ins the func tion. T his is ca l led runtime . A m acro, on the other hand, runs before the program does, when the program isread and com piled by your L isp environm ent. T his is ca l led macro expansion t ime .

Now tha t we’ve discussed the basic thinking behind L isp m acros, le t’s take a c lose r look a t how let1 was defined.

Page 391: Land of Lisp - Barski M.D., Conrad

H ow M acros Are Transformed

W hen we define a new m acro with the defmacro com m and, we’re basica l ly teaching the L isp m acro expansion system a newtransform ation tha t i t can use to transla te code before running a program . T he m acro rece ives raw source code in i ts a rgum ents, inthe form of L isp expressions. Its job is to he lp the m acro expander transform this raw code into standard L isp code tha t keeps theL isp blob happy.

L e t’s take a c lose r look a t how our let1 m acro ge ts t ransform ed. Here is i ts de fini t ion once aga in:

(defmacro let1 (var val &body body)`(let ((,var ,val)),@body))

T he first l ine of this defmacro ca l l te l ls the m acro expander, “Hey, i f you see a form in code tha t begins with le t1, he re ’swhat you need to do to transform i t into standard L isp. ” A m acro defined with defmacro m ay a lso have a rgum ents passed into i t ,which wil l conta in the raw source code found inside the m acro when the m acro is used. T he let1 m acro has three such a rgum ents

passed into i t : var, val, and body . So what do these three a rgum ents represent?

As you can see , when we use let1, we’l l end up having three diffe rent expressions inside i t , which a re the a rgum ents to the let1m acro:var

T he first a rgum ent is the nam e of the variable we’re defining. T his nam e wil l be ava ilable within our m acro using theargum ent nam ed var. In this exam ple , i t wil l equa l the sym bol foo.

valT he second expression holds the code tha t de te rm ines the va lue of the variable . In our m acro, this is the second a rgum ent,

val. It wil l equa l the l ist (+ 2 3).body

T he third expression inside a let1 ca l l is the body code , which m akes use of the new variable tha t’s c rea ted (in this case ,foo). It wil l be ava ilable in the m acro through the a rgum ent nam ed body.

Since the let com m and is a l lowed to have m ult iple sta tem ents in i ts body, we wil l want to m irror this behavior in the let1

Page 392: Land of Lisp - Barski M.D., Conrad

m acro. T his is why, in the defmacro com m and defining le t1, the fina l body a rgum ent has the spec ia l keyword &body in front of i t .T his te l ls the m acro expander “Give m e a l l rem aining expressions in the m acro in a l ist . ” Because of this, the body a rgum ent inour let1 exam ple is ac tua lly ((* foo foo))—a nested l ist . In this exam ple , we put only a single sta tem ent inside let1.

Now tha t you’ve seen what the va lues to the a rgum ents of our let1 m acro a re , le t’s see how the m acro uses this inform ation totransform the let1 into a standard let tha t the L isp com pile r can understand. T he easiest way to transform source code in L isp isto use backquote syntax. (If you don’t rem em ber how to use backquotes, please see How Quasiquoting W orks in How QuasiquotingW orks. ) W ith backquotes, we can build the code for a proper let com m and using code passed to let1. Here ’s our let1 m acroaga in for re fe rence :(defmacro let1 (var val &body body)

`(let ((,var ,val))

,@body))

As you can see , the let1 m acro re turns a backquoted l ist sta rt ing with the sym bol let , fol lowed by the variable nam e andva lue , placed in a proper nested l ist , which L isp’s let com m and requires. T he com m as cause the ac tua l va riable nam e and va lueto be plopped in a t these loca tions. Fina lly, we place the body code from the let1 in the ana logous place in the let com m and

.T he body a rgum ent is inse rted into the transform ed code using the splic ing com m a (,@). T o understand why the body needs to be

handled in this spec ia l way, consider the fol lowing use of our let1 m acro:> (let1 foo (+ 2 3)

(princ "Lisp is awesome!")(* foo foo))Lisp is awesome!25

In this case , we’ve put m ore than one thing inside the body of our let. Rem em ber tha t the let com m and inc ludes an im plic i tprogn com m and, and i t can have m ult iple L isp instruc tions inside . Our new let1 m acro a l lows for this as well by plac ing thespec ia l &body m arker in front of the body a rgum ent, causing a l l rem aining syntax expressions to be passed into let1 a s a l ist . So, inthe preceding exam ple , the body a rgum ent conta ins the code ((princ "Lisp is awesome!") (* foo foo)).

Page 393: Land of Lisp - Barski M.D., Conrad

Using the Simple M acro

Now tha t we’ve writ ten our let1 m acro, le t’s rewrite our custom add func tion in a c leaner way:(defun add (a b)

(let1 x (+ a b)(format t "The sum is ˜a" x)x))

Isn’t this m uch easie r on the eyes?W e can use the macroexpand com m and to see code genera ted by a m acro. Sim ply pass the m acro’s code to macroexpand, l ike

this:> (macroexpand '(let1 foo (+ 2 3)

(* foo foo)))

(LET ((FOO (+ 2 3))) (* FOO FOO)) ;

T

You can now see the raw code genera ted by let1 . T he T a t the end just m eans macroexpand was handed a va lidm acro tha t i t was able to expand.

As your m acros becom e m ore com plex, you’l l find tha t macroexpand is a va luable tool in test ing and debugging the ir struc ture .

Page 394: Land of Lisp - Barski M.D., Conrad

M ore Complex M acros

L et’s suppose you need a custom my-length com m and. T his is a c lassic l ist-ea t ing func tion tha t wil l count the length of a l ist .W e’ll wri te i t in the proper “ ta i l ca l l optim ized” style (discussed in Chapte r 14), where the recursive func tion ca ll is in the ta i lposi t ion. Here ’s the code :(defun my-length (lst)

(labels ((f (lst acc)

(if lst

(f (cdr lst) (1+ acc))acc)))(f lst 0)))

As you can see , this func tion has tons of repe ti t ive stuff, once aga in giving us tha t dreaded fee l ing of dé jà vu. T here a re tworepe ti t ive pa tte rns in this func tion:

As in other l ist-ea te r func tions, we have the annoying check to see if the l ist is em pty and the assoc ia ted use of cdr

.

W e did a l l this ve rbose work to c rea te a loca l func tion f .L e t’s write som e m acros tha t m ake this func tion (and other func tions with the sam e repe ti t ion) m ore pithy.

Page 395: Land of Lisp - Barski M.D., Conrad

A M acro for Splitting Lists

First , le t’s c rea te a split m acro. It wil l le t us write c leaner l ist-ea te r func tions, such as our my-length func tion.L ist-ea te rs a lways check if the l ist is em pty. If i t isn’t , they take apart the l ist using car and/or cdr, and then perform opera t ions

on the head and/or ta i l of the l ist . T he split m acro does this for us. Here ’s wha t i t looks l ike when we use the finished splitm acro:

> (split '(2 3)

(format t "This can be split into ˜a and ˜a." head tail)(format t "This cannot be split."))This can be split into 2 and (3).

> (split '()(format t "This can be split into ˜a and ˜a." head tail)

(format t "This cannot be split."))This cannot be split.

T he first a rgum ent of the split m acro is a l ist you want to spli t into a head and a ta i l . If this is possible , the next

expression in the split m acro wil l be ca l led . As a bonus, our split m acro autom atica l ly c rea tes two variables for us, nam ed

head and tail. T his way, we don’t a lways need to ca l l car and cdr inside l ist-ea t ing func tions. If the l ist is em pty , we ca ll

the expression a t the end .L e t’s look a t the code for the split m acro. Note tha t this ini t ia l ve rsion of the m acro conta ins som e bugs we’l l discuss short ly:;Warning! Contains Bugs!

(defmacro split (val yes no)

`(if ,val

(let ((head (car ,val))

(tail (cdr ,val)))

,yes)

,no))

Our split m acro requires three (and only three ) expressions as a rgum ents . T his m eans when we use this m acro, we’l la lways need exac tly three i tem s.

T he code tha t needs to be genera ted by split is pre t ty stra ightforward. First , we have an if tha t checks if the l ist is em pty

. If i t is, we break apart the l ist and st ick i t into our two loca l va riables, head and tail . T hen we put in the code tha t

handles the “yes, we can spli t the l ist” case . If we can’t spl i t the l ist , we ca ll the no case . Note tha t in the no case ,we don’t have access to the head/tail va riables, since they a ren’t c rea ted if the l ist can’t be spli t .

W ith this new split m acro, we can c lean up our my-length m acro a bi t :(defun my-length (lst)

(labels ((f (lst acc)(split lst

(f tail (1+ acc))acc)))(f lst 0)))

Notice how we now m ake use of the tail va riable c rea ted by split, sim plifying our code . Macros tha t autom atica l lygenera te variables l ike this a re ca l led anaphoric macros.

However, we a re not ye t finished with our split m acro. Although i t basica l ly works, i t conta ins som e subtle bugs tha t we need toaddress.

Page 396: Land of Lisp - Barski M.D., Conrad

Avoiding Repeated Execution in M acros

One com m on bug tha t can happen in a m acro is incorrec t repea ted execution of code . In fac t , our current ve rsion of the splitm acro conta ins this flaw. Here is an exam ple tha t c lea rly shows the problem :

> (split (progn (princ "Lisp rocks!")'(2 3))(format t "This can be split into ˜a and ˜a." head tail)(format t "This cannot be split."))Lisp rocks!Lisp rocks!Lisp rocks!This can be split into 2 and (3).

In this use of split, the sta tem ent “L isp rocks!” was printed three t im es, even though i t appears only once in the origina l code .How is this possible?

Rem em ber tha t the a rgum ents passed into a m acro consist of raw source code . T his m eans the val a rgum ent passed into split

conta ins the raw code of the progn sta tem ent , inc luding the raw code for the princ sta tem ent within i t . Since we re fe renceval three t im es inside the split m acro, i t causes the princ sta tem ent to be executed three t im es.

W e can verify this by running this exam ple through macroexpand:> (macroexpand '(split (progn (princ "Lisp rocks!")

'(2 3))(format t "This can be split into ˜a and ˜a." head tail)(format t "This cannot be split.")))

(IF (PROGN (PRINC "Lisp rocks!") '(2 3))(LET

((HEAD (CAR (PROGN (PRINC "Lisp rocks!") '(2 3))))

(TAIL (CDR (PROGN (PRINC "Lisp rocks!") '(2 3)))))(FORMAT T "This can be split into ˜a and ˜a." HEAD TAIL))(FORMAT T "This cannot be split.")) ;T

As you can see , the princ sta tem ent appears three t im es . T his causes unexpec ted behavior and is ine ffic ient ,since we’re repea tedly running the sam e code unnecessa ri ly.

If you give this problem som e thought, the solution isn’t too hard to figure out . W e sim ply need to c rea te a loca l va riable insidethe split m acro, l ike this:;Warning! Still contains a bug!

(defmacro split (val yes no)`(let1 x ,val(if x(let ((head (car x))(tail (cdr x))),yes),no)))

Note tha t we m ade use of let1 in this new version of split. As this shows, i t is pe rfec tly okay to use m acros inside other m acros.Now if we re run our previous exam ple , we can see tha t split behaves correc tly, princing the sta tem ent only once :> (split (progn (princ "Lisp rocks!")

'(2 3))(format t "This can be split into ˜a and ˜a." head tail)(format t "This cannot be split."))Lisp rocks!This can be split into 2 and (3).

Unfortuna te ly, this new version of the split m acro introduces ye t another bug. L e t’s tackle this new bug next .

Page 397: Land of Lisp - Barski M.D., Conrad

Avoiding Variable Capture

T o see the bug in our newest ve rsion of split, t ry running the fol lowing:

> (let1 × 100(split '(2 3)(+ x head)nil))*** - +: (2 3) is not a number

Can you te l l wha t happened? W e just c rea ted a variable x inside the new version of our split m acro! Here ’s wha t the ca l l tosplit looks l ike if we macroexpand i t :> (macroexpand '(split '(2 3)

(+ x head)nil))

(LET ((X '(2 3)))(IF X (LET ((HEAD (CAR X)) (TAIL (CDR X))) (+ X HEAD)) NIL)) ;T

Notice how the expanded version of split conta ins a defini t ion of x . T his blocks the com peting defini t ion in our

troublesom e exam ple . In this scenario, the split m acro acc identa l ly captured the variable x and overwrote i t in anunexpec ted way. How can we avoid this problem ?

One sim ple solution would be to not c rea te a va riable x in the m acro, but to instead use a variable with som e insane long nam elike xqweopfjsadlkjgh. T hen we could fee l pre t ty confident the variable used inside the m acro wil l never c lash with a variableinside the code tha t uses i t . If fac t , the re is a Com m on L isp func tion ca lled gensym whose job i t is to genera te c razy variablenam es exac tly for this purpose :> (gensym)

#:G8695T he gensym func tion wil l c rea te a unique variable nam e for you tha t is guaranteed never to c lash with any other variable nam e

in your code . You m ay notice tha t i t a lso has a spec ia l pre fix ( #: ) tha t diffe rentia tes i t from other nam es. Com m on L isp handlesthese gensym-based nam es as a spec ia l case and wil l stop you from using the nam e of a gensym va riable direc t ly.

Now le t’s use the gensym func tion inside our split m acro to protec t the m acro from causing variable capture :;This function is finally safe to use

(defmacro split (val yes no)

(let1 g (gensym)

`(let1 ,g ,val(if ,g(let ((head (car ,g))(tail (cdr ,g))),yes),no))))

In the first l ine of our revised m acro, we define a variable g tha t conta ins the gensym nam e . It ’s ve ry im portant to noticetha t the re is not a backquote a t the front of this l ine . T his m eans tha t this l ine of code is run a t macro expand t ime , not runtime ,and i t is pe rfec tly fine to define the variable g a t this point . T he let1 on the next l ine , however, has a backquote in front of i t

. T his l ine wil l be run a t runtim e , so we don’t want to use a hardcoded variable in this spot . In this new version, we insteaduse the unique gensym nam e stored in g.

Now every t im e the split m acro is used, a unique nam e is genera ted to hold the inte rna l va lue . W e can test this by runningsom e exam ples through macroexpand:> (macroexpand '(split '(2 3)

(+ x head)nil))

(LET (( #:G8627 '(2 3))) (IF #:G8627 (LET ((HEAD (CAR #:G8627))(TAIL (CDR #:G8627))) (+ X HEAD)) NIL)) ;T> (macroexpand '(split '(2 3)(+ x head)nil))

(LET (( #:G8628 '(2 3))) (IF #:G8628 (LET ((HEAD (CAR #:G8628))(TAIL (CDR #:G8628))) (+ X HEAD)) NIL)) ;T

Notice how a diffe rently nam ed loca l va riable was c rea ted in both instances . T his guarantees tha t the variable nam ewill not only be unique within your code , but wil l a lso be unique if the split m acro is ever used m ult iple t im es in a nestedfashion. W e have now crea ted a ful ly debugged version of our split m acro.

Just because i t is now bug-free does not m ean tha t i t is free of va riable capture . Note tha t the m acro st i l l de fines the variableshead and tail. If you used this func tion in other code in which head or ta i l had an a l te rna te m eaning, your code would fa i l!However, in the case of head and tail, the capture is on purpose . In this si tua t ion, the variable capture is a feature , not a bug—itis an anaphoric m acro. As we’ve discussed, this m eans tha t i t m akes nam ed variables or func tions ava ilable tha t we can use in the

Page 398: Land of Lisp - Barski M.D., Conrad

body of the m acro.

Page 399: Land of Lisp - Barski M.D., Conrad

A Recursion M acro

L et’s take another look a t our im proved my-length m acro:(defun my-length (lst)

(labels ((f (lst acc)(split lst(f tail (1+ acc))acc)))(f lst 0)))

As we discussed, the re is an addit iona l repe ti t ive pa tte rn in this code : T he c rea tion of a loca l func tion f. L e t’s write anotherm acro tha t ge ts rid of this addit iona l visua l noise : recurse. Here ’s an exam ple of the recurse m acro in use :

> (recurse (n 9)

(fresh-line)

(if (zerop n)

(princ "lift-off!")

(progn (princ n)

(self (1-n)))))987654321lift-off!

T he first pa ram ete r into the recurse m acro is a l ist of va riables and the ir sta rt ing va lues . In this case , we’re dec la ringonly one variable (n) and se t t ing i ts sta rt ing va lue to 9. T he rest of the l ines in the m acro m ake up the body of the recursivefunc tion.

T he first thing we do in the body is sta rt a fresh l ine . T hen we check if n has reached ze ro ye t . If i t has, we print

“ l ift-off!” . Otherwise , we print the current num ber and ca ll the func tion aga in, recursive ly. L ike our split m acro, therecurse m acro is anaphoric . In the case of recurse, i t m akes a func tion nam ed self ava ilable , which we ca ll when we’re ready to

perform a recursion . W e a lso subtrac t one from n a t this point to lower the countdown num ber.Now tha t we’ve seen how recurse should work, le t’s write this recurse m acro. In order to process the l ist of a rgum ents and

sta rt ing va lues, i t ’s use ful for us to have a func tion tha t can group i tem s into a l ist of pa irs. Here is a func tion, pairs, tha taccom plishes this:> (defun pairs (lst)

(labels ((f (lst acc)

(split lst

(if tail

(f (cdr tail) (cons (cons head (car tail)) acc))

(reverse acc))

(reverse acc))))(f lst nil)))PAIRS> (pairs '(a b c d e f))((A . B) (C . D) (E . F))

T he pairs func tion is a ta i l-ca l l-optim ized l ist-ea te r, which, i ronica lly, has i ts own loca l func tion f . (Short ly, we won’t

need to dec la re such a func tion anym ore . ) It uses split to break an i tem off the l ist . However, since i t needs to process two

item s (a pa ir) from the l ist a t once , we need to run an addit iona l check to see if the ta i l is em pty . If the re a re no i tem s in

the l ist (or only one i tem le ft ), we re turn our accum ula ted va lues. Otherwise , we recursive ly process the rest of the

Page 400: Land of Lisp - Barski M.D., Conrad

l ist , with a new pa ir of i tem s placed into the accum ula tor .Now we’re fina lly ready to write the recurse m acro:(defmacro recurse (vars &body body)

(let1 p (pairs vars)

`(labels ((self ,(mapcar #'car p),@body))

(self ,@(mapcar #'cdr p)))))As you can see , i t sim ply transform s the recursion into a tradit iona l loca l func tion. First , i t uses our new pairs func tion to take

apart the variable nam es and sta rt ing va lues, and puts the result into p . T hen i t de fines a loca l func tion sim ply nam ed self.

T he variable nam es for self a re the odd-num bered i tem s from p . Since we want self to be accessible , anaphorica l ly, frominside the m acro, we use a pla in nam e instead of a gensym nam e for this func tion. At the bottom of the m acro, we then sim ply ca l l

self, passing in a l l the sta rt ing va lues .Now tha t we’ve c rea ted the recurse m acro, le t’s once aga in c lean up our my-length func tion using this new language construc t:(defun my-length (lst)

(recurse (lst lstacc 0)(split lst(f tail (1+ acc))acc)))

As you can see , the re is ve ry l i t t le repe ti t ion or visua l noise in this ve rsion of our my-length func tion.Now you can apprec ia te how he lpful m acros can be when trying to write c lean, succ inc t code . However, a l ibe ra l use of m acros

will a lso require you to bear som e costs tha t you need to be aware of. W e’ll look a t the potentia l downsides to m acros next .

Page 401: Land of Lisp - Barski M.D., Conrad

M acros: Dangers and Alternatives

Macros a l low us to write code tha t genera tes other code , m aking the L isp languages a wonderful tool for m etaprogram m ing andprototyping new language ideas. But, a t som e leve l , m acros a re just a sle ight of hand: T hey le t you trick the L ispcom pile r/ inte rpre te r into accepting your own custom ized language construc ts and trea t ing them like standard L isp. T hey a re indeeda powerful tool in a program m er’s tool chest , but they a re not as e legant as som e of the other program m ing tools you’veencounte red in this book.

T he m ain drawback of m acros is tha t they can m ake i t ha rd for other program m ers to understand your code . Afte r a l l , i f you’rec rea ting your own language dia lec t , other program m ers won’t be fam ilia r with i t . E ven your future se lf—say, in a year or two—m ay have a hard t im e understanding the struc ture of your code if you’ve m ade heavy use of m acros. Because of this, experiencedL ispers wil l do the ir best to use a l te rna te techniques to m acro program m ing whenever possible . Often, a beginning L isper wil l wri tea m acro in si tua tions tha t could be addressed in other, c leaner ways.

For instance , i t ’s fun to see how we were able to c lean up our my-length func tion by adding a couple of m acros nam ed splitand recurse. However, in the previous two chapte rs, you lea rned about another tool , func tiona l program m ing, which can a lso beused to c lean up l ist-ea te r func tions. One powerful func tion often used by func tiona l program m ers is reduce. It is a higher-orderfunc tion tha t accepts a func tion and a l ist , and wil l ca l l the func tion once for every va lue in the l ist . Here is the my-lengthfunc tion rewrit ten to use the powerful reduce func tion, ra ther than m acros:(defun my-length (lst)

(reduce (lambda (x i)

(1+ x))

lst

:initial-value 0))As you can see , this new version of my-length easi ly blows away our previous version. It is shorte r, and i t doesn’t re ly on any of

the nonstandard m acros tha t we c rea ted.

T he first a rgum ent to reduce holds our reduc tion func tion . Its job is to keep track of, and upda te , an accum ula ted va lue ,here nam ed x. T his va riable x wil l hold the current accum ula ted va lue , which in this case wil l be the length of the l ist so fa r. T his

m eans we can sim ply add one to x to upda te i t to i ts new va lue . Since the reduc tion func tion wil l be ca l led once for everyitem in the l ist , i t wil l , in the end, genera te the length of the l ist . (T he reduc tion func tion a lso rece ives, a s an a rgum ent, thecurrent i tem in the l ist , he re given as the variable i. However, we do not need i t for ca lcula t ing the l ist’s length. ) T he next i tem

passed to reduce is the l ist we want to reduce . Fina lly, since the accum ula ted length we’re ca lcula t ing should have an

init ia l va lue of ze ro, we indica te this by se t t ing the :initial-value keyword a rgum ent to ze ro .Clearly, the re a re other scenarios where the l ist-ea te r m acros we’ve c rea ted in this chapte r a re st i l l use ful . T here a re m any cases

where the reduce func tion could not be so easi ly used. So in the end, the re a re st i l l m any si tua tions where c rea ting your own L ispdia lec t is exac tly the right solution to a problem , as you’l l see in the next chapte r.

Page 402: Land of Lisp - Barski M.D., Conrad

What You've Learned

T his chapte r covered m acro program m ing. You’ve lea rned the fol lowing:Macros le t you write code tha t writes code . W ith m acros, you can c rea te your own program m ing language and convert i t to

standard L isp just be fore the com pile r can ge t a peek a t i t .Macros a l low you to ge t rid of tha t fee l ing of dé jà vu when writ ing your code , in si tua tions when nothing e lse can do so.You m ust be ca re ful when writ ing m acros so tha t they don’t lead to unintentiona l , repea ted execution of code .You need to be ca re ful to avoid unintended variable capture in m acros. You can avoid this by using gensym nam es.If va riables c rea ted by a m acro a re exposed on purpose , as a fea ture of the m acro, the m acro is ca l led an anaphoric macro.Macro program m ing is a ve ry powerful technique . However, t ry to use func tiona l program m ing instead to solve a problem

whenever possible . Macros should a lways be a last resort .

Page 403: Land of Lisp - Barski M.D., Conrad

Chapter 17. Domain-Spec ific Languages

One of the best reasons for using m acros is to perform domain-spec if ic language (DSL) program m ing. DSL program m ing is anadvanced m acro program m ing technique tha t a l lows us to solve difficult program m ing problem s by drast ica l ly changing thestruc ture and appearance of L isp code to optim ize i t for a spec ia l ized purpose . Although m acros a re not stric t ly necessa ry for doingDSL program m ing, by writ ing a se t of m acros, you can easi ly c rea te a DSL in L isp.

Page 404: Land of Lisp - Barski M.D., Conrad

What Is a Domain?

According to the 2000 US Census, the average fam ily in the United Sta tes had 1.86 children. Since no individua l fam ily hasexac tly 1.86 children, i t is obvious tha t no part icula r fam ily is t ruly perfec tly average . In the sam e way, the re is no such thing asan average com pute r program . E very program is designed to solve a spec ific problem , and every a rea of hum an inquiry, or domain,has i ts own idiosyncra t ic requirem ents tha t influence program s tha t solve problem s in the given a rea . W ith DSL s, we enhance thecore of our program m ing language to take these dom ain-spec ific requirem ents into account, potentia l ly m aking our result ing codeeasie r to write and understand.

L e t’s take a look a t som e spec ific dom ains and c rea te som e DSL s tha t le t us easi ly work within these dom ains using L isp. In thischapte r, we’l l c rea te two diffe rent DSL s. First , we’l l c rea te a DSL for writ ing scalable vec tor graphics (SV G) fi le s. T hen we’l lwrite a DSL for c rea ting com m ands in a text adventure—we’re fina lly going to upgrade our W izard’s Adventure Gam e fromChapte r 5 and Chapte r 6 to m ake i t ful ly playable !

Page 405: Land of Lisp - Barski M.D., Conrad

Writing SVG F iles

T he SVG form at is a fi le form at for drawing graphics. In this form at, you spec ify objec ts l ike c irc les and polygons, and then passthem to a com patible com pute r program to view. Because the SVG form at spec ifies a drawing using pure m ath func tions instead ofraw pixe ls, i t is easy for a program to render an SVG im age a t any size , m aking im ages in this form at easi ly sca lable .

T he SVG form at is currently rece iving a lot of a t tention from web deve lopers. All m odern browsers (exc luding Microsoft Inte rne tE xplore r) support SVG na tive ly. Recently, Google re leased a se t of l ibra ries ca l led SVG W eb tha t adds decent support for SVG,even in Inte rne t E xplore r. T his a l lows SVG to work in m ore than 90 percent of current web browsers. Fina lly, SVG has becom e aprac tica l and e ffic ient option for drawing graphics on websites.

T he SVG form at is buil t on top of the XML form at. Here is an exam ple of wha t a com ple te SVG fi le looks l ike :<svg xmlns="http://www.w3.org/2000/svg">

<circle cx="50"cy="50"r="50"style="fill:rgb(255,0,0);stroke:rgb(155,0,0)"></circle><circle cx="100"cy="100"r="50"style="fill:rgb(0,0,255);stroke:rgb(0,0,155)"></circle></svg>

Sim ply copy this text and place i t in a fi le nam ed example . svg (or download this fi le from http: / / landofl isp. com /). T hen you canopen the fi le from the Fire fox web browser (the Safa ri , Chrom e, and Opera web browsers should a lso work).

Here is wha t you should see , with a red and blue c irc le :

Page 406: Land of Lisp - Barski M.D., Conrad

Now, le t’s write som e m acros and func tions to le t us c rea te a pic ture l ike this direc t ly in Com m on L isp!

Page 407: Land of Lisp - Barski M.D., Conrad

Creating XM L and H TM L with the tag M acro

T he XML da ta form at (just l ike the HT ML da ta form at) consists prim ari ly of nested tags:

<mytag><inner_tag></inner_tag>

</mytag>

E very tag a lso has a m atching c losing tag . T he c losing tag has the sam e nam e, but with a slash preceding i t .Addit iona lly, tags m ay conta in a t tributes:<mytag color="BLUE" height="9"></mytag>In this exam ple , we c rea te a tag nam ed mytag tha t has the a t tribute of be ing blue and has a he ight of 9.

Writing a M acro H elper F unction

Often, when writ ing a m acro to perform a task, you’l l find a lot of wha t your m acro needs to do can be handled by a func tioninstead. Because of this, i t is often prudent to first wri te a he lper func tion tha t does m ost of wha t the m acro needs to do. T hen youwrite the m acro, keeping i t a s sim ple as possible by leveraging the he lper func tion. T his is wha t we’re going to do as we write am acro to c rea te XML -style tags in L isp.

Here is our he lper func tion, ca l led print-tag, which prints a single opening (or c losing) tag:(defun print-tag (name alst closingp)

(princ #\<)

(when closingp(princ #\/))

(princ (string-downcase name))

(mapc (lambda (att)

(format t " ˜a=\"˜a\"" (string-downcase (car att)) (cdr att)))alst)

(princ #\>))

First , the print-tag func tion prints an opening angle bracke t . Since this is only a charac te r, we use the l i te ra l charac te r

syntax by pre fixing the bracke t with #\ . T hen we check the predica te closingp . If i t is t rue , the tag needs to have a slash infront of i t to m ake i t a c losing tag. T hen we print the nam e of the tag, converted to lowercase with the string-downcase func tion

. Next, we i te ra te through a l l the a t tributes in the alst of a t tributes and print out each a t tribute /va lue pa ir .

Fina lly, we end by putt ing in a c losing angle bracke t .T he following is an exam ple use of the print-tag func tion. Since i t is a pla in func tion and not a m acro, i t ’s easy to debug in

the RE PL . T his is another reason why he lper func tions a re a good idea when c rea ting m acros.> (print-tag 'mytag '((color . blue) (height . 9)) nil)

<mytag color="BLUE" height="9">As you can see , this func tion does a fine job of print ing an XML tag. However, i t would be a rea l chore if a l l tags had to be

crea ted in this way. T ha t’s why we’re going to write the tag m acro next .

Creating the tag M acro

T he tag m acro we’l l c rea te has been adopted from the m acro of the sam e nam e in Paul Graham ’s Arc L isp dia lec t . It im proveson the print-tag func tion in severa l c ruc ia l ways, a l l of which could not be rem edied without having a m acro:

T ags a lways com e in pa irs. However, i f we want to nest tags, a func tion would not be able to print tags tha tsurround the tags printed inside i t . T his is because i t requires us to execute code before and a fte r nested tags a reeva lua ted. T his is possible in a m acro, but not a func tion.

T ag nam es and a t tribute nam es usua lly do not need to change in a dynam ic way. Because of this, i t ’s redundantto need to pre fix tag nam es with a single quote . In other words, tag nam es should by default be trea ted as if theywere in da ta m ode .

Unlike tag nam es, i t ’s ve ry desirable for the va lues of a t tributes to be dynam ica lly genera ted. Our m acro wil lhave a syntax tha t places the a t tribute va lues into code m ode so we can execute L isp code to popula te these va lues.

Idea lly, this is how we would l ike the tag m acro to work, when we use i t in the RE PL :> (tag mytag (color 'blue height (+ 4 5)))

<mytag color="BLUE" height="9"></mytag>Notice tha t the tag nam e and a t tribute l ist no longer need quotes in front of them . Addit iona lly, i t is now easy to ca lcula te an

a ttribute dynam ica lly with L isp code . In this case , we’re ca lcula t ing tha t the he ight is 4 plus 5.Here’s the m acro tha t accom plishes this ta sk:

Page 408: Land of Lisp - Barski M.D., Conrad

(defmacro tag (name atts &body body)

`(progn (print-tag ',name

(list ,@(mapcar (lambda (x)

`(cons ',(car x) ,(cdr x)))

(pairs atts)))nil)

,@body

(print-tag ',name nil t)))

As you would expec t , the m acro first ca l ls print-tag to genera te the opening tag . T his is a bi t t ricky when we genera tethe a l ist of a t tributes for print-tag, since we want the va lues for the a t tributes to be in code m ode . W e accom plish this by

wrapping the a t tributes using list . T hen we mapcar through the a t tributes, which we’ve pa ired with the pairs func tion . (Rem em ber tha t we c rea ted the pairs func tion toward the end of the previous chapte r. ) For each a t tribute pa ir, we genera te acode fragm ent in the l ist tha t consists of cons, without a quota t ion m ark in front of the va lue of the a t tribute , so tha t we can

dynam ica lly ca lcula te i t .

Next, we put a l l the code nested inside our tag m acro, so tha t i t is ca l led a fte r the opening tag . Fina lly we c rea te a

c losing tag .T o m ake m ore sense of how this m acro handles the a t tribute l ist , le t’s pass the output from our exam ple to macroexpand:> (macroexpand '(tag mytag (color 'blue height (+ 4 5))))

(PROGN (PRINT-TAG 'MYTAG

(LIST (CONS 'COLOR 'BLUE)

(CONS 'HEIGHT (+ 4 5)))NIL)(PRINT-TAG 'MYTAG NIL T)) ;T

L ooking a t the m acro expansion, i t should be c lea r how the tag m acro builds the a t tribute l ist to pass to print-tag and

how it a l lows us to dynam ica lly genera te a t tribute va lues, such as the he ight a t tribute .Here is another exam ple of this m acro in use , now with two inner tags:> (tag mytag (color 'blue size 'big)

(tag first_inner_tag ())(tag second_inner_tag ()))

<mytag color="BLUE" size="BIG"><first_inner_tag></first_inner_tag><second_inner_tag></second_inner_tag></mytag>

Notice how i t correc tly surrounds the inner, nested tags with proper XML opening and c losing tags. Note a lso tha t I have added

line breaks and indenta t ion to the output for c la ri ty. T he ac tua l output of the tag func tion a lways prints on a single l ine ,without l ine breaks or indenta t ion.

Using the tag M acro to G enerate H TM L

T he tag m acro can be used for genera t ing XML or HT ML . For instance , we could do the fol lowing to genera te a “Hello W orld”HT ML docum ent:> (tag html ()

(tag body ()(princ "Hello World!")))<html><body>Hello World!</body></html>

Since HT ML uses predefined tags (unlike XML , where the tags can have any nam e), we could write sim ple m acros for spec ificHT ML tags tha t m ake them even easie r to write HT ML in L isp. For instance , he re a re som e sim ple html and body m acros:(defmacro html (&body body)

`(tag html (),@body))(defmacro body (&body body)`(tag body (),@body))

Now we could write our “Hello W orld” HT ML exam ple even m ore e legantly:> (html

Page 409: Land of Lisp - Barski M.D., Conrad

(body(princ "Hello World!")))<html><body>Hello World!</body></html>

However, we want to use the tag m acro to c rea te SVG drawings instead. So le t’s expand our DSL for the SVG dom ain.

Page 410: Land of Lisp - Barski M.D., Conrad

Creating SVG -Spec ific M acros and F unctions

First , le t’s write the svg m acro, which em bodies an entire SVG im age . Here i t is:(defmacro svg (&body body)

`(tag svg (xmlns "http://www.w3.org/2000/svg"

"xmlns:xlink" "http://www.w3.org/1999/xlink"),@body))

T he svg m acro is buil t on top of the tag m acro. SVG im ages, for our purposes, require two spec ia l a t tributes to be c rea ted:

T he xmlns a t tribute te l ls the SVG viewer (in our case , the Fire fox web browser) where i t can find the proper

docum enta t ion for the SVG form at .

T he second a t tribute enables hyperl inks inside the pic ture . W e’ll be using this hyperl inking fea ture in m oreadvanced exam ples, sta rt ing in the next chapte r.

T o draw pic tures, we’l l need to m anipula te colors. T o keep things sim ple , we’re just going to represent colors as RGB triplesstored in a l ist . For instance , the color (255 0 0) is bright red.

Often, i t is use ful to genera te l ighte r or da rker variants of a pa rt icula r color. T he following brightness func tion does this for us:(defun brightness (col amt)

(mapcar (lambda (x)(min 255 (max 0 (+ x amt))))col))

If you pass bright red into this func tion and se t the brightness to nega tive 100, you can see tha t i t wil l genera te a da rker red:> (brightness '(255 0 0) −100)

(155 0 0)Next, le t’s c rea te a func tion tha t se ts the style of an SVG pic ture e lem ent:(defun svg-style (color)

(format nil

"˜{fill:rgb(˜a,˜a,˜a);stroke:rgb(˜a,˜a,˜a)˜}"(append color

(brightness color −100))))

T he svg-style func tion accepts a color, and then se ts the fi l l and stroke (outl ine ) of a pic ture e lem ent . By using our

brightness func tion, we can m ake the outl ine a darker variant of the fi l l . T his way, we need to spec ify only a single color forevery e lem ent in our pic tures, while m ainta ining a pleasing appearance .

Now, le t’s c rea te a func tion to draw a c irc le . Since we won’t need to nest other SVG tags inside a c irc le , the re is no need towrite a m acro for drawing c irc les—a func tion suffices.(defun circle (center radius color)

(tag circle (cx (car center)

cy (cdr center)

r radius

style (svg-style color))))

W e’ll want to se t the cente r, radius, and color of each c irc le . T he cente r needs to be assigned to the cx and cy SVG

attributes of the c irc le . T he radius is put in the r a t tribute . W e se t the style of our c irc le with our svg-style func tion .

W e a re now ready to draw the sim ple SVG pic ture of two c irc les shown earl ie r, using our new DSL ! Here’s how we do i t :> (svg (circle '(50 . 50) 50 '(255 0 0))

(circle '(100 . 100) 50 '(0 0 255)))<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><circle cx="50" cy="50" r="50"style="fill:rgb(255,0,0);stroke:rgb(155,0,0)"></circle><circle cx="100"cy="100" r="50" style="fill:rgb(0,0,255);stroke:rgb(0,0,155)"></circle></svg>

W e now have a func tiona l SVG DSL . L e t’s add som e m ore func tiona li ty to our DSL so we can apprec ia te the power a DSL cangive to our program s.

Page 411: Land of Lisp - Barski M.D., Conrad

Building a M ore Complicated SVG Example

L et’s add a new func tion to our SVG DSL tha t m akes i t easy to draw an a rbitra ry polygon:(defun polygon (points color)

(tag polygon (points (format nil

"˜{˜a,˜a ˜}"

(mapcan (lambda (tp)(list (car tp) (cdr tp)))points))style (svg-style color))))

An SVG polygon stores a l l the points of the polygon in the points a t tribute . W e construc t the l ist of points by using a

format sta tem ent, which conta ins the ˜{ ˜} control strings . Rem em ber from Chapte r 11 tha t these control strings le t usi te ra te through a l ist inside the format func tion. In this case , we’re i te ra t ing through the l ist of points. W e then fla t ten the l ist of

point pa irs using mapcan , which you m ay rem em ber is the sam e as using a mapcar fol lowed by an append.In this exam ple , we’re going to draw som e random walks. A random walk is a graph of wha t you would ge t i f you dec ide , a t each

m om ent in t im e , to fl ip a coin and then m ove e i the r up or down a step. Random walks a re very sim ila r in behavior to stocks in thestock m arke t . T hey a re often used as a sta rt ing point for financ ia l m ode ling. Here is a func tion tha t genera tes a random walk:(defun random-walk (value length)

(unless (zerop length)(cons value

(random-walk (if (zerop (random 2))(1-value)(1+ value))(1-length)))))

T his func tion builds a l ist of num bers, sta rt ing with the value pa ram ete r. T hen i t inc reases or decreases this va lue random ly. W e

choose which direc t ion to m ove using the random func tion . (Note tha t , in order to keep i t sim ple , this func tion isn’t ta i l ca l loptim ized, since the cons happens a fte r the recursive ca l l . )

Here ’s an exam ple of how we can use the random-walk func tion:> (random-walk 100 10)

(100 101 102 101 100 101 102 103 102 103)Now le t’s use our SVG DSL to draw a slew of random walks in a pic ture :(with-open-file (*standard-output* "random_walk.svg"

:direction :output

:if-exists :supersede)

(svg (loop repeat 10

do (polygon (append '((0 . 200))(loop for xfor y in (random-walk 100 400)collect (cons x y))

'((400 . 200)))(loop repeat 3

collect (random 256))))))Since the am ount of da ta c rea ted in this exam ple is quite huge , we’re dum ping the da ta stra ight to a fi le (nam ed

random_walk . svg), instead of print ing i t to the RE PL . W e do this by redirec t ing the *standard-output* dynam ic variable , atechnique introduced in Chapte r 12. Notice how we can m ix L isp code free ly with our DSL com m ands. For instance , we can loop

right inside the SVG m acro to genera te 10 polygons a t once .T o m ake the graph pre t ty, we’re going to fi l l in the a rea under each graph l ine with a color. T o do this, we’l l represent each

line using a polygon, with the base l ine a long the bottom of the graph (with a y-coordina te of 200) inc luded as points to c lose theshape :

Page 412: Land of Lisp - Barski M.D., Conrad

T his is why we add points for the bottom -le ft and bottom -right corner as we c rea te each polygon. For even m ore

fun, we a lso random ize the color of each graph l ine .Here is an exam ple of som e random graphs genera ted by this ve ry sim ple DSL code :

Now tha t you’ve seen how easi ly you can write XML , HT ML , and SVG DSL s in L isp, le t’s c rea te an entire ly diffe rent kind ofDSL —one tha t wil l le t us build custom gam e com m ands for our W izard’s Adventure Gam e from Chapte r 5 and Chapte r 6!

Page 413: Land of Lisp - Barski M.D., Conrad

Creating Custom G ame Commands for Wizard's Adventure G ame

If you rem em ber, when we last encounte red the gam e sta rring our wizard and apprentice in Chapte r 5 and Chapte r 6, we couldwalk a round the world and pick up objec ts. However, we couldn’t rea l ly perform any other inte rest ing or fun ac tions. T o m ake agam e fun, i t should inc lude spec ia l ac t ions tha t can be perform ed with ce rta in objec ts and/or a t ce rta in loca tions in the gam e. W eneed frogs tha t can be kissed, dragons tha t can be fought, and perhaps even m aidens tha t can be rescued!

Crea ting these kinds of inte rest ing ac t ivi t ie s in the gam e poses a unique cha llenge . On the one hand, the re a re c lea rly m anysim ila ri t ie s be tween such diffe rent gam e ac tions. For instance , m ost of them will require us to have an objec t in our possession. Onthe other hand, they a l l need to have unique and idiosyncratic properties (enabled through com m and-spec ific L isp code) or thegam e becom es boring. As you’l l see , a DSL can he lp you add m any such unique com m ands to your gam e.

T o run the code from here unti l the end of this chapte r, we’re going to use a l l the gam e code from Chapte r 5 and Chapte r 6. Justput the code from those chapte rs into a fi le nam ed wizards_game. l isp (or download wizards_game. l isp from http: / / landofl isp. com /).As soon as the gam e is loaded, you can type gam e com m ands l ike look direc t ly in the CL ISP RE PL . Alte rna tive ly, you can use thegame-repl com m and we c rea ted in Chapte r 6 to ge t a m ore polished gam e experience . Rem em ber tha t the quit com m and wil ltake you out of the gam e RE PL .

Here’s wha t you do to load the gam e code from the RE PL and sta rt running gam e com m ands:> (load "wizards_game.lisp")

;; Loading file wizards_game.lisp ...;; Loaded file wizards_game.lispT> (look)(YOU ARE IN THE ATTIC. THERE IS A GIANT WELDING TORCH IN THE CORNER. THERE ISA LADDER GOING DOWNSTAIRS FROM HERE.)> (game-repl)lookYou are in the living-room. A wizard is snoring loudly on the couch. There isa door going west from here. There is a ladder going upstairsfrom here. You see a whiskey on the floor. You see a bucket on the floor.quit

Page 414: Land of Lisp - Barski M.D., Conrad

Creating New G ame Commands by H and

So what should our gam e DSL look l ike? T he only way to rea l ly know is to first c rea te som e com m ands by hand. T hen we cansee if the re a re any com m on pa tte rns be tween diffe rent com m ands tha t we can use as the basis of our DSL .

A Command for Welding

In the a t t ic of the wizard’s house is a we lding m achine . L e t’s a l low the players to weld the cha in to the bucke t i f they bringthose i tem s to tha t loca tion. Here ’s the code to m ake this happen:

(defun have (object)(member object (inventory)))

(defparameter *chain-welded* nil)

(defun weld (subject object)

(if (and (eq *location* 'attic)(eq subject 'chain)(eq object 'bucket)(have 'chain)(have 'bucket)(not *chain-welded*))

Page 415: Land of Lisp - Barski M.D., Conrad

(progn (setf *chain-welded* t)'(the chain is now securely welded to the bucket.))

'(you cannot weld like that.)))

First , we need an easy way of checking whether the player is currently ca rrying an objec t , using the have func tion .Rem em ber tha t we c rea ted a com m and for checking what the player is ca rrying, nam ed inventory. If an objec t is a m em ber ofthe inventory, i t m eans the player m ust “have” tha t objec t .

Next, our program needs som e way of keeping track of whe ther or not the cha in and bucke t a re welded toge ther, since the re wil lbe ac t ions la te r in the gam e tha t a re possible only once this we lding has happened. For this purpose , we c rea te a globa l , dynam ic

variable nam ed *chain-welded* .

Fina lly, we need to c rea te the welding com m and i tse lf . W elding is possible only if a slew of condit ions a re m et :

You m ust be in the a t t ic .You m ust have chain and bucket a s the subjec t and objec t of the welding com m and.You m ust be ca rrying the cha in and bucke t with you.T he cha in and bucke t can’t a lready be welded toge ther.

If these condit ions a re m et, we se t our *chain-welded* va riable to true and print a m essage indica ting this success. If any

of the condit ions fa i l , we indica te tha t the welding was unsuccessful .L e t’s t ry the com m and in the CL ISP RE PL :> (weld 'chain 'bucket)

(YOU CANNOT WELD LIKE THAT.)W ell, tha t’s exac tly the right response . Afte r a l l , we’re not in the a t t ic , and we a ren’t ca rrying the right objec ts. So fa r, so good.Next, le t’s t ry our new com m and in our fancy game-repl:> (game-repl)

weld chain bucketI do not know that command.quit

W hat? W hy doesn’t i t “know” tha t com m and? T he answer is sim ple : Our game-repl has som e basic protec tions aga inst runningunauthorized com m ands. T o rem edy this, we need to add weld to our l ist of pe rm itted com m ands:> (pushnew 'weld *allowed-commands*)

(WELD LOOK WALK PICKUP INVENTORY)> (game-repl)weld chain bucketYou cannot weld like that.

By using the pushnew com m and, the weld func tion is added only to the a l lowed com m ands if i t wasn’t a lready present in tha tl ist . Problem solved!

A Command for Dunking

In the wizard’s garden, the re is a we ll . L e t’s c rea te a com m and tha t le ts the player dunk the bucke t in the well to fi l l i t withwate r:

Page 416: Land of Lisp - Barski M.D., Conrad

(setf *bucket-filled* nil)

(defun dunk (subject object)

(if (and (eq *location* 'garden)(eq subject 'bucket)(eq object 'well)(have 'bucket)*chain-welded*)(progn (setf *bucket-filled* 't)'(the bucket is now full of water))'(you cannot dunk like that.)))

(pushnew 'dunk *allowed-commands*)

As with our weld com m and, we first need a variable to keep track of whe ther the bucke t has been fi l led ye t . Next, we

need a dunk func tion . Notice how, with dunking, we once aga in have a long l ist of condit ions tha t need to be m et before we

can successfully com ple te the ac t ion . Som e of these a re sim ila r to those we needed for our welding com m and. For instance ,dunking a lso requires the player to be in a spec ific loca tion with the correc t objec t . Other condit ions a re dunking-spec ific , such asthe fac t tha t the player needs to have a welded cha in before be ing able to dunk. Fina lly, we need to push the dunk func tion onto

our l ist of a l lowed ac tions .

Page 417: Land of Lisp - Barski M.D., Conrad

The game-action M acro

Now tha t we’ve c rea ted two custom gam e ac tions for our gam e, i t ’s obvious tha t the weld and dunk com m ands a re very sim ila rin som e ways. However, a s in our SVG libra ry, each gam e com m and needs to conta in a ce rta in am ount of dynam ic logic in i t , tocustom ize the behavior of the com m and. L e t’s write a game-action m acro tha t addresses these issues. It wil l m ake i t m uch easie rto c rea te new gam e com m ands.

(defmacro game-action (command subj obj place &body body)

`(progn (defun ,command (subject object)

(if (and (eq *location* ',place)(eq subject ',subj)(eq object ',obj)

(have ',subj))

,@body

'(i cant ,command like that.)))

(pushnew ',command *allowed-commands*)))T his game-action m acro em bodies the com m on pa tte rn be tween our dunk and weld com m ands. T he param ete rs to game-action

are the nam e of the com m and, the two objec ts involved in the ac t ion, the place i t needs to occur, and som e a rbitra ry addit iona l

Page 418: Land of Lisp - Barski M.D., Conrad

code in the body pa ram ete r tha t le ts us add custom logic to the com m and .

T he m ain job of the game-action m acro is to define a new func tion for a com m and . It m ay be surprising to you tha t am acro can do som ething as powerful as de fine a new func tion on i ts own, but the re is nothing to stop i t from doing this. I hope thisexam ple shows you just how flexible and m ind-bending the Com m on L isp m acro system can be .

Since a l l gam e ac tions for this gam e require the loca tion, subjec t , and objec t , we can take ca re of som e of the condit ions

direc t ly within this m acro . However, we’re going to leave other condit ions open for each spec ific com m and. Notice , for

exam ple , tha t the subjec t of the gam e sentence needs to be owned by the player , but the objec t does not . T his m akes sense ,since the re a re m any ac tions tha t can be perform ed, such as “ throw rock dragon,” where the objec t of the sentence (dragon) doesnot need to be in the player’s inventory.

Once the basic m acro-leve l condit ions have been m et, we wil l de fe r the rest of the logic to the leve l of the individua l com m and

. If the condit ions were not m e t, we print an e rror m essage , custom ized with the nam e of the current com m and .

Fina lly, we pushnew the com m and into the l ist of a l lowed com m ands for our fancy game-repl .One thing we do not do in this m acro is de fine or se t any globa l va riables. If a gam e com m and needs to define a *chain-

welded* or *bucket-filled* globa l va riable , i t m ust do this i tse lf. T his m akes sense , since the re is c lea rly no guarantee tha t the rewill be a one-to-one re la t ionship be tween sta te variables for our gam e and part icula r com m ands. For instance , som e com m andsm ay be perm itted m ult iple t im es, m aking the sta te unnecessa ry. Or an ac t ion m ay depend on m ult iple sta te variables. Having thiskind of va ria t ion in the com m ands is wha t m akes them unique and fun.

W ith this m acro, we now have a sim ple DSL for c rea ting new gam e ac tions! E ssentia l ly, this com m and gives us our ownprogram m ing language , spec ia l ized for the dom ain of c rea ting gam e com m ands. L e t’s rewrite our previous weld and dunkcom m ands using our new gam e com m and program m ing language :(defparameter *chain-welded* nil)

(game-action weld chain bucket attic(if (and (have 'bucket) (not *chain-welded*))(progn (setf *chain-welded* 't)'(the chain is now securely welded to the bucket.))'(you do not have a bucket.)))

(setf *bucket-filled* nil)

(game-action dunk bucket well garden(if *chain-welded*(progn (setf *bucket-filled* 't)'(the bucket is now full of water))'(the water level is too low to reach.)))

As you can see , these com m ands have becom e m uch easie r on the eyes. Notice how weld checks for ownership of the bucke t ,whereas dunk does not need to check for ownership of the well .

T o further i l lustra te the va lue of using m acros to im plem ent our gam e com m and DSL , le t’s im plem ent a m ore com plica tedgam e com m and, splash:(game-action splash bucket wizard living-room

(cond ((not *bucket-filled*) '(the bucket has nothing in it.))((have 'frog) '(the wizard awakens and sees that you stole his frog.he is so upset he banishes you to thenetherworlds-you lose! the end.))(t '(the wizard awakens from his slumber and greets you warmly.he hands you the magic low-carb donut-you win! the end.))))

For this com m and, the re a re three dist inc t scenarios tha t m ight happen:

T he bucke t is em pty.Your bucke t is ful l , but you stole the frog. In tha t case , you lose .Your bucke t is ful l and you didn’t stea l the frog. You win!

W ith our game-action m acro, we can support m any ac tion com m ands, each with spec ia l idiosyncra t ic behavior. St i l l , we a reable to avoid unnecessa ry repe ti t ion.

Note

T h e game-action com m and exposes the subject and object va riables within the body of the m acro. T his a l lows gam ecom m ands to access this inform ation, but i t m ight a lso cause a nam e coll ision if the code tha t c rea tes the game-action com m andsa lso has variables nam ed subject and object. As an exerc ise , t ry m odifying the game-action m acro so tha t the subject andobject va riables a re replaced by gensym nam es, as discussed in Chapte r 16.

Page 419: Land of Lisp - Barski M.D., Conrad

Let's Try the Completed Wizard's Adventure G ame!

Here is a sam ple run through of the W izard’s Adventure Gam e tha t shows off som e of the rich func tiona li ty we’ve put into thisgam e. Play the gam e yourse lf and see if you can win the m agic donut!

> (game-repl)lookYou are in the living-room. There is a wizard snoring loudly on the couch.There is a door going west from here. There is a ladder going upstairs fromhere. You see a whiskey on the floor. You see a bucket on the floor.pickup bucketYou are now carrying the bucketpickup whiskeyYou are now carrying the whiskeyinventoryItems-whiskey bucketwalk upstairsYou are in the attic. There is a giant welding torch in the corner. There is aladder going downstairs from here.walk eastYou cannot go that way.walk downstairsYou are in the living-room. A wizard is snoring loudly on the couch. There isa door going west from here. There is a ladder going upstairs from here.walk westYou are in a beautiful garden. There is a well in front of you. There is adoor going east from here. You see a frog on the floor. You seea chain on the floor.dunk bucket wellThe water level is too low to reach.pickup chainYou are now carrying the chainwalk eastYou are in the living-room. A wizard is snoring loudly on the couch. There isa door going west from here. There is a ladder going upstairsfrom here.splash bucket wizard

Page 420: Land of Lisp - Barski M.D., Conrad

The bucket has nothing in it.

Page 421: Land of Lisp - Barski M.D., Conrad

What You've Learned

T his chapte r dem onstra ted how to c rea te DSL s in L isp. You lea rned the fol lowing:W hen you need to do som e weird program m ing for a ve ry spec ific dom ain, Macros a re a grea t solution. W ith them , you can

crea te your own DSL .Often, i t m akes sense to first wri te a he lper func tion for a m acro (l ike print-tag), and then write a m acro (l ike tag) to add

im provem ents tha t only a m acro can provide . T hese im provem ents usua lly involve be ing able to access the code with a c lea re r,and often sa fe r, syntax.

You can m ix DSL s with regula r L isp code , which gives you a lot of power.DSL s a re use ful when you need to write ve ry spec ific code—whether i t ’s code for a web page , code tha t draws a pic ture , or

code tha t builds spec ia l gam e com m ands.

Page 422: Land of Lisp - Barski M.D., Conrad

Chapter 18. Lazy P rogramming

In Chapte r 14, you lea rned tha t your program s can be sim ple r and c leaner when buil t with c lean, m ath-l ike func tions. T hesefunc tions a lways re turn the sam e result , which depends sole ly on the a rgum ents passed into them . W hen you re ly only on thesetypes of func tions, you a re using the func tional programming sty le .

However, when we used the func tiona l program m ing style to c rea te the Dice of Doom gam e in Chapte r 15, a problem becam eevident: If your func tions re ly entire ly on the a rgum ents passed into them , the stuff tha t you need to pass into them often becom eshuge .

In the Dice of Doom gam e, we pass a round the game-tree va riable , which holds a l l the possible future sta tes of the gam e board.T his is a t ruly m assive struc ture , even on a m easly 3-by-3 board! So while the gam e’s current design m akes our code very sim pleand e legant , i t doesn’t appear to sca le well to la rger gam e boards, which would have exponentia l ly la rger gam e trees. T he onlyway we could conce ivably m ainta in our e legant code while a l lowing m ore com plex gam es on la rger boards is to m ake our programsm art enough not to look a t every conce ivable m ove right from the sta rt of the gam e. Is this possible? Yes, i t is possible , using afea ture ca l led lazy evaluation. In this chapte r, we’l l em ploy lazy eva lua tion to c rea te an im proved version of Dice of Doom .

Page 423: Land of Lisp - Barski M.D., Conrad

Adding Lazy Evaluation to Lisp

W ith lazy eva lua tion, we can st i l l c rea te our entire gam e tree in a single place in our code—at the beginning of our gam e.However, we use som e c lever tricks so tha t som e branches of our gam e tree a re hidden in c louds:

T he branches of the gam e tree a re st i l l dec la red right from the sta rt . However, we don’t bother doing a l l the ac tua l ca lcula t ionsfor the branches in c louds, as we would do when we c rea te a “ rea l” branch. T his is the lazy pa rt of lazy eva lua tion.

Instead, we wait to see if anyone “ looks” a t a c loudy branch. T he m om ent this happens, POOF!, we c rea te a rea l branch of ourgam e tree a t tha t spot:

Page 424: Land of Lisp - Barski M.D., Conrad

T his m eans tha t these branches in the gam e tree a re c rea ted only if som e part of the code happens to look a t them . If the playernever chooses a part icula r m ove in the gam e, and the AI never dec ides to contem pla te i t , our program will laz i ly avoid theca lcula t ions needed to figure out wha t the given branch ac tua lly looks l ike .

Som e languages, such as Haske ll and Clojure L isp, conta in support for lazy eva lua tion as part of the core of the language . Infac t , Clojure encourages i ts use and c lea rly dem onstra tes how useful i t is for func tiona l program m ing. However, the ANSI Com m onL isp standard does not conta in any sim ila r fea ture for such lazy eva lua tion. Fortuna te ly, with Com m on L isp’s powerful m acrosystem , we can easi ly add this fea ture to the language ourse lves!

Page 425: Land of Lisp - Barski M.D., Conrad

Creating the lazy and force Commands

T he m ost basic com m ands for lazy eva lua tion we’re going to c rea te a re lazy and force. T he lazy com m and wil l be a wrapperyou can put a round a piece of code , te l l ing L isp tha t you would l ike the code to be eva lua ted in a lazy way, l ike this:> (lazy (+ 1 2))

#<FUNCTION ...>As you can see , the com pute r does not t ry to ca lcula te the va lue of 1 plus 2. Instead, i t sim ply re turns a func tion. T o ge t the

ac tua l result of the ca lcula t ion, we m ust ca l l our other basic lazy eva lua tion com m and on a lazy va lue :> (force (lazy (+ 1 2)))

3T he im portant thing is tha t the ca lcula t ion was perform ed, but not when the lazy va lue was c rea ted—only when i t was forced.

T o see tha t this is the case , le t’s look a t a m ore com plex exam ple :

> (defun add (a b)(princ "I am adding now")(+ a b))ADD

> (defparameter *foo* (lazy (add 1 2)))*FOO*

> (force *foo*)

I am adding now3

Here , we’ve c rea ted our own add func tion, which, as a side e ffec t , prints a m essage to the console showing when the addit ion is

happening . Next, we laz i ly add two num bers with our func tion and store the result in the variable *foo* . So fa r, weknow the addit ion hasn’t ac tua lly happened, since the m essage “ I am adding now” has not ye t appeared.

T hen we force our variable . By forc ing the va lue , the ca lcula t ion is ac tua lly perform ed, and the result of 3 is re turned.

You can see tha t the addit ion took place when we forced the lazy va lue , since our m essage was a lso printed in the console .Here is the code for a sim ple im plem enta t ion of lazy:

(defmacro lazy (&body body)

(let ((forced (gensym))(value (gensym)))

`(let ((,forced nil)

(,value nil))

(lambda ()

(unless ,forced

(setf ,value (progn ,@body))

(setf ,forced t))

,value))))

W e im plem ent lazy by dec la ring a m acro . T his m acro wil l require two variables in the code i t genera tes. W e need to

dec la re these as gensym nam es , a s discussed in Chapte r 16. Next, we begin genera t ing the code tha t the m acro wil l output

(note the backquote a t the beginning of this l ine ).

At the top of the code genera ted by the m acro is a dec la ra t ion for two loca l va riables, using the gensym nam es we c rea ted

. T he first va riable te l ls us whe ther this lazy va lue has been forced ye t . If i t is nil, the va lue can hide in a c loud. If thevariable is t rue , the va lue is no longer hidden in a c loud, because i t has been forced.

Once the va lue has been ca lcula ted through a ca l l to force, we store the result ing va lue in another variable , though ini t ia l ly this

va lue isn’t used and is se t to nil . W hen our lazy m acro is ca l led, we want i t to re turn a func tion, which can be ca l led a t a

la te r t im e to force our lazy va lue to re turn a result . T here fore , we dec la re a lam bda func tion next .Rem em ber tha t any loca l va riables dec la red outside this lam bda func tion wil l be captured by the func tion as a c losure . T his

Page 426: Land of Lisp - Barski M.D., Conrad

m eans tha t the loca l va riables above wil l pe rsist be tween subsequent ca l ls of the lam bda func tion. W hy does thism atte r? W ell , once the c loud goes POOF!, we have com ple ted a l l the work to ca lcula te a va lue , and we don’t want to do i t aga inwhen the lazy va lue is forced and checked aga in m ult iple t im es in the future . W e can avoid this by rem em bering the va lue a fte r

the first force he re be tween ca lls.W hen our lazy va lue is forced (by ca ll ing the lam bda func tion we c rea ted), the first quest ion we m ust ask ourse lves is whe ther i t

has been forced a lready or is st i l l hidden behind the c loud . For a va lue tha t has not ye t been forced, we go POOF! and

perform the lazy ca lcula t ion , and save i t a s our value. W e a lso m ark i t a s having been forced . Now the c loud hasbeen destroyed.

Once the c loud is gone , we can sim ply re turn our ca lcula ted va lue . T his m ay have been just ca lcula ted, or i t m ay a lreadyexist from a previous ca l l to force.

Unlike the (adm ittedly m ind-bending) code for the lazy m acro, the force func tion is super-sim ple . All i t does is ca l l the lam bdafunc tion c rea ted by lazy:(defun force (lazy-value)

(funcall lazy-value))W e now have a ful ly func tiona l se t of prim it ive lazy eva lua tion com m ands. Many diffe rent types of sophist ica ted tools could be

buil t on top of these sim ple lazy and force com m ands.

Page 427: Land of Lisp - Barski M.D., Conrad

Creating a Lazy Lists Library

W e will now em ploy our new com m ands to build a l ibra ry for lazy l ists, based loose ly on the ir im plem enta t ion in Clojure . (InClojure , lazy l ists a re re fe rred to as lazy sequences. )

Since the fundam enta l com m and for working with L isp l ists is the cons com m and, you shouldn’t be surprised tha t the firstcom m and we c rea te for working with lazy l ists is the lazy-cons com m and:(defmacro lazy-cons (a d)

`(lazy (cons ,a ,d)))T his m acro em ula tes the behavior of cons, except tha t the result is wrapped in the lazy m acro. T o accom pany lazy-cons, we’l l

a lso c rea te lazy-car and lazy-cdr com m ands:(defun lazy-car (x)

(car (force x)))

(defun lazy-cdr (x)(cdr (force x)))

All these func tions do is force the lazy va lue and then ca ll car and cdr, re spec tive ly. L e t’s t ry using these new com m ands:

> (defparameter *foo* (lazy-cons 4 7))*FOO*

> (lazy-car *foo*)4

> (lazy-cdr *foo*)7

As you can see , we can use lazy-cons exac tly as we would use cons . T hen we can take apart a lazy cons in the sam e way

we would take apart a cons .So fa r, i t looks l ike our lazy l ist func tions a ren’t any diffe rent from the standard cons, car, and cdr func tions. However, we can

ac tua lly use them to perform som e pre t ty am az ing fea ts. Consider, for instance , the fol lowing defini t ion:(defparameter *integers*

(labels ((f (n)

(lazy-cons n (f (1+ n)))))(f 1)))

Here , we’ve used the lazy-cons com m and to dec la re som ething im possible : a va riable tha t holds a l ist of a l l posi t ive integers!

W e do this by c rea ting a loca l func tion f , which we then ca ll recursive ly to build an infini te cha in of lazy-conses, using an

ever-increasing num ber n . Once we’ve dec la red this seem ingly im possible *integers* va riable , we can use i t just a s youm ight expec t:> (lazy-car *integers*)

1> (lazy-car (lazy-cdr *integers*))2> (lazy-car (lazy-cdr (lazy-cdr *integers*)))3

As long as we st ick to using only our lazy- com m ands, we can pull wha tever we want out of our infini te l ist of integers, forc ingm ore and m ore num bers from *integers* on an as-needed basis.

Since not a l l l ists a re infini te (as is the l ist of posi t ive integers), we’l l a lso need to have a concept of a lazy-nil to te rm ina te al ist . Sim ila rly, we need a lazy-null func tion tha t we can use to check if we’ve reached the end of a l ist , just a s the null func tioncan be used to check for the end of a regula r l ist .(defun lazy-nil ()

(lazy nil))

(defun lazy-null (x)(not (force x)))

Now tha t we have a l l the basic building blocks for working with lazy l ists, le t’s c rea te som e useful func tions for our l ibra ry.

Page 428: Land of Lisp - Barski M.D., Conrad

Converting Between Regular Lists and Lazy Lists

One obvious thing we would want to be able to do is convert a regula r l ist into a lazy l ist . T he make-lazy func tion a l lows us todo this:(defun make-lazy (lst)

(lazy (when lst

(cons (car lst) (make-lazy (cdr lst))))))As the make-lazy func tion c lea rly shows, writ ing lazy l ist l ibra ry func tions is sort of l ike writ ing zen koans. T he only way to

understand them is to sta re a t them for a long t im e . T he E nglish language doesn’t have appropria te words for c lea rly expla iningfunc tions l ike make-lazy.

In broad te rm s, make-lazy uses recursion to trave l ac ross the l ist , and then wraps each cons in a ca l l to the lazy m acro

. However, to ge t the ful l m eaning of this func tion (and the other rem aining func tions in our lazy l ibra ry), you’l l just have totry to think ca re fully about wha t lazy and force rea l ly m ean, and m edita te a bi t over each func tion. L uckily, once our l i t t le lazylist l ibra ry is com ple te , i t wil l hide m ost of the strangeness of lazy eva lua tion.

Just as we wrote the make-lazy func tion to convert regula r l ists to lazy l ists, we can c rea te som e func tions to do the reverse—convert lazy l ists into regula r ones. T he take and take-all func tions a l low us to do this.

(defun take (n lst)(unless (or (zerop n) (lazy-null lst))(cons (lazy-car lst) (take (1-n) (lazy-cdr lst)))))

(defun take-all (lst)(unless (lazy-null lst)(cons (lazy-car lst) (take-all (lazy-cdr lst)))))

T he reason we want two diffe rent com m ands for going from lazy to regula r l ists is tha t , unlike regula r l ists, lazy l ists can beinfini te . T here fore , i t is use ful to have an addit iona l com m and tha t le ts us take just a spec ified num ber of i tem s from the l ist . T he

take func tion accepts an extra a rgum ent n tha t indica tes just how m any va lues we want to take . If we just want a l l va lues,

we can ca ll the take-all func tion . Of course , this func tion cannot be used on infini te l ists—taking a l l i tem s from an infini tel ist would lead to an infini te loop.

L e t’s t ry out our new lazy l ist conversion func tions:

> (take 10 *integers*)(1 2 3 4 5 6 7 8 9 10)

> (take 10 (make-lazy '(q w e r t y u i o p a s d f)))(Q W E R T Y U I O P)> (take-all (make-lazy '(q w e r t y u i o p a s d f)))

(Q W E R T Y U I O P A S D F)As you would expec t , i f we take the first 10 integers off the l ist of a l l posi t ive integers, we just ge t the num bers 1 through 10 as a

result . T he take func tion can a lso be used on a fini te l ist we’ve c rea ted by ca ll ing make-lazy . However, i f a l ist is

fini te , we can use the sim ple r take-all func tion and just ge t a regula r l ist of a l l i tem s in the lazy l ist .

Page 429: Land of Lisp - Barski M.D., Conrad

M apping and Searching Across Lazy Lists

W e a lso want to be able to m ap and search ac ross lazy l ists. Here a re som e func tions to a l low tha t:(defun lazy-mapcar (fun lst)

(lazy (unless (lazy-null lst)(cons (funcall fun (lazy-car lst))(lazy-mapcar fun (lazy-cdr lst))))))

(defun lazy-mapcan (fun lst)(labels ((f (lst-cur)(if (lazy-null lst-cur)(force (lazy-mapcan fun (lazy-cdr lst)))(cons (lazy-car lst-cur) (lazy (f (lazy-cdr lst-cur)))))))(lazy (unless (lazy-null lst)(f (funcall fun (lazy-car lst)))))))

(defun lazy-find-if (fun lst)(unless (lazy-null lst)(let ((x (lazy-car lst)))(if (funcall fun x)x(lazy-find-if fun (lazy-cdr lst))))))

(defun lazy-nth (n lst)(if (zerop n)(lazy-car lst)(lazy-nth (1-n) (lazy-cdr lst))))

T hese func tions a re ana logous to the func tions mapcar, mapcan, find-if, and nth. T he only diffe rence is tha t they accept andre turn lazy l ists. T his m eans tha t instead of using null, car, and cdr, they use the lazy versions of these func tions (lazy-null,lazy-car, and lazy-cdr) tha t we just c rea ted.

Using these func tions is pre t ty stra ightforward:

> (take 10 (lazy-mapcar #'sqrt *integers*))(1 1.4142135 1.7320508 2 2.236068 2.44948982.6457512 2.828427 3 3.1622777)

> (take 10 (lazy-mapcan (lambda (x)(if (evenp x)

(make-lazy (list x))

(lazy-nil)))*integers*))(2 4 6 8 10 12 14 16 18 20)

> (lazy-find-if #'oddp (make-lazy '(2 4 6 7 8 10)))7

> (lazy-nth 4 (make-lazy '(a b c d e f g)))E

Call ing lazy-mapcar to m ap the square root func tion ac ross the posit ive integers gives us a lazy l ist of the square roots of the

posit ive integers. T he first 10 a re shown . Next, we ca ll lazy-mapcan and check if each posit ive integer is even. If i t is,

we re turn a lazy l ist of the num bers . If i t isn’t , we re turn the lazy em pty l ist . T he result is tha t we’ve fi l te red out a l l

the even num bers from our lazy l ist of integers. W e can use lazy-find-if to find the first odd num ber in a lazy l ist . In this

case , the num ber was 7. Fina lly, we can use lazy-nth to pick a num ber out of a spec ific loca tion in a lazy l ist .W e have now writ ten an entire , i f ra ther sim ple , lazy l ist l ibra ry. Place a l l the func tions we’ve writ ten so fa r in this chapte r in a

fi le nam ed lazy . l isp (or sim ply download tha t fi le from http: / / landofl isp. com /).Now, you’re going to see tha t lazy l ists a l low us to grea tly boost the power of our Dice of Doom gam e engine!

Page 430: Land of Lisp - Barski M.D., Conrad

Dice of Doom, Version 2

In Chapte r 15, we c rea ted the first ve rsion of our Dice of Doom gam e. W e a re now going to m odify som e of the func tions fromtha t ve rsion. T o proceed, place the code from tha t chapte r into a fi le nam ed dice_of_doom_v1. l isp so tha t we can re fe rence i t inthis new version (or just download tha t fi le from http: / / landofl isp. com /).

T o use our previous Dice of Doom and our new lazy l ist l ibra ry, run the fol lowing in the RE PL :> (load "dice_of_doom_v1.lisp")

> (load "lazy.lisp")Next, we’re going to inc rease the size of our board to a m ore room y 4-by-4:> (defparameter *board-size* 4)

> (defparameter *board-hexnum* (* *board-size* *board-size*))T o a llow the gam e to run a t a reasonable speed a t this la rger size , we’l l m ake the l ist of m oves a t each branch of our gam e tree

a lazy l ist , instead of just a regula r l ist . By sim ply convert ing this one struc ture in our gam e from a regula r l ist to a lazy l ist , theentire gam e tree wil l becom e lazy as a result . T o accom plish this, we now need to redefine som e of the func tions from the firstve rsion of our gam e to use our new lazy l ist func tions.

First , le t’s m ake som e sm all m odifica t ions to the func tions tha t ca lcula te the a t tacking and passing m oves possible from a givenboard posit ion:(defun add-passing-move (board player spare-dice first-move moves)

(if first-movemoves

(lazy-cons (list nil(game-tree (add-new-dice board player(1-spare-dice))(mod (1+ player) *num-players*)0t))moves)))

(defun attacking-moves (board cur-player spare-dice)(labels ((player (pos)(car (aref board pos)))(dice (pos)(cadr (aref board pos))))

(lazy-mapcan(lambda (src)(if (eq (player src) cur-player)

(lazy-mapcan(lambda (dst)(if (and (not (eq (player dst)cur-player))(> (dice src) (dice dst)))

(make-lazy(list (list (list src dst)(game-tree (board-attack boardcur-playersrcdst(dice src))cur-player(+ spare-dice (dice dst))nil))))

(lazy-nil)))

(make-lazy (neighbors src)))

(lazy-nil)))

(make-lazy (loop for n below *board-hexnum*collect n)))))

As you can see , the add-passing-move func tion needs only one sm all change . Since the l ist of m oves is now a lazy l ist , we use

lazy-cons to add a passing m ove to the top of the l ist of possible m oves .T he attacking-moves func tion requires a few m ore changes. First , since i t now needs to re turn a lazy l ist , we use lazy-mapcan

in l ieu of mapcan in two places as the m oves a re ca lcula ted . T he lazy-mapcan func tion a lso requires the l ists c rea ted

inside i t to be lazy, which we accom plish with the make-lazy func tion . Also, any place we re turned nil we now

instead re turn a lazy-nil . Fina lly, we a lso m ake the l ist of ca lcula ted board posit ions lazy , since i t is fed intothe oute r lazy-mapcan.

Next, le t’s m ake sim ila r changes to two of the func tions tha t dea l with hum an players:

Page 431: Land of Lisp - Barski M.D., Conrad

(defun handle-human (tree)(fresh-line)(princ "choose your move:")(let ((moves (caddr tree)))(labels ((print-moves (moves n)

(unless (lazy-null moves)

(let* ((move (lazy-car moves))(action (car move)))(fresh-line)(format t "˜a. " n)(if action(format t "˜a -> ˜a" (car action) (cadr action))(princ "end turn")))

(print-moves (lazy-cdr moves) (1+ n)))))(print-moves moves 1))(fresh-line)

(cadr (lazy-nth (1- (read)) moves))))

(defun play-vs-human (tree)(print-info tree)

(if (not (lazy-null (caddr tree)))(play-vs-human (handle-human tree))(announce-winner (cadr tree))))

In the handle-human func tion, we have a loca l func tion print-moves, which is a l ist-ea te r func tion ac ross the l ist of m oves. W e

m odify i t to use our lazy com m ands when checking for the end of the l ist , taking a m ove off the front of the l ist , and

recursing ac ross the ta i l of the l ist . Fina lly, we m odify handle-human to use lazy-nth to pick a m ove a fte r the hum an

chooses i t from the l ist of options .In the play-vs-human func tion, we m ake just a single pinpoint change . In order to de te rm ine whether we’ve reached the end of a

gam e, we need to check whether the l ist of subsequent possible m oves is em pty, and then announce the winner. W e sim ply use

lazy-null to check if the lazy l ist of m oves is em pty .W ith these sim ple changes in place , you can play Dice of Doom aga inst another hum an on m uch la rger board sizes, since no

m ove in the tree is rea l ized unless one of the players dec ides to m ake i t . On our la rger, 4-by-4 board, ente r the fol lowing to sta rt agam e (just a s for ve rsion 1 of our gam e):> (play-vs-human (game-tree (gen-board) 0 0 t))

current player = aa-1 a-3 a-1 b-2b-3 a-3 a-3 a-1a-3 a-3 b-1 a-2b-3 a-3 a-1 a-3choose your move:1. 5 -> 102. 6 -> 103. 9 -> 104. 11 -> 105. 15 -> 10

Version 1 would sc reech to a ha lt the m om ent this com m and was executed. T his is because i t would need to genera te theentire ty of the gam e tree , for every possible move of the whole game , be fore the gam e would even sta rt playing.

W ith our lazy version of Dice of Doom , the gam e sta rts instantly!

Page 432: Land of Lisp - Barski M.D., Conrad

M aking O ur AI Work on Larger G ame Boards

Next, we’re going to adjust our gam e AI func tions to use the new lazy l ist l ibra ry when processing m oves. Along the way, we wil lm ake som e addit iona l im provem ents to the AI code .

Page 433: Land of Lisp - Barski M.D., Conrad

Trimming the G ame Tree

In version 1 of Dice of Doom , our AI code was, in ce rta in ways, extrem ely powerful . T his is because , a t every dec ision point , theAI player would look a t every possible future board posit ion to choose the absolute best next m ove . In this way, i t could play aperfec t gam e of Dice of Doom , winning every gam e tha t was winnable .

However, such a design does not sca le to la rger boards. T his is because i t becom es im possible to contem pla te every singlepossible future m ove once the re a re too m any. In fac t , the whole point of our new lazy gam e tree is to avoid contem pla ting everypossible m ove . T here fore , we need a way to te l l the com pute r, “Consider only this m any m oves, and no m ore . ” In other words, wewant to be able te l l i t to look only two, three , or four m oves ahead, and then stop looking any further.

T he func tiona l program m ing style of Dice of Doom a llows us to do this in a ve ry e legant but nonobvious way.T he obv ious solution to the problem would be to m odify the get-ratings and rate-position from version 1 to have a new

argum ent ca l led search-depth. T hen we could ask ourse lves a t every ca l l of those func tions, “Have we reached the m axim umsearch depth we want?”

T he problem with this approach is tha t i t gunks up those func tions with extra , confusing code . In fac t , the way we eva lua te boardposit ions is theore t ica l ly a separa te issue from how deep we wish to sea rch. As program m ers l ike to say, these issues a re orthogonal ,and i t would be best i f we could write separa te func tions to dea l with each of these issues independently.

In fac t , with our new lazy gam e tree , i t is possible to write a separa te func tion tha t is sole ly responsible for “ trim m ing” thesearch tree and is com ple te ly independent from the m ain AI code tha t contem pla tes and ra tes possible m oves.

Here is the func tion tha t t rim s our tree :

(defun limit-tree-depth (tree depth)(list (car tree)(cadr tree)

(if (zerop depth)

(lazy-nil)(lazy-mapcar (lambda (move)(list (car move)

(limit-tree-depth (cadr move) (1-depth))))(caddr tree)))))

T his is a pre t ty sim ple func tion tha t takes just two a rgum ents: a lazy tree and the depth to which we wish to trim i t . As aresult , i t just outputs a new gam e tree , ca l l ing i tse lf recursive ly, decrem enting the depth for each leve l i t t rave ls into the tree

. Once this depth reaches ze ro , we know we’re a t the leve l tha t we want to trim , and we se t the lazy l ist of m oves to

the em pty l ist .Now a ll we need to do is ca l l our new limit-tree-depth func tion before doing our AI ra t ing ca lcula t ions. W e do this by

tweaking our handle-computer func tion a bi t :(defparameter *ai-level* 4)

(defun handle-computer (tree)

(let ((ratings (get-ratings (limit-tree-depth tree *ai-level*)(car tree))))(cadr (lazy-nth (position (apply #'max ratings) ratings)

(caddr tree)))))Before ca l l ing get-ratings to ge t a ra t ing for every next ava ilable m ove , we transform our gam e tree into our trim m ed gam e

Page 434: Land of Lisp - Barski M.D., Conrad

t ree . All of our AI code can now run on the trim m ed tree , com ple te ly oblivious to the fac t tha t a la rger gam e tree exists ortha t the re a re deeper m oves i t isn’t inc luding in i ts ca lcula t ions. W ith this technique , we have m anaged to decouple the code tha tl im its the AI sea rch depth from the a lgori thm tha t ac tua lly eva lua tes board posit ions. One other sm all m odifica t ion is to use lazy-

nth when picking a m ove out of the lazy l ist of m oves .

Note

T he limit-tree-depth func tion uses a pre t ty c rude m ethod for trim m ing our tree : It sim ply trim s a l l t ree branches beyond acerta in depth. For m ost board gam es, doing this is an optim al way of trim m ing the gam e tree . However, Dice of Doom has theuncom m on property tha t m ult iple m oves in a row are a l lowed for each player. It would probably be m ore optim al if limit-tree-depth took into account how m any t im es we’ve switched players as a c ri te rion for trim m ing a branch. But our sim ple r ve rsionworks well enough.

At this point , we should a lso m ake a pinpoint change to play-vs-computer:(defun play-vs-computer (tree)

(print-info tree)

(cond ((lazy-null (caddr tree)) (announce-winner (cadr tree)))((zerop (car tree)) (play-vs-computer (handle-human tree)))(t (play-vs-computer (handle-computer tree)))))

Here , we just added a lazy-null to check for the end of the lazy l ist of m oves in a single spot .Now le t’s look a t another trick tha t wil l im prove the power of our AI code .

Page 435: Land of Lisp - Barski M.D., Conrad

Applying H euristics

By trim m ing our gam e tree , we’ve fundam enta l ly changed our AI player. W ithout trim m ing, the AI player was able to play aperfec t gam e a t a l l t im es. By trim m ing the tree , however, i t is possible for the AI to “m iss som ething, ” since i t is no longercontem pla ting every possible future m ove . In version 2 of Dice of Doom , the com pute r player wil l no longer be able to play aperfec t gam e—just a “pre t ty good” gam e is possible .

Basica lly, we’ve exchanged the AI’s abil i ty to play a perfec t gam e for m uch be tte r pe rform ance . In the process, we’ve turnedthe AI code from som ething prec ise tha t can be ana lyzed by m athem atics into som ething tha t is “squishie r” and fa r le ss prec ise . Ascom pute r sc ientists would say, we have now ente red into the rea lm of heurist ics.

In com pute r sc ience , heurist ics a re program m ing techniques tha t a re im perfec t , but a l low us to ge t good results ve ry quickly.Broadly speaking, any technique tha t is fast but not guaranteed to work 100 percent of the t im e is a heurist ic . W hen we write codetha t uses heurist ics (as our Dice of Doom AI engine now does), i t is often worthwhile to use som e c rea tive thinking and to “playaround” with the code in diffe rent ways.

Basica lly, since we’re a lready given up on our goa l of a pe rfec t solution and a re now using im prec ise techniques, i t ’s possibletha t tweaking the knobs on the heurist ic code in diffe rent ways could dram atica l ly im prove our results. And indeed, i t turns out tha tthe re is a sim ple change we can m ake to our Dice of Doom AI heurist ics tha t wil l significantly im prove the AI player’s gam e.

Page 436: Land of Lisp - Barski M.D., Conrad

Winning by a Lot vs. Winning by a Little

In version 1 of our Dice of Doom code , the AI player had no reason to ever worry about i ts m argin of vic tory. All i t ca red aboutwas tha t when the gam e ended, i t had ownership of a t least one m ore te rri tory of the board than i ts opponent, which m eant i t hadwon.

However, now tha t we’re using im prec ise heurist ics in our AI code , i t m atte rs a lot how la rge the lead is a t any point in thegam e. A heurist ic rule for this si tua t ion is “ If I am tota l ly whom ping m y opponent in the gam e, i t is pre t ty unlike ly he /she wil l beable to recover, even if I look only a few m oves ahead. ”

Rem em ber tha t a m inim ax a lgori thm (as we’re using in our AI) assigns a point score to every fina l lea f branch in the tree . Inversion 1 of our gam e, this score was e i the r 0 or 1, or som etim es 1/2 when the gam e ended in a t ie . In version 2, these a re not t ruly“fina l leaves” in the tree , but sim ply leaves in our m uch sm alle r t rim m ed tree . In this si tua t ion, i t would be m uch be tte r i f ourlea f point scores had a la rger range of va lues, so tha t we can te l l which m oves lead to a gam e we’re winning by “a lot” and whichm oves lead to a gam e we’re winning by only “a l i t t le . ”

L e t’s write a score-board func tion tha t uses som e m ore com plex heurist ics to score the board posit ion a t a lea f:(defun score-board (board player)

(loop for hex across boardfor pos from 0

sum (if (eq (car hex) player)

(if (threatened pos board)

1

2)

−1)))

T he score-board func tion loops ac ross a l l of the hexes of the board and builds a running tota l of points for each hex using

the sum direc t ive of the loop m acro. If the player we’re scoring owns the current hex , we want to add posit ive points to the

tota l .T o dec ide exac tly how m any points to add to the tota l for an occupied hex, we m ake another heurist ic observa tion: Hexes tha t

ne ighbor a stronger opponent a ren’t quite as va luable as hexes without strong ne ighbors. W e’ll ca l l a hex tha t ne ighbors an enem y

hex tha t has m ore dice on i t a threatened hex . For hexes tha t a re threa tened , we’l l add only 1 point to the point tota l .

For hexes tha t a re unthrea tened, we’l l add 2 points . Fina lly, for each hex owned by an opposing player, we’l l subtrac t 1 point

from the tota l .Aga in, the im portant thing to rea l ize is tha t score-board is a heurist ic func tion, and there is no truly right or wrong way to

genera te such a score . Instead of adding 2 points for unthrea tened hexes, we could just a s easi ly have added 1.5 points. Indeve loping this exam ple , I ran som e sim ula tions playing various opponents using diffe rent ve rsions of the score-board func tion,and this ve rsion ended up working reasonably well . Deve loping heurist ics is not an exac t sc ience .

Here is the func tion tha t de te rm ines whe ther a given hex is threa tened:(defun threatened (pos board)

(let* ((hex (aref board pos))(player (car hex))(dice (cadr hex)))

(loop for n in (neighbors pos)

do (let* ((nhex (aref board n))(nplayer (car nhex))(ndice (cadr nhex)))

(when (and (not (eq player nplayer)) (> ndice dice))

(return t))))))

First , we ge t the hex in quest ion and figure out who the occupying player is, and how m any dice tha t player has . T hen we

loop through a l l the ne ighboring squares for the current posi t ion . Afte r tha t , we find out the player and dice count for each of

the ne ighbors . As soon as we find a ne ighboring hex owned by an opponent with a la rger dice count (a threa tening ne ighbor)

, we can re turn true . Ca ll ing return in this way causes the loop to stop ea rly with true as a result .

Page 437: Land of Lisp - Barski M.D., Conrad

Now tha t we have com ple ted our score-board and threatened func tions, we’re ready to write our im proved get-ratings andrate-position func tions:(defun get-ratings (tree player)

(take-all (lazy-mapcar (lambda (move)(rate-position (cadr move) player))(caddr tree))))

(defun rate-position (tree player)(let ((moves (caddr tree)))

(if (not (lazy-null moves))(apply (if (eq (car tree) player)#'max#'min)(get-ratings tree player))

(score-board (cadr tree) player))))

As you can see , we’ve upda ted a couple l ines of code to be com patible with our new lazy gam e tree . Notice tha t any

gam e posit ions tha t lack fol low-up m oves (tha t is, leaves) now cause our new score -board func tion to be ca l led .Now tha t we have a ful ly working heurist ic AI player tha t can play on la rger gam e boards, le t’s t ry i t out . As usua l , a l l m oves for

player B in the fol lowing exam ple a re be ing autom atica l ly ca lcula ted by the AI a lgori thm :> (play-vs-computer (game-tree (gen-board) 0 0 t))

current player = aa-1 b-2 b-1 a-3b-3 a-1 a-3 a-3b-3 b-2 b-2 b-2a-3 a-3 a-2 a-2choose your move:1. 3 -> 22. 6 -> 23. 6 -> 104. 6 -> 15. 6 -> 116. 7 -> 117. 7 -> 28. 13 -> 93current player = aa-1 b-2 b-1 a-3b-3 a-1 a-1 a-3b-3 b-2 a-2 b-2a-3 a-3 a-2 a-2choose your move:1. end turn2. 3 -> 23. 7 -> 114. 7 -> 25. 13 -> 91current player = ba-2 b-2 b-1 a-3b-3 a-1 a-1 a-3b-3 b-2 a-2 b-2a-3 a-3 a-2 a-2current player = ba-2 b-1 b-1 a-3b-3 b-1 a-1 a-3b-3 b-2 a-2 b-2a-3 a-3 a-2 a-2current player = bb-2 b-1 b-1 a-3b-1 b-1 a-1 a-3b-3 b-2 a-2 b-2a-3 a-3 a-2 a-2current player = bb-2 b-1 b-1 a-3b-1 b-1 b-1 a-3b-3 b-2 a-2 b-1a-3 a-3 a-2 a-2current player = ab-3 b-2 b-2 a-3b-1 b-1 b-1 a-3b-3 b-2 a-2 b-1a-3 a-3 a-2 a-2choose your move:1. 3 -> 22. 7 -> 113. 7 -> 24. 7 -> 6

Page 438: Land of Lisp - Barski M.D., Conrad

5. 10 -> 66. 10 -> 57. 10 -> 118. 13 -> 99. 15 -> 11...

W ith these changes in place , the AI player wil l win a round 65 to 70 percent of a l l gam es (depending on the board size and AIleve l) when pit ted aga inst a player tha t chooses only random m oves. T his is ac tua lly a very good result . Our sim ple gen-boardfunc tion often c rea tes very lopsided sta rt ing posit ions, so m any of the rem aining 30 percent of the gam es a re sim ply unwinnable forthe com pute r.

Page 439: Land of Lisp - Barski M.D., Conrad

Alpha Beta P runing

L et’s add one fina l im provem ent to version 2 of our Dice of Doom AI.A lpha-be ta pruning is a we ll-known optim iza tion of the m inim ax a lgori thm tha t im proves perform ance by skipping over som e

branches (pruning those branches) if i t is ce rta in tha t they wil l not im pac t the fina l m inim ax eva lua tion.

W hen would a branch in the gam e tree be unable to im pac t the fina l result? In order to understand how a lpha-be ta pruningworks, look a t the fol lowing pic ture , showing the gam e tree for a sim ple 2-by-2 board:

Page 440: Land of Lisp - Barski M.D., Conrad

At the top of this pic ture is the sta rt ing posit ion of the gam e. T he a rrows point to possible m oves. Above each board i t sta teswhich player (A or B) currently is m aking a m ove .

T he pic ture a lso shows the results of a m inim ax ana lysis of the gam e tree . On the bottom right of each board, you can see anum ber showing how our la test get-ratings func tion (with the new score-board logic ) would ra te tha t posi t ion. For lea f nodes (theboards a long the very bottom ), this num ber is ca lcula ted through score-board. For branch nodes, the num ber is ca lcula ted based onthe m inim ax a lgori thm .

E very posit ion in the gam e tree tha t a l lows a choice of m oves is m arked e i the r as a MAX node or MIN node . Since the ana lysisin the pic ture is based on finding the best m ove for player A, a l l places a l lowing choices for player A a re m arked as MAX. Allposit ions a l lowing choices for player B a re m arked as MIN. As you can see from the pic ture , this gam e is pre t ty unexc it ing, andthere is only one posit ion where player B ac tua lly has a choice of m oves. In other words, only one MIN node exists in the gam etree .

W orking le ft to right , the m inim ax a lgori thm trave ls, depth first , exploring a l l the way down to the leaves. T his is ca l led adepth-f irst search. (W e’re assum ing no trim m ing is occurring, with *ai-level* se t ve ry high. ) T hen i t chooses e i the r the m axim umor m inim um scores for any nodes tha t have m ore than one branch.

W hen i t does this, the first (le ft) branch of the MIN node in the pic ture ends up with a score of 8. If the AI engine now dips intothe right branch, i t rea l ly only ca res wha t i t finds the re as long as the score rem ains be low 8. Afte r a l l , the m inim um of 8 and anyla rger num ber la rger than 8 wil l st i l l be 8, m aking such la rge num bers irre levant to the eventua l outcom e of the ca lcula t ion.

As soon as the AI finds a node in the right branch tha t has a score of 8 (m arked with a sta r in the pic ture ), i t knows the rest of theright branch is i rre levant and can be pruned away from our ca lcula t ions. T his m eans the m inim ax a lgori thm has no need to look a tthe branch in the tree m arked with the dotted l ine in the pic ture .

T his is a sim ple exam ple , showing a lpha-be ta pruning in ac t ion. In the gam e tree shown in the pic ture , this pruning leads to onlym odest savings, since just a sm all num ber of the tota l nodes can be pruned. However, with la rger gam e trees, the savings fromalpha-be ta pruning a re typica lly im m ense , consti tut ing a m ajori ty of the nodes in the gam e tree .

W e’re going to take som e l ibe rt ies in how we im plem ent a lpha-be ta pruning in our gam e to keep things sim ple . First , an a lpha-be ta pruning a lgori thm usua lly wil l pass a round two variables ca l led, na tura l ly, alpha and beta.

T his is because i t ’s possible to write code tha t handles both the MAX nodes and MIN nodes a t once by switching alpha and betabe tween the high and low l im its. In our exam ple , we’re going to use the variables upper-limit and lower-limit instead,indica ting the highest and lowest va lues we ca re about as we traverse the gam e tree . As a cost , the re wil l be som e repe ti t ive -looking code for handling the MAX and MIN cases. However, thinking of a lpha-be ta pruning in te rm s of upper-limit and lower-limit m akes the code a bi t easie r to understand.

Another com prom ise we’re m aking is tha t we’re not decoupling the pruning code from the m inim ax code . Rem em ber tha t withthe trim m ing code , we wrote an independent func tion nam ed limit-tree-depth, which separa ted the ac t of t rim m ing from the restof the AI code . W e could use a sim ila r approach for separa t ing the a lpha-be ta pruning code as well , c rea t ing a func tion tha t cantransform the gam e tree into a pruned version on i ts own. However, doing this is a bi t m ore involved, because the a lpha-be tapruning code m ust have access to inte rm edia te m inim ax ca lcula t ions. For a m ore advanced AI engine , this would be a good idea .For our sim ple engine , we wil l just add our a lpha-be ta pruning check direc t ly inside our m inim ax func tions.

So le t’s ge t sta rted. First , we’l l rewrite our get-ratings func tion as two new func tions: ab-get-ratings-max and ab-get-ratings-min.

Page 441: Land of Lisp - Barski M.D., Conrad

Rem em ber tha t the get-ratings func tion was responsible for ca lcula t ing the best score out of m ult iple ava ilable m oves from asingle -board a rrangem ent. Now, however, we want i t to stop ea rly in i ts eva lua tion of m oves once i t dec ides i t has found a m ovetha t’s “as good as is possible . ” Dete rm ining whether i t has reached this point is subtly diffe rent depending on whether the node inquestion is a MAX m ove (a m ove of the current player) or a MIN m ove (a m ove for the opponent).

L e t’s look a t the version responsible for MAX nodes first :

(defun ab-get-ratings-max (tree player upper-limit lower-limit)(labels ((f (moves lower-limit)(unless (lazy-null moves)

(let ((x (ab-rate-position (cadr (lazy-car moves))playerupper-limitlower-limit)))

(if (>= x upper-limit)

(list x)

(cons x (f (lazy-cdr moves) (max x lower-limit))))))))(f (caddr tree) lower-limit)))

W e’re now passing in an extra upper-limit and lower-limit a rgum ent into ab-get-ratings-max . T his func tion won’tac tua lly ever check the lower-limit a rgum ent direc t ly, since i t is concerned only with finding the m axim um ra t ing possible fromthe given loca tion in the tree . However, i t wil l pass this va lue on to child branches, which m ay conta in MIN nodes tha t do ca reabout the lower l im it .

W hen we ra te the next branch of the tree (by ca ll ing ab-rate-position, which we’l l wri te short ly), we save the result a s

x. If x is grea te r than or equa l to our upper-limit , we know we got a result a s good as we can hope for, and can just re turn

the la test ra t ing as a fina l va lue in our l ist .

If x isn’t la rge enough, we need to keep looking a t the rem aining branches . Note tha t x wil l becom e the new lower-limiti f i t ’s la rger than the previous lower-limit.

Next, le t’s look a t the ab-get-ratings-min func tion:(defun ab-get-ratings-min (tree player upper-limit lower-limit)

(labels ((f (moves upper-limit)(unless (lazy-null moves)(let ((x (ab-rate-position (cadr (lazy-car moves))playerupper-limitlower-limit)))(if (<= x lower-limit)(list x)(cons x (f (lazy-cdr moves) (min x upper-limit))))))))(f (caddr tree) upper-limit)))

T he ab-get-ratings-min func tion is basica l ly identica l to the ab-get-ratings-max func tion, except the roles of the upper andlower l im its a re fl ipped. Based on the repe ti t iveness of these two func tions, you could probably im agine how the ab-get-ratings-max and ab-get-ratings-min func tions could be com bined into a single func tion. As m entioned ea rl ie r, with tha t approach, ra therthan upper-limit and lower-limit, you would use the m ore generic te rm s alpha and beta, a s these wil l diffe r based on whetherthe node is a MAX node or a MIN node .

Next, we need to tweak rate-position, the func tion tha t ra tes a single -board a rrangem ent:(defun ab-rate-position (tree player upper-limit lower-limit)

(let ((moves (caddr tree)))(if (not (lazy-null moves))

(if (eq (car tree) player)

(apply #'max (ab-get-ratings-max treeplayerupper-limitlower-limit))

(apply #'min (ab-get-ratings-min treeplayerupper-limitlower-limit)))(score-board (cadr tree) player))))

In our new ab-rate-position, we check if this node in the gam e tree is a m ove for us or a m ove for an opponent . If i t ’s a

m ove for us, then i t’s a MAX node , and we want to dispa tch to ab-get-ratings-max . If i t ’s the opponent’s turn, we instead

Page 442: Land of Lisp - Barski M.D., Conrad

dispa tch to ab-get-ratings-min . Otherwise , ab-rate-positon is the sam e as our previous rate-position func tion.T o com ple te our support for a lpha-be ta pruning, we need to m odify one m ore func tion: the handle-computer func tion tha t kicks

off our m inim ax ca lcula t ions:(defun handle-computer (tree)

(let ((ratings (ab-get-ratings-max (limit-tree-depth tree *ai-level*)(car tree)

most-positive-fixnum

most-negative-fixnum)))(cadr (lazy-nth (position (apply #'max ratings) ratings) (caddr tree)))))

T his func tion sta rts off the m inim ax ca lcula t ion by ca ll ing ab-get-ratings-max , since the first m ove m ost de fini te lybe longs to the ta rge t player and there fore is a MAX node .

W hen we ca ll this func tion, we’l l need to pass in our sta rt ing upper-limit and lower-limit. Since we’re a t the very beginningof our m inim ax searching, we’l l want to se t these to be as la rge and as sm all a s possible . Idea lly, we would want them to beposit ive inf ini ty and negative inf ini ty . Although m any L isp environm ents conta in support for such concepts, they a re not pa rt of theANSI Com m on L isp standard. However, the standard does define most-positive-fixnum and most-negative-fixnum, which a revery la rge posit ive and nega tive num bers, m aking them perfec tly suited for our purposes. Hence , we pass these into ab-get-

ratings-max to sta rt off our l im its .If we wanted to squeeze out a tad m ore e ffic iency from our AI engine , we could, instead, se t the upper-limit and lower-limit

to be the m axim um and m inim um va lues from our score-board func tion. T ha t would sl ightly im prove the am ount of pruning tha tis possible . However, the score-board func tion m ay re turn a diffe rent range of scores based on the size of the board. and i t m ighthave other dependenc ies if we dec ide to optim ize board scoring even m ore in the future . T here fore , i t is best for the t im e be ing ifwe se t our l im its to nigh infini ty for the sta rt of our m inim ax ca lcula t ions so we don’t need to worry about this.

As a fina l reward for once aga in im proving the perform ance of our AI, le t’s inc rease the size of the board to use a 5-by-5 gam efie ld. W ith our new lazy, t rim m ed, and pruned AI a lgori thm s, we should be able to handle this la rger board without a swea t:(defparameter *board-size* 5)

(defparameter *board-hexnum* (* *board-size* *board-size*))

Note

Rem em ber tha t we used m em oiza tion for som e of our ea rl ie r func tions. If you have a lready played som e gam es in this chapte ron a 4-by-4 board, one func tion in part icula r, the neighbors func tion, m ay re turn results based on this old board size . T his is onlyan issue if you’ve a lready played a gam e on the 4-by-4 board without resta rt ing your L isp in the inte rim . T o fix this, sim ply re runthe defini t ion of the neighbors func tion in dice_of_doom_v1.lisp from the RE PL (inc luding the m em oized revision a t the bottomof the fi le ) to c lea r any cached results.

Here ’s wha t our gam e looks l ike now:> (play-vs-computer (game-tree (gen-board) 0 0 t))

current player = aa-2 b-2 a-1 b-2 b-2a-1 b-2 b-3 b-3 a-3a-1 b-2 a-3 b-1 b-2b-1 b-3 a-2 b-2 a-1b-3 b-1 b-1 a-3 b-3choose your move:1. 9 -> 132. 9 -> 43. 9 -> 144. 12 -> 135. 17 -> 226. 23 -> 187. 23 -> 223current player = aa-2 b-2 a-1 b-2 b-2a-1 b-2 b-3 b-3 a-1a-1 b-2 a-3 b-1 a-2b-1 b-3 a-2 b-2 a-1b-3 b-1 b-1 a-3 b-3choose your move:1. end turn2. 12 -> 133. 14 -> 134. 14 -> 155. 17 -> 226. 23 -> 187. 23 -> 221current player = ba-3 b-2 a-1 b-2 b-2a-1 b-2 b-3 b-3 a-1

Page 443: Land of Lisp - Barski M.D., Conrad

a-1 b-2 a-3 b-1 a-2b-1 b-3 a-2 b-2 a-1b-3 b-1 b-1 a-3 b-3current player = ba-3 b-1 a-1 b-2 b-2b-1 b-2 b-3 b-3 a-1a-1 b-2 a-3 b-1 a-2b-1 b-3 a-2 b-2 a-1b-3 b-1 b-1 a-3 b-3current player = ba-3 b-1 b-1 b-1 b-2b-1 b-2 b-3 b-3 a-1a-1 b-2 a-3 b-1 a-2b-1 b-3 a-2 b-2 a-1b-3 b-1 b-1 a-3 b-3current player = ba-3 b-1 b-1 b-1 b-1b-1 b-2 b-3 b-3 b-1a-1 b-2 a-3 b-1 a-2b-1 b-3 a-2 b-2 a-1b-3 b-1 b-1 a-3 b-3current player = ba-3 b-1 b-1 b-1 b-1b-1 b-1 b-3 b-3 b-1b-1 b-2 a-3 b-1 a-2b-1 b-3 a-2 b-2 a-1b-3 b-1 b-1 a-3 b-3current player = ba-3 b-1 b-1 b-1 b-1b-1 b-1 b-3 b-3 b-1b-1 b-2 a-3 b-1 a-2b-1 b-1 b-2 b-2 a-1b-3 b-1 b-1 a-3 b-3current player = ba-3 b-1 b-1 b-1 b-1b-1 b-1 b-3 b-3 b-1b-1 b-2 a-3 b-1 a-2b-1 b-1 b-2 b-2 b-2b-3 b-1 b-1 a-3 b-1current player = aa-3 b-2 b-2 b-2 b-2b-2 b-2 b-3 b-3 b-1b-1 b-2 a-3 b-1 a-2b-1 b-1 b-2 b-2 b-2b-3 b-1 b-1 a-3 b-1choose your move:1. 0 -> 42. 0 -> 13. 0 -> 54. 12 -> 135. 14 -> 106. 14 -> 97. 14 -> 138. 14 -> 159. 23 -> 1810. 23 -> 1711. 23 -> 2212. 23 -> 24

At this point , our RE PL gam e inte rface is becom ing rea l ly im prac tica l for such a la rge gam e fie ld. W e’ll be addressing tha tnext .

Page 444: Land of Lisp - Barski M.D., Conrad

What You've Learned

In this chapte r, we m ade the com pute r player for our Dice of Doom gam e m uch m ore sophist ica ted. W e im plem enting the gam etree using lazy l ists, and applied severa l optim iza tion techniques to l im it the num ber of board posit ions tha t a re sea rched by the AIengine . Along the way, you lea rned the fol lowing:

Lazy programming a l lows you to work with very la rge (and even infini te ) da ta struc tures and do so e ffic iently.Once you have a lazy m acro and a force func tion, you can use them to build m ore sophist ica ted lazy opera t ions, inc luding

building a lazy l ist l ibra ry.Heurist ics a re im perfec t a lgori thm s tha t can be used to im prove the perform ance of your code , with som e c rea tive thinking. In

our exam ple , we m ade som e heurist ic changes to how we score lea f nodes.Once we converted Dice of Doom to use a lazy tree , we were able to e legantly trim the gam e tree in order to l im it how deep

the AI thinks when contem pla ting i ts m oves.Alpha-be ta pruning le ts us im prove perform ance even m ore , by pruning branches tha t have no way of im pac ting the fina l scores

on the m oves be ing considered by the AI.

Page 445: Land of Lisp - Barski M.D., Conrad

Chapter 19. Creating a G raphical , Web-Based Version of Dice of Doom

In the previous chapte r, we c rea ted a second version of Dice of Doom to play on la rger gam e boards. It has becom e quitedifficult to understand the board and m ake m oves using our c rude console inte rface . Certa inly, Dice of Doom would be infini te lybe tte r i f we had a pre t ty graphica l gam e board tha t a l lowed us to sim ply c l ick where we wanted to m ake our m oves. W ell , I havegood news for you . . .

In this chapte r, we’l l put toge ther a lot of code from earl ie r chapte rs to transform Dice of Doom into a ful l-fea tured, graphica lgam e you can play right inside a web browser!

Page 446: Land of Lisp - Barski M.D., Conrad

Drawing the G ame Board Using the SVG F ormat

W e’ve a lready writ ten a prim it ive web se rver in Chapte r 13. Also, we’ve covered how to draw SVG graphics with a DSL inChapte r 17. L ucky for us, the new HT ML 5 standard inc ludes fea tures tha t m ake i t possible to em bed SVG pic tures direc t ly inside astandard HT ML docum ent. In this way, we’l l be able to use our sim ple l i t t le web se rver to se rve up som e fully inte rac tive vec torgraphics. You’l l be am azed a t how easy i t is to do this.

Note

At the t im e this book was writ ten, the only web browser to support inl ine SVG within HT ML was Fire fox 3.7 Alpha . Use this, or am ore recent re lease of Fire fox with our new version of Dice of Doom . If you’re having problem s, t ry naviga ting to the about:configpage in the Fire fox address bar, and se t the htm l5. enable configura t ion se t t ing to true . T his wil l a l low Fire fox to use the la testHT ML 5 se tt ings.

Also, rem em ber tha t our web se rver l ibra ry is not pure ANSI Com m on L isp, and m akes use of som e CL ISP-spec ific extensions.T his m eans i t requires CL ISP to func tion.

First , we’l l need to pull in code from various other chapte rs to ge t ready. In the previous chapte r, we c rea ted version 2 of ourDice of Doom engine . Place a l l the code from tha t chapte r in a fi le nam ed dice_of_doom_v2. l isp. You should a lso a lready havecrea ted a fi le nam ed webserver. l isp from Chapte r 13. (T hese fi le s a re a l l free ly ava ilable from http: / / landofl isp. com /. )

L e t’s load in these fi le s:> (load "dice_of_doom_v2.lisp")

> (load "webserver.lisp")For our SVG support , we’l l a lso need the SVG-rendering code from Chapte r 16 and Chapte r 17. Place those func tions in svg. l isp.

(T his fi le is a lso ava ilable from http: / / landofl isp. com /. ) For re fe rence , the func tions we’l l need a re let1, split, pairs, print-tag,tag, svg, brightness, svg-style, and polygon. L oad this fi le next:> (load "svg.lisp")Now le t’s write som e code tha t can draw a pre t ty version of our gam e board using SVG. First , we’l l want to define som e constants

tha t control the various dim ensions needed to draw the board:(defparameter *board-width* 900)

(defparameter *board-height* 500)

(defparameter *board-scale* 64)

(defparameter *top-offset* 3)

(defparameter *dice-scale* 40)

(defparameter *dot-size* 0.05)T he board width and he ight wil l be 900-by-500 pixe ls, which is a good size for playing a gam e in a browser on m ost people ’s

com pute r sc reens. T he board sca le represents ha lf the width of a single hex on the sc reen in pixe ls. T he *top-offset*

variable te l ls us we want three extra hex he ights of free space above the base of the board. W e’ll need this because a hexwith lot of dice on i t wil l have i ts dice st icking out , upward, and we need room for these dice to be visible on the sc reen. T he

*dice-scale* va riable te l ls us tha t a single die wil l be about 40 pixe ls ta l l and wide on the sc reen. Fina lly, we se t *dot-

size* to 0.05, which te l ls us tha t each dot wil l be about 0.05 t im es the size of a die .

Page 447: Land of Lisp - Barski M.D., Conrad

Drawing a Die

Now we’re ready to write a func tion tha t can draw a die . Note tha t we won’t use bi tm aps or anything l ike tha t to draw. Instead,we’re drawing a die “ the hard way,” by rendering i t direc t ly out of raw SVG polygons. Here ’s the code :

(defun draw-die-svg (x y col)

(labels ((calc-pt (pt)(cons (+ x (* *dice-scale* (car pt)))(+ y (* *dice-scale* (cdr pt)))))

(f (pol col)(polygon (mapcar #'calc-pt pol) col)))

(f '((0 . −1) (−0.6 . −0.75) (0 . −0.5) (0.6 . −0.75))(brightness col 40))(f '((0 . −0.5) (−0.6 . −0.75) (−0.6 . 0) (0 . 0.25))col)(f '((0 . −0.5) (0.6 . −0.75) (0.6 . 0) (0 . 0.25))(brightness col −40))

(mapc (lambda (x y)(polygon (mapcar (lambda (xx yy)(calc-pt (cons (+ x (* xx *dot-size*))(+ y (* yy *dot-size*)))))'(−1 −1 1 1)'(−1 1 1 −1))'(255 255 255)))

'(−0.05 0.125 0.3 −0.3 −0.125 0.05 0.2 0.2 0.45 0.45 −0.45 −0.2)'(−0.875 −0.80 −0.725 −0.775 −0.70 −0.625−0.35 −0.05 −0.45 −0.15 −0.45 −0.05))))

T o draw a die , we need to pass in three a rgum ents . T he first two a re the x and y posit ion a t which the die should appear inthe SVG pic ture . T he third is the color we want the die to be . T his func tion wil l take som e l ibe rt ies with tha t color and m odify i ta s needed to give the die a l i t t le shading.

Anything we draw in this func tion wil l need to be rendered in a sca led fashion, based on the *dice-scale* constant we defined.

T here fore , we first de fine a loca l func tion calc-pt tha t sca les a point for us . Since we’l l need to draw severa l sca ledpolygons, le t’s a lso c rea te a convenience func tion, f, tha t runs calc-pt aga inst a l l points in a polygon and then draws i t by ca l l ing

the polygon func tion .A die in our pic ture wil l have three visible faces: the top face , the front face , and the right face . W e draw these by ca ll ing our

func tion f three t im es sta rt ing here and using som e hard-coded coordina tes for the three faces.

T he last thing we need to do is draw the l i t t le dots on the faces of the die . W e do this by mapcing the coordina tes for the

dots aga inst a lam bda func tion tha t can render a dot . T his lam bda func tion uses the *dot-size* va riable to sca le down asquare -shaped polygon tha t represents each dot on the die face . W e could write m ore sophist ica ted code to draw c ircula r and/ore l l ipt ica l dots, but the dots a re so sm all tha t squares look just fine .

L e t’s t ry drawing a die a t x=50 and y=50 with an RGB red (255 0 0) color:> (svg 100 100 (draw-die-svg 50 50 '(255 0 0)))

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="100" width="100"><polygonpoints="50,10 26.0,20.0 50,30.0 74.0,20.0 " style="fill:rgb(255,40,40);stroke:rgb(155,0,0)"></polygon><polygon points="50,30.0 26.0,20.0 26.0,50 50,60.0" style="fill:rgb(255,0,0);stroke:rgb(155,0,0)"></polygon><polygon points="50,30.0 74.0,20.0 74.0,50 50,60.0 " style="fill:rgb(215,0,0);stroke:rgb(115,0,0)"></polygon><polygon points="46.0,13.0 46.0,17.0 50.0,17.0 50.0,13.0 " style="fill:rgb(255,255,255);stroke:rgb(155,155,155)"></polygon><polygon points="53.0,16.0 53.0,20.0 57.0,20.0 57.0,16.0" style="fill:rgb(255,255,255);stroke:rgb(155,155,155)"></polygon><polygonpoints="60.0,18.999998 60.0,23.0 64.0,23.0 64.0,18.999998 "style="fill:rgb(255,255,255);stroke:rgb(155,155,155)"></polygon><polygonpoints="36.0,17.0 36.0,21.000002 40.0,21.000002 40.0,17.0 " style="fill:rgb(255,255,255);stroke:rgb(155,155,155)"></polygon><polygon points="43.0,20.0 43.0,24.0 47.0,24.0 47.0,20.0 " style="fill:rgb(255,255,255);stroke:rgb(155,155,155)"></polygon><polygon points="50.0,23.0 50.0,27.0 54.0,27.0 54.0,23.0 "style="fill:rgb(255,255,255);stroke:rgb(155,155,155)"></polygon><polygon points="56.0,34.0 56.0,38.060.0,38.0 60.0,34.0 " style="fill:rgb(255,255,255);stroke:rgb(155,155,155)"></polygon><polygon points="56.0,46.0 56.0,50.0 60.0,50.0 60.0,46.0 "style="fill:rgb(255,255,255);stroke:rgb(155,155,155)"></polygon><polygon points="66.0,30.0 66.0,34.0 70.0,34.0 70.0,30.0 " style="fill:rgb(255,255,255);stroke:rgb(155,155,155)"></polygon><polygonpoints="66.0,42.0 66.0,46.0 70.0,46.0 70.0,42.0 " style="fill:rgb

Page 448: Land of Lisp - Barski M.D., Conrad

(255,255,255);stroke:rgb(155,155,155)"></polygon><polygon points="30.0,30.030.0,34.0 34.0,34.0 34.0,30.0 " style="fill:rgb(255,255,255);stroke:rgb(155,155,155)"></polygon><polygon points="40.0,46.0 40.0,50.0 44.0,50.0 44.0,46.0 " style="fill:rgb(255,255,255);stroke:rgb(155,155,155)"></polygon></svg>

If you want to see wha t the fina l die looks l ike , just save this gobbledygook to a fi le nam ed die . svg. T hen load the result inFire fox, where you should see the fol lowing pic ture (shown a t a blown-up size ):

Page 449: Land of Lisp - Barski M.D., Conrad

Drawing a Tile

Next, le t’s write the func tion to draw an entire hex t i le , inc luding the base and the dice on the t i le :(defun draw-tile-svg (x y pos hex xx yy col chosen-tile)

(loop for z below 2

do (polygon (mapcar (lambda (pt)(cons (+ xx (* *board-scale* (car pt)))(+ yy (* *board-scale*(+ (cdr pt) (* (- 1 z) 0.1))))))

'((−1 . −0.2) (0 . −0.5) (1 . −0.2)(1 . 0.2) (0 . 0.5) (−1 . 0.2)))

(if (eql pos chosen-tile)(brightness col 100)col)))

(loop for z below (second hex)

do (draw-die-svg (+ xx(* *dice-scale*0.3

(if (oddp (+ x y z))−0.30.3)))(- yy (* *dice-scale* z 0.8)) col)))

T his func tion takes in a lot of pa ram ete rs, because a lot of inform ation is encoded in a single t i le of the board. You’l l lea rn theprec ise m eaning of each of these param ete rs when we draw the board in the next sec t ion.

First , our draw-tile-svg func tion draws the base . T o give the base a m ild 3D look, we’l l draw i t twice , with one leve l stacked

on top of the other. Here is the loop tha t draws the two bases. W ithin tha t loop, we need to draw a hexagona l polygon .

W e m ap a sca ling func tion ac ross the coordina tes so tha t they a re sca led to our *board-scale* va riable . Here you can seethe six points of a hexagon in perspec tive encoded using dec im al nota t ion. T he color of the base wil l be brightened sl ightly if i thas been chosen by the player to perform a m ove . W e do this by inc reasing the brightness of the t i le when c rea ting our polygons

.Afte r we’ve finished drawing the t i le base , we need to draw the dice tha t reside on the t i le . W e do this by looping ac ross the

num ber of dice and then ca ll ing our draw-die-svg func tion . W hen ca lcula t ing the x and y posit ions of the dice , weneed to perform a bi t of sca l ing m ath. T he m ost inte rest ing piece of this m ath is tha t we shift the dice a bi t to the le ft or right ,

depending on whether the sum of the x-, y-, and z -coordina tes for a given die is odd or even . T his m akes the stacks look al i t t le im perfec t and wil l give the stacked dice for the com ple te board a pleasing, na tura l appearance .

Now le t’s ca l l our func tion to draw a finished t i le and see how i t looks. Aga in, just copy the output from this com m and to a fi lenam ed som ething l ike t i le . svg.> (svg 300 300 (draw-tile-svg 0 0 0 '(0 3) 100 150 '(255 0 0) nil))

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="300" width="300"><polygon points="36,143.6 100,124.4164,143.6 164,169.2 100,188.4 36,169.2 " style="fill:rgb(255,0,0);stroke:rgb(155,0,0)">...

Here’s wha t you should see when looking a t the fi le in Fire fox:

Page 450: Land of Lisp - Barski M.D., Conrad
Page 451: Land of Lisp - Barski M.D., Conrad

Drawing the Board

Now we’re ready to write a func tion tha t draws an entire gam e board as an SVG. It wil l be very sim ila r to our draw-boardfunc tion, which we’ve been using to draw the board to the console . It fulfi l ls the sam e role , but sim ply outputs the result a s SVGdata .(defparameter *die-colors* '((255 63 63) (63 63 255)))

(defun draw-board-svg (board chosen-tile legal-tiles)

(loop for y below *board-size*

do (loop for x below *board-size*

for pos = (+ x (* *board-size* y))for hex = (aref board pos)for xx = (* *board-scale* (+ (* 2 x) (- *board-size* y)))for yy = (* *board-scale* (+ (* y 0.7) *top-offset*))for col = (brightness (nth (first hex) *die-colors*)(* −15 (- *board-size* y)))

do (if (member pos legal-tiles)(tag g ()

(tag a ("xlink:href" (make-game-link pos))

(draw-tile-svg x y pos hex xx yy col chosen-tile)))(draw-tile-svg x y pos hex xx yy col chosen-tile)))))

(defun make-game-link (pos)(format nil "/game.html?chosen=˜a" pos))

T he draw-board-svg func tion takes the board as an a rgum ent, but a lso requires two other a rgum ents tha t wil l be im portant for

using the pic ture as the front end of the use r inte rface for our gam e . One a rgum ent is chosen-tile, which indica tes a t i letha t the player has c l icked with the m ouse . W e’re going to color tha t t i le a bi t l ighte r, so the player can te l l tha t the com pute r hasrecognized the se lec t ion. Another a rgum ent is legal-tiles, which indica tes which t i le s the player can lega lly c l ick next .

It so happens tha t SVG pic tures have a fea ture for web l inks, which works just l ike the <a href="..."> hyperl inks in regula rHT ML . If a t i le is a lega l t i le for the player’s next m ove , we’l l wrap the SVG for tha t t i le in such a l ink, m aking i t c l ickable .Having the legal-tiles pa ram ete r le ts us know which t i le s we want to be c l ickable .

T he draw-board-svg func tion consists of a couple of nested loops tha t loop through the y and x coordina tes of thet i le board. For each t i le , we then define a ton of loca l va riables (using the fac i l i ty for loca l va riables in the loop m acro introduced

in Chapte r 10). First , we dec la re pos , which indica tes the posit ion of current t i le in the hex a rray. T hen we fe tch tha t hex.Next, we ca lcula te the pixe l coordina tes for the t i le s, in the variables xx and yy. As you can see , the m ath for these coordina tesge ts a bi t t ricky, since the board is drawn in perspec tive on the sc reen.

T he fina l loca l va riable we define is col, which wil l hold the color of the t i le and dice in the current spot . W e do this by usinga l ist of die colors, which currently holds the colors red (for player A) and blue (for player B). W e a lso darken the color a bi t basedon the y-coordina te using the brightness func tion (discussed in Chapte r 17). T his da rkens the rows in the back a bi t , adding to the3D appearance of our SVG gam e board.

If the current t i le is a m em ber of the lega l t i le s , we’re going to wrap i t in a web l ink, as m entioned previously. In SVG,

this is done with a tag in the form <a xlink:href="...">, which we c rea te here . Notice tha t we a lso wrap each t i le in a<g> tag, which te l ls the SVG rendere r to trea t the polygons in this t i le as a group. T o figure out the ac tua l URL we want to l ink to,we ca ll the make-game-link func tion. T his func tion builds an appropria te URL . You’l l understand the form at of the URL be tte ronce we sta rt wri t ing the code tha t handles the web se rver for our gam e.

Fina lly, we’re ready to ca l l our draw-tile func tion . T here a re two diffe rent ve rsions of the ca l l in our code : one for thehyperl inked version and one for the nonlinked version.

Phew! Now we can fina lly draw a ful l gam e board dynam ica lly, using the SVG form at:> (svg *board-width* *board-height* (draw-board-svg (gen-board) nil nil))

<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="500" width="900"><polygon points="256,185.6 320,166.4384,185.6 384,211.2 320,230.4 256,211.2 "...

If you save the output to board. svg and load i t in Fire fox, he re is wha t you should see :

Page 452: Land of Lisp - Barski M.D., Conrad
Page 453: Land of Lisp - Barski M.D., Conrad

Building the Web Server Interface

Now tha t we’ve com ple ted the graphica l side of Dice of Doom version 3, we’re ready to write the side tha t inte rfaces with theweb se rver.

Page 454: Land of Lisp - Barski M.D., Conrad

Writing O ur Web Request H andler

T he centra l func tion for our web se rver handling is ca l led dod-request-handler. It is the func tion tha t we can pass to the servecom m and in our web se rver l ibra ry, and i t is responsible for handling a l l the web requests com ing from the web browser. Here is thecode for dod-request-handler:(defparameter *cur-game-tree* nil)

(defparameter *from-tile* nil)

(defun dod-request-handler (path header params)

(if (equal path "game.html")

(progn (princ "<!doctype html>")

(tag center ()(princ "Welcome to DICE OF DOOM!")(tag br ())

(let ((chosen (assoc 'chosen params)))

(when (or (not *cur-game-tree*) (not chosen))(setf chosen nil)

(web-initialize))(cond ((lazy-null (caddr *cur-game-tree*))

(web-announce-winner (cadr *cur-game-tree*)))((zerop (car *cur-game-tree*))

(web-handle-human(when chosen(read-from-string (cdr chosen)))))

(t (web-handle-computer))))(tag br ())

(draw-dod-page *cur-game-tree* *from-tile*)))(princ "Sorry... I don't know that page.")))

First , this func tion checks whe ther the current page be ing fe tched from the web se rver is game.html . T his is the page

where our gam e wil l reside on the web se rver. At the top of the page , we spec ify the doctype . W hen done in this way, i t te l lsthe web browser to expec t an HT ML 5-encoded web page . T hen we put in som e sim ple HT ML to cente r the page and print a

welcom e m essage .T he params passed from the web se rver l ibra ry m ay conta in an im portant va lue nam ed chosen, which we fe tch using this l ine

. If the re is no chosen t i le , or i f the gam e tree is currently em pty , i t m eans the player m ust be sta rt ing a brand-new

gam e. If tha t’s the case , we wil l ca l l a func tion nam ed web-initialize .Next, we need to find out whe ther the gam e has ended. W e can te l l this by checking if the l ist of m oves is em pty (which, as you

m ight rem em ber, is stored in the caddr loca tion of the tree ). In tha t case , we’l l announce a winner .Following tha t , we need to see if the current player is player ze ro, which m eans the player is the hum an player. In tha t case ,

we’l l ca l l the func tion web-handle-human to build the rest of the HT ML da ta in the body of the page . W e a lso use the read-from-string func tion to pull the num ber of the chosen t i le from the chosen pa ram ete r, i f i t exists.

In a l l other cases, we know we’re dea ling with a com pute r player and hand over control to web-handle-computer to buildthe rest of the HT ML .

L astly, the dod-request-handler func tion needs to ca l l the draw-dod-page func tion to draw the gam e board, which we do here

.

Page 455: Land of Lisp - Barski M.D., Conrad

Limitations of O ur G ame Web Server

T he l im ita t ions of our gam e web se rver a re quite significant . First of a l l , for sim plic i ty’s sake , the dod-request-handlerfunc tion m akes absolute ly no e ffort to try to de te rm ine from whom the web request is com ing. It behaves as if a l l gam einte rac tions were com ing from a single player, and there fore isn’t a t rue m ult iplayer se rver for Dice of Doom . If m ult iple playerswere to try to play diffe rent gam es a t the sam e t im e , the dod-request-handler would ge t confused and bad things would happen.

It would not be too difficult to expand dod-request-handler into a true web se rver for m ult iple , pa ra l le l gam es. T o do this, wewould need to pull session inform ation out of the header da ta i t rece ives as an a rgum ent from the web se rver, and then a l l va riablesi t re fe rences (such as *cur-game-tree*, for instance) would need to l ive in a hash table , using the session inform ation as a key.T his way, each player would have her own gam e tree , and our engine could then se rve m ult iple gam es in para l le l . T heim plem enta t ion of such a m ult igam e version of the dod-request-handler is “an exerc ise for the reader. ”

Another l im ita t ion of dod-request-handler is tha t i t reads inform ation from the URL using the read-from-string func tion. Asyou’ve lea rned in ea rl ie r chapte rs, this func tion can be com prom ised to run a rbitra ry code in the hands of an experienced (and evil)L isper.

Page 456: Land of Lisp - Barski M.D., Conrad

Initial iz ing a New G ame

Here is the web-initialize func tion, which ini t ia l izes our gam e engine to sta rt a brand-new gam e of Dice of Doom :(defun web-initialize ()

(setf *from-tile* nil)

(setf *cur-game-tree* (game-tree (gen-board) 0 0 t)))As you can see , i t genera tes a random gam e board, builds a tree from it , and then stores the result in the globa l *cur-game-

tree* va riable .

Page 457: Land of Lisp - Barski M.D., Conrad

Announcing a Winner

Here is the func tion tha t announces the winner within the web browser:(defun web-announce-winner (board)

(fresh-line)(let ((w (winners board)))(if (> (length w) 1)(format t "The game is a tie between ˜a" (mapcar #'player-letter w))(format t "The winner is ˜a" (player-letter (car w)))))

(tag a (href "game.html")(princ " play again")))

It is exac tly the sam e as our previous announce-winner func tion, except tha t i t now inc ludes som e extra code a t the end to build

a web l ink , which wil l a l low us to conveniently sta rt a brand-new gam e, since the current gam e has ended.

Page 458: Land of Lisp - Barski M.D., Conrad

H andling the H uman P layer

T he web-handle-human func tion is responsible for c rea ting the HT ML and doing the bookkeeping when the player taking thecurrent turn is the hum an player.(defun web-handle-human (pos)

(cond ((not pos) (princ "Please choose a hex to move from:"))((eq pos 'pass) (setf *cur-game-tree*(cadr (lazy-car (caddr *cur-game-tree*))))

(princ "Your reinforcements have been placed.")(tag a (href (make-game-link nil))(princ "continue")))

((not *from-tile*) (setf *from-tile* pos)(princ "Now choose a destination:"))

((eq pos *from-tile*) (setf *from-tile* nil)(princ "Move cancelled."))

(t (setf *cur-game-tree*(cadr (lazy-find-if (lambda (move)(equal (car move)(list *from-tile* pos)))(caddr *cur-game-tree*))))(setf *from-tile* nil)(princ "You may now ")

(tag a (href (make-game-link 'pass))(princ "pass"))(princ " or make another move:"))))

T he recent choices the hum an has m ade dic ta te wha t this func tion wil l do. T he web-handle-human func tion knows the hum an’schoices by re fe renc ing the m ost recently chosen posit ion, which derives from a variable passed as a pa ram ete r through the webrequest . It a lso can re fe rence the *from-tile* globa l va riable , which te l ls i t which t i le the player ini t ia l ly chose to use as asta rt ing loca tion for a m ove . It needs both of these va lues, since a m ove has both a source loca tion and a dest ina tion loca tion.

If the player has not ye t chosen a loca tion, we want to print a m essage requesting tha t the player choose a hex . If the

player chose to pass, we want to print a m essage saying tha t player’s re inforcem ents have been placed . (Rem em ber tha tre inforcem ents a re placed right a fte r som eone passes. )

Next, we check if the *from-tile* va riable is ni l . If this is the case , i t m eans the player has not ye t chosen a sta rt ing loca tion

for a dice a t tack. If i t ’s nil, we can se t *from-tile* equa l to the loca tion tha t was just se lec ted , a s we ll a s ask the playerto se lec t a dest ina tion.

If the currently se lec ted loca tion is the sam e as the *from-tile* va riable , i t m eans a t i le was se lec ted twice . T his m ust m eanthe player has changed his m ind and wants to undo his se lec t ion. T here fore , we wil l se t *from-tile* to nil and print a

cance lla t ion m essage .In a l l other cases, i t m eans the player has se lec ted two va lid loca tions for the sta rt and end of an a t tack. W e can now advance

t h e *cur-game-tree* to point to the appropria te next t ree inside the lazy l ist of ava ilable m oves . W e want to print a

m essage , a l lowing the player to pass or m ake ye t another a t tack.W e have now com ple ted the code our gam e se rver wil l use to inte rac t with the hum an player. Next, le t’s write a func tion to

handle the com pute r player.

Page 459: Land of Lisp - Barski M.D., Conrad

H andling the Computer P layer

Handling the web inte rface for our com pute r player is pre t ty sim ple . Afte r a l l , com pute r players don’t need any fancy userinte rface stuff to know what’s going on in the gam e. All the web stuff tha t happens when the com pute r is m aking m oves is the resole ly for the benefi t of the hum an player. Here is the web-handle-computer code tha t renders the HT ML in the web inte rface asthe AI player m akes a m ove :(defun web-handle-computer ()

(setf *cur-game-tree* (handle-computer *cur-game-tree*))

(princ "The computer has moved. ")

(tag script ()(princ"window.setTimeout('window.location=\"game.html?chosen=NIL\"',5000)")))

All this func tion does is ca l l our previous handle-computer func tion, which wil l re turn the next branch tha t the com pute r has

se lec ted in the gam e tree . W e use this to upda te our *cur-game-tree* va riable . Next, we print a m essage to sta te tha t the

player has m oved . T he last pa rt of the func tion is a c lever l i t t le gim m ick to spice up our web inte rface a bi t . It puts a

sm idgen of JavaScript in the HT ML of the web page , which forces the web browser to autom atica l ly load a new web page infive seconds. T his m eans tha t as the com pute r AI player m akes i ts m oves, we ge t to see everything happen in a c rude anim ation!

Page 460: Land of Lisp - Barski M.D., Conrad

Drawing the SVG G ame Board from Within the H TM L

W e have only one m ore func tion to write to com ple te version 3 of Dice of Doom : the draw-dod-page func tion. T his func tioninte rfaces our page gam e se rver code with the SVG code tha t draws our board.(defun draw-dod-page (tree selected-tile)

(svg *board-width**board-height*(draw-board-svg (cadr tree)selected-tile

(take-all (if selected-tile(lazy-mapcar

(lambda (move)(when (eql (caar move)

selected-tile)

(cadar move)))(caddr tree))

(lazy-mapcar #'caar (caddr tree)))))))T he m ost com plica ted part of this func tion is the code tha t de te rm ines which t i le s on the board a re lega l t i le s for the player to

c lick . If the player has a lready se lec ted a t i le , we want to find a l l m oves where the sta rt ing posit ion for the m ove

m atches the se lec ted t i le and re turn the dest ina tion posit ion for the given m ove . If the player hasn’t se lec ted a t i le

ye t , we just want to re turn a l l the lega l sta rt ing posit ions .W e have now com ple ted our ful ly graphica l ve rsion of Dice of Doom . L e t’s play!

Page 461: Land of Lisp - Barski M.D., Conrad

P laying Version 3 of Dice of Doom

First , we need to sta rt up our web se rver. Sim ply se rve up our dod-request-handler, and we’re ready to go:> (serve #'dod-request-handler)Now m ove over to Fire fox and go to ht tp: / / loca lhost:8080/gam e.htm l. You should see our gam e in your browser:

W hen you c l ick a t i le , i t is highlighted:

Now you can se lec t a t i le to a t tack. In this exam ple , we’l l choose the stack of two dice to the right of the se lec ted stack:

Page 462: Land of Lisp - Barski M.D., Conrad

Next, le t’s pass our turn by c l icking the pass web l ink. T his wil l cause the re inforcem ent dice to be placed (in this case , only asingle addit iona l die in the upper-le ft corner):

If you now hit continue, you wil l see the gam e cyc le autom atica l ly through the m oves for the com pute r player, in a sim ila rfashion. It wil l keep going on l ike this unti l the re is a winner for the gam e. You can a lways sta rt a new gam e by just going back tothe origina l game.html URL .

T his is m uch nice r than the c rude console inte rface we’ve been using so fa r! But the re a re st i l l a few, fina l im provem ents we’regoing to m ake to pep up Dice of Doom . W e’ll be covering those in the next (and fina l chapte r) of this book.

Page 463: Land of Lisp - Barski M.D., Conrad

What You've Learned

In this chapte r, we discussed how you can genera te inte rac tive graphics in a web browser from a L isp program . Along the way,you lea rned the fol lowing:

You can c rea te a graphica l ve rsion of Dice Of Doom by rendering the board using the SVG form at.T he HT ML 5 standard supports inl ine SVG im ages. You can use this to c rea te an inte rac tive , web-based version of your gam e.T he sim ple web se rver used for our exam ple has severa l l im ita t ions. For exam ple , our gam e cannot be played by m ult iple

players. However, the request handle r could be expanded to a l low for m ult iple , pa ra l le l gam es.

Page 464: Land of Lisp - Barski M.D., Conrad

Chapter 20. M aking Dice of Doom M ore F un

It’s now t im e to c rea te a fina l ve rsion of Dice of Doom . Version 4 of our gam e wil l be m uch m ore fun to play than our ea rl ie rversions.

Although you probably were not aware of i t , we m ade som e m ajor com prom ises in the rules for our gam e to m ake i t easie r toprogram . In this chapte r, we wil l a l low m ore players, add roll ing of the dice , and im plem ent a few m ore changes to m ake Dice ofDoom a m uch m ore inte rest ing gam e.

Page 465: Land of Lisp - Barski M.D., Conrad

Increasing the Number of P layers

T o begin, put a l l the code from the previous chapte r in a fi le nam ed dice_of_doom_v3. l isp (a lso ava ilable from the com panionwebsite ), and then execute the fol lowing com m and:> (load "dice_of_doom_v3.lisp")T he first change we’re going to m ake is to inc rease the num ber of players from two to four. T hree of these wil l be AI opponents,

played by the com pute r. Because of how we’ve writ ten our code so fa r, this requires very l i t t le extra code :(defparameter *num-players* 4)

(defparameter *die-colors* '((255 63 63) (63 63 255) (63 255 63)(255 63 255)))

First , we sim ply change our *num-players* va riable to 4. T hen we need to indica te addit iona l die colors for our new players.T he colors for the four players wil l be red, blue , green, and purple .

It turns out tha t the AI we’ve c rea ted so fa r a lready works just fine in a four-player gam e.Our AI gam e engine wil l use wha t is ca l led a “paranoid stra tegy. ” T his m eans tha t the AI players wil l a lways assum e tha t every

other player (inc luding the hum an) has no other goa l but to—how should I put this?—screw them over persona lly. T his isn’t a badstra tegy to use ; however, a gam e with m ore than two players opens up new possibil i t ie s. For instance , losing players could gang upon a winning player to im prove the ir odds. Our gam e AI isn’t sm art enough to form such packs of coopera tion, but i t ’s good enough.

Now tha t we’ve a lready tweaked som e constants to inc rease the num ber of players, le t’s tweak a couple m ore :(defparameter *max-dice* 5)

(defparameter *ai-level* 2)Here , we’re inc reasing the m axim um num ber of dice on a hex t i le from three to five , and decreasing the leve l of our AI from

four to two. W ith the new rules described in this chapte r, we’l l need to dum b down our AI a bi t to m ake sure i t stays z ippy. Sincethere a re now four com peting players, the AI ac tua lly doesn’t need to be so sm art to cha llenge the hum an opponent.

Page 466: Land of Lisp - Barski M.D., Conrad

Rolling the Dice

I’m sure you’ve probably noticed one obvious flaw in our gam e so fa r: Despite the fac t tha t i t is ca l led Dice of Doom , i t ac tua llyis com ple te ly devoid of any random ness! T he dice a re never rol led, and the la rger stack wil l a lways autom atica l ly win, whichm akes for a pre t ty lam e dice gam e. Now we’re fina lly going to rec t ify this flaw.

In this ve rsion of the gam e, during an a t tack, both pi les of dice a re rol led, and whoever rol ls the highest num ber wins the ba tt le .T ies a re a vic tory for the defender. If the a t tacker loses, tha t player m ust surrender a l l dice from the a t tacking hex except one .

In the l ingo of AI program m ing, this m eans we wil l add chance nodes to our gam e tree . T he way we’re going to im plem ent thisis pre t ty sim ple .

Page 467: Land of Lisp - Barski M.D., Conrad

Building Chance Nodes

E very m ove in our lazy l ist of m oves up to now has a lways had exac tly two i tem s in i t : a desc ript ion of the m ove (a l ist of thesource and dest ina tion of the a t tack, or nil for a passing m ove) and the new node of the gam e tree for when the m ove has beentaken. Now we’re sim ply going to add a third i tem to a m ove , which conta ins the gam e tree for an unsuccessful a t tack. T his m eanstha t each m ove in our m ove l ist wil l double as a chance node , with two possible fol low-up nodes for the next gam e tree , dependingon whether an a t tack is successful .

L e t’s upda te our attacking-moves func tion to add this extra i tem to the m ove so tha t each m ove ac ts as a chance node .(defun attacking-moves (board cur-player spare-dice)

(labels ((player (pos)(car (aref board pos)))(dice (pos)(cadr (aref board pos))))(lazy-mapcan (lambda (src)(if (eq (player src) cur-player)(lazy-mapcan(lambda (dst)(if (and (not (eq (player dst) cur-player))(> (dice src) 1))(make-lazy (list (list (list src dst)(game-tree (board-attack board cur-player src dst (dice src))cur-player(+ spare-dice (dice dst))nil)

(game-tree (board-attack-fail board cur-player src dst (dice src))cur-player(+ spare-dice (dice dst))nil))))(lazy-nil)))(make-lazy (neighbors src)))(lazy-nil)))(make-lazy (loop for n below *board-hexnum*collect n)))))

T he only thing new in this upda ted version of attacking-moves is right he re , where we add a third i tem as we c rea te anew m ove in the gam e tree . T he board in this a l te rna te branch of our chance node is construc ted by ca ll ing the func tion board-attack-fail, which we wil l wri te next .

T he board-attack-fail func tion does exac tly wha t you would expec t: It takes a board and re turns a board tha t has a l l dice butone rem oved from the hex from which a fa i led a t tack origina ted.(defun board-attack-fail (board player src dst dice)

(board-array (loop for pos from 0for hex across boardcollect (if (eq pos src)

(list player 1)

hex))))

Here , we sim ply loop over the board and re turn each hex unm odified , unless i t happens to be the source hex for the a t tack.

In tha t case , we rem ove a l l dice from tha t hex but one .

Page 468: Land of Lisp - Barski M.D., Conrad

Doing the Actual Dice Roll ing

Next, we need to write som e func tions to ac tua lly rol l the dice . Here is a func tion tha t rol ls a pi le of dice :(defun roll-dice (dice-num)

(let ((total (loop repeat dice-numsum (1+ (random 6)))))(fresh-line)

(format t "On ˜a dice rolled ˜a. " dice-num total)

total))First , i t ca lcula tes a tota l count of a pi le of rol led dice by looping once for each die . For each die , i t genera tes a random

num ber from 1 to 6. T hen i t stores the tota l sum in the total va riable . Next, the roll-dice func tion prints a desc ript ive

m essage about the rol l . Fina lly, i t re turns the tota l .Since we’re never going to rol l a pi le of dice in isola t ion, le t’s c rea te another func tion tha t pi ts two piles of dice aga inst each

other:(defun roll-against (src-dice dst-dice)

(> (roll-dice src-dice) (roll-dice dst-dice)))T his sim ply ca l ls roll-dice twice and com pares the tota l of the two rolls. W e’ll want to use this func tion as we trave l a long our

gam e tree to pick e i the r the winning or losing m ove as a turn is chosen by e i the r the hum an or the com pute r.

Page 469: Land of Lisp - Barski M.D., Conrad

Calling the Dice Roll ing Code from O ur G ame Engine

In the context of our gam e engine , rol l ing dice sim ply m eans picking e i the r the winning or losing branch of the chance nodeafte r the hum an or com pute r has chosen a m ove . T his ac t ion is pe rform ed by the pick-chance-branch func tion:

(defun pick-chance-branch (board move)(labels ((dice (pos)(cadr (aref board pos))))(let ((path (car move)))

(if (or (null path) (roll-against (dice (car path))(dice (cadr path))))

(cadr move)

(caddr move)))))

T his func tion takes the current board and a lso the m ove tha t conta ins the chance node tha t needs to be resolved . W hen thepa th inside the m ove is not null, we ca ll roll-against with a count of dice in the source and dest ina tion hexes a long the pa th of

a t tack . W e check for a null pa th because tha t m eans the m ove was a “pass, ” which doesn’t require any dice rol l ing.

If the dice rol l for the a t tack is successful , we rem ove the first child tree from the chance node within the m ove . If the

a ttack is unsuccessful , we re turn the second child of the chance node .Now we need to m ake sure tha t the pick-chance-branch func tion is ca l led when the hum an or com pute r chooses a m ove . First ,

le t’s take ca re of the hum an:(defun handle-human (tree)

(fresh-line)(princ "choose your move:")(let ((moves (caddr tree)))(labels ((print-moves (moves n)(unless (lazy-null moves)(let* ((move (lazy-car moves))(action (car move)))(fresh-line)(format t "˜a. " n)(if action(format t "˜a -> ˜a" (car action) (cadr action))(princ "end turn")))(print-moves (lazy-cdr moves) (1+ n)))))(print-moves moves 1))(fresh-line)

(pick-chance-branch (cadr tree) (lazy-nth (1- (read)) moves))))All we’ve done here is to add a ca l l to pick-chance-branch a t the end of our previous handle-human func tion, a t the point we

need to re turn the child branch of the gam e tree tha t holds the next sta te of the gam e .W e upda te the handle-computer func tion in the sam e way:(defun handle-computer (tree)

(let ((ratings (get-ratings (limit-tree-depth tree *ai-level*) (car tree))))(pick-chance-branch(cadr tree)

(lazy-nth (position (apply #'max ratings) ratings) (caddr tree)))))

Again, we’ve sim ply added a ca l l to pick-chance-branch a t the end of the func tion .It is now possible to play our upda ted Dice of Doom gam e. However, a t this point , the com pute r player wil l play a very poor

gam e, because the AI does not ye t understand tha t the chance nodes exist . It wil l sim ply assum e tha t every a t tack wil l a lways besuccessful , m aking i t m uch too foolhardy to play a decent gam e. W e need to im prove our AI so tha t i t takes into account theroll ing of the dice as i t m akes i ts dec isions.

Page 470: Land of Lisp - Barski M.D., Conrad

Updating the AI

For the AI to be able to dea l with the dice rol ls tha t a re now im portant to our gam e, i t m ust know a l i t t le som ething about thesta t ist ics of dice rol ls. T he following table gives i t the needed sta t ist ica l inform ation:(defparameter *dice-odds* #(#(0.84 0.97 1.0 1.0)

#(0.44 0.78 0.94 0.99)#(0.15 0.45 0.74 0.91)#(0.04 0.19 0.46 0.72)#(0.01 0.06 0.22 0.46)))

T his table conta ins the odds of winning for each possible pa iring of dice in our gam e. T he colum ns represent the a t tacking dice ,sta rt ing with one die . T he rows represent the dest ina tion dice , sta rt ing with two dice (the m inim um dice needed for an a t tack).

T his table te l ls us, for instance , tha t a rol l of two a t tacking dice aga inst one defending die has an 84 percent chance of winning.Four a t tacking dice aga inst three defending dice have a 74 percent chance of winning.

If you rem em ber, the core func tion in our AI code is the get-ratings func tion, which gives a point score to the l ist of possiblefollow-up m oves. W e need to m odify how i t ca lcula tes the score of each possible m ove to take the odds of success of the dice rol linto account. W e a re now going to m ake use of our *dice-odds* table , a s well a s the point scores of the successful or fa i ledoutcom es of each a t tack, to inte rpola te a com bined score for each ava ilable m ove :(defun get-ratings (tree player)

(let ((board (cadr tree)))(labels ((dice (pos)(cadr (aref board pos))))(take-all (lazy-mapcar(lambda (move)(let ((path (car move)))(if path(let* ((src (car path))(dst (cadr path))

(odds (aref (aref *dice-odds*(1- (dice dst)))(- (dice src) 2))))

(+ (* odds (rate-position (cadr move) player))

(* (- 1 odds) (rate-position (caddr move)player))))(rate-position (cadr move) player))))(caddr tree))))))

In our upda ted get-ratings func tion, we look up the odds of each a t tack succeeding from our table . T hen we m ult iply the

odds with the ra t ing for the winning child tree . Addit iona lly, we add in the odds of losing the a t tack (one m inus the odds of

winning) m ult ipl ied by the ra t ing for the losing board posit ion . W e now have an upda ted get-ratings func tion tha tunderstands chance nodes and accounts for them appropria te ly when genera t ing the score for a m ove .

For our gam e AI to be ful ly com patible with chance nodes, we need to m ake one addit iona l sm all change . Our tree -trim m ingfunc tion needs to know about the two branches of the chance node within each m ove , so i t can properly trim both the winning andlosing a l te rna tives for each m ove :(defun limit-tree-depth (tree depth)

(list (car tree)(cadr tree)(if (zerop depth)(lazy-nil)(lazy-mapcar (lambda (move)(cons (car move)

(mapcar (lambda (x)(limit-tree-depth x (1-depth)))(cdr move))))(caddr tree)))))

W e mapcar ac ross the ta i l of each m ove , so trim m ing is pe rform ed on both branches of any chance nodes.

Note

Version 4 of Dice of Doom will not have a lpha-be ta pruning. Perform ing proper a lpha-be ta pruning in the presence of chancenodes is ve ry com plex.

Page 471: Land of Lisp - Barski M.D., Conrad

Improving the Dice of Doom Reinforcement Rules

Until now, the num ber of re inforcem ents a t the end of a player’s turn a lways equa ls the num ber of captured opponent dice , m inusone . T his re inforcem ent rule guaranteed tha t the tota l num ber of dice in a gam e a lways decreases, so tha t the gam e was ce rta in toeventua lly te rm ina te , and the gam e tree was a lways fini te in size .

However, since version 2, our gam e tree has been a lazy tree , so i t is pe rfec tly fine if the tree is infini te . Rem em ber tha t one ofthe m ain benefi ts of lazy eva lua tion is tha t you can have da ta struc tures tha t a re infini te in size .

T here fore , we a re now going to adjust our re inforcem ent rules to m ake our gam e stra tegica lly m ore inte rest ing.According to our new rules, the num ber of re inforcem ent dice wil l equa l the num ber of t i le s in the player’s la rgest contiguous

te rri tory. T his adds a lot of stra tegic depth, because the players m ust constantly dec ide whe ther to risk connec ting the ir te rri tories,or pe rhaps even to sac rifice sm alle r, nonviable te rri tories by sending them on suic ide m issions.

In order to im plem ent this new re inforcem ent rule , le t’s first de fine the func tion get-connected, which re turns a l ist of t i le s tha ta re owned by the current player and a re connec ted as a c luste r of ne ighbors to the ta rge t t i le :(defun get-connected (board player pos)

(labels ((check-pos (pos visited)(if (and (eq (car (aref board pos)) player)(not (member pos visited)))(check-neighbors (neighbors pos) (cons pos visited))visited))

(check-neighbors (lst visited)(if lst(check-neighbors (cdr lst) (check-pos (car lst) visited))visited)))

(check-pos pos '())))T his func tion uses the sam e a lgori thm for finding connec ted t i le s as we used for ca lcula t ing connec tedness in our Grand T heft

W um pus gam e in Chapte r 8. W e traverse through the hexes and the ir ne ighbors recursive ly, while m ainta ining a visited l ist .

T he get-connected func tion accom plishes this by defining two recursive loca l func tions. T he check-pos func tion checks a

single posit ion and appends any new ne ighbors accessible from tha t loca tion to the visi ted l ist . T he check-neighbors func tion checks an entire l ist of ne ighbors, sim ila rly appending new ne ighbors to the visi ted l ist . T hese two func tions ca l l each otherrecursive ly unti l a l l ne ighbors in a c luste r a re found. T o sta rt off this recursive ca lcula t ion, we ca ll the check-pos func tion with

the ta rge t posi t ion and an ini t ia l ly em pty visited l ist .W e can now find c luste rs. However, to find the largest c luste r, we need the largest-cluster-size func tion:(defun largest-cluster-size (board player)

(labels ((f (pos visited best)

(if (< pos *board-hexnum*)(if (and (eq (car (aref board pos)) player)

(not (member pos visited)))

(let* ((cluster (get-connected board player pos))(size (length cluster)))

(if (> size best)

(f (1+ pos) (append cluster visited) size)

(f (1+ pos) (append cluster visited) best)))(f (1+ pos) visited best))best)))(f 0 '() 0)))

T his func tion defines a loca l func tion f, which we’l l use to check every posit ion on the board, while m ainta ining both a l ist of

previously visi ted nodes and the size of the la rgest , best c luste r found so fa r .

As long as the current posi t ion num ber is le ss than the tota l num ber of spots on the board , we continue to check t i le s. If the

current t i le to be checked be longs to the player and a lso has not ye t been visi ted , we’l l ca l l get-connected to re trieve the

c luste r of hexes reachable from this spot . T hen, i f the size of the c luste r is la rger than the best found so fa r , we m ake

this the new best size in our recursive ca l l . Otherwise , we proceed by ca ll ing f while keeping the previous best size .(T he best va riable a t this point wil l hold the best va lue found so fa r from previous i te ra t ions. ) No m atte r wha t happens, however,the pos va riable is inc rem ented with every recursive ca l l to f, so tha t we eventua lly cover the whole board.

Page 472: Land of Lisp - Barski M.D., Conrad

Fina lly, we need to upda te add-new-dice to m ake use of our new rule for choosing the num ber of re inforcem ents:

(defun add-new-dice (board player spare-dice)(labels ((f (lst n)(cond ((zerop n) lst)((null lst) nil)(t (let ((cur-player (caar lst))(cur-dice (cadar lst)))(if (and (eq cur-player player) (< cur-dice *max-dice*))(cons (list cur-player (1+ cur-dice))(f (cdr lst) (1-n)))(cons (car lst) (f (cdr lst) n))))))))(board-array (f (coerce board 'list)

(largest-cluster-size board player)))))

As you can see , the add-new-dice func tion st i l l rece ives spare-dice a s an a rgum ent for com patibi l i ty with our old code ,but now this a rgum ent is sim ply ignored. Instead, the num ber of re inforcem ents added to the board depends on the size of the

la rgest c luste r . Otherwise , the add-new-dice is identica l to our previous version.T his is a l l the code we need to enable the new re inforcem ent rules. Note tha t , due to the design of our code , the AI player has

full access to the gam e tree . Since the gam e tree now conta ins a l l of this new re inforcem ent da ta , the AI wil l autom atica l ly adapti ts playing stra tegy to take into account the new re inforcem ent rules!

Page 473: Land of Lisp - Barski M.D., Conrad

Conclusion

W e’ve gone through quite a long trip as we’ve c rea ted the Dice of Doom gam e, em ploying an im m ense num ber of diffe rentprogram m ing techniques a long the way. W e’ve taken even m ore trips with a l l the other gam es in this book. T hanks for taking thisjourney with m e through the world of L isp program m ing!

I suggest tha t you take a m om ent to enjoy the frui ts of your labor and play a few gam es of the fourth and fina l ve rsion of Dice ofDoom . Aga in, a l l you need to do is se rve up the Dice of Doom request handle r through our web se rver:> (serve #'dod-request-handler)Now you can play Dice of Doom in Fire fox (aga in, a t the address localhost:8080/game.html) a s i t is m eant to be played, with four

players and a l l the new rules we’ve added in this chapte r.

Good luck with a l l your Dice of Doom ba tt les and a l l your future L isp program m ing!

Page 474: Land of Lisp - Barski M.D., Conrad
Page 475: Land of Lisp - Barski M.D., Conrad

Appendix A. Epilogue

Now tha t you’ve worked your way through this book, he re is one fina l reward: A story about the technologies behind the entireL isp fam ily of program m ing languages, se t in the not-too-distant future . . .

Page 476: Land of Lisp - Barski M.D., Conrad
Page 477: Land of Lisp - Barski M.D., Conrad
Page 478: Land of Lisp - Barski M.D., Conrad
Page 479: Land of Lisp - Barski M.D., Conrad
Page 480: Land of Lisp - Barski M.D., Conrad
Page 481: Land of Lisp - Barski M.D., Conrad
Page 482: Land of Lisp - Barski M.D., Conrad
Page 483: Land of Lisp - Barski M.D., Conrad
Page 484: Land of Lisp - Barski M.D., Conrad
Page 485: Land of Lisp - Barski M.D., Conrad
Page 486: Land of Lisp - Barski M.D., Conrad
Page 487: Land of Lisp - Barski M.D., Conrad

F unctional G uild Cruiser

Lisp Dialec tCom m on L isp

Page 488: Land of Lisp - Barski M.D., Conrad

Synopsis

Func tiona l program m ing is a m athem atica l approach to program m ing tha t was pioneered by the c rea tors of L isp. Func tiona lprogram m ing places ce rta in restric t ions on the program m er, but i t can lead to very e legant code . W hen using func tiona lprogram m ing, every variable tha t is used by a given func tion m ust be one of the fol lowing:

A param ete r passed into tha t func tionA loca l va riable c rea ted within tha t func tionA constant

Also, func tiona l program m ing doesn’t a l low a func tion to have side e f fec ts. T his m eans a func tion can’t wri te to the disk, printm essages on the sc reen, or do anything other than re turn a result . T he goa l is to write m ost of a program using “ func tiona l code , ”while re ta ining a teensy bit of code tha t does any dirty, nonfunc tiona l stuff tha t is st i l l needed.

Page 489: Land of Lisp - Barski M.D., Conrad

H ow It K il ls Bugs

W riting code in a func tiona l style guarantees tha t a func tion does only one thing (re turns a va lue) and is dependent on one onlything (the param ete rs passed to i t ). T his m akes i t ve ry easy to debug. No m atte r how m any t im es you run a func tion, as long asyou’re passing i t the sam e da ta , you wil l a lways ge t the sam e result .

E xam ple A-1. E xam ple

(defun unique-letters (name)(concatenate 'string"Hello "(coerce (remove-duplicates name) 'string)))

(defun ask-and-respond ()(princ "What is your name?")(princ (unique-letters (read-line))))

Page 490: Land of Lisp - Barski M.D., Conrad

Explanation

If you ente r this code into the L isp RE PL and execute (ask-and-respond), you wil l be asked for your nam e, and then gree ted byyour nam e but with a l l duplica te le t te rs rem oved. All the hard work in this func tion is handled by unique-letters, which is

writ ten in a func tiona l style . T he dirty work of inte rac ting with the use r, which can’t be writ ten in a pure ly func tiona l way,

is handled by ask-and-respond .

Page 491: Land of Lisp - Barski M.D., Conrad

Weakness

T he m ain weakness of func tiona l program m ing is tha t som e side e ffec ts a re a lm ost a lways necessa ry for a program to ac tua lly dosom ething. T his m eans you can’t wri te a use ful program tha t has the entire ty of i ts code writ ten in the func tiona l style . At least asm all am ount of code wil l be nonfunc tiona l .

Func tiona l program m ing is discussed in Chapte r 14.

Page 492: Land of Lisp - Barski M.D., Conrad
Page 493: Land of Lisp - Barski M.D., Conrad

M acro G uild M elee F ighters

Lisp Dialec tCom m on L isp

Page 494: Land of Lisp - Barski M.D., Conrad

Synopsis

True macros a re one of L isp’s m ost unique and am az ing fea tures. In fac t , the reason L ispers put up with a l l those annoyingparentheses in the ir code is tha t those parentheses enable the awesom e L isp m acro system .

T rue m acros a l low you to add new func tiona li ty to L isp in a very fundam enta l way. E xperienced L ispers can use m acros to m akethe ir L isp com pile r/ inte rpre te r do the ir bidding c leanly and e legantly.

Page 495: Land of Lisp - Barski M.D., Conrad

H ow It K il ls Bugs

By using m acros, an experienced L isper can m inim ize code duplica t ion, and be tte r ta i lor the underlying language to theproblem a t hand. T his leads to c leaner code and fewer bugs.

E xam ple A-2. E xam ple(defmacro three-way-if (expr a b &rest c)

(let ((val (gensym)))`(let ((,val ,expr))

(cond ((and (numberp ,val) (zerop ,val)) ,a)

(,val ,@c)

(t ,b)))))

Page 496: Land of Lisp - Barski M.D., Conrad

Explanation

L isp m acros a re so powerful tha t you can ac tua lly write your own if-then com m and! T he code shown here c rea tes a m acro ca l led

three-way-if tha t has three branches: one for a nil va lue , one for a num erica l ze ro va lue , and one for everything e lse

. For m ost purposes, a func tion l ike this m ight seem stupid, but i f you ever want to write a program tha t constantly needs todist inguish ze ros from nils (or needs to handle som e other dom ain-spec ific headache), you’l l m ake your l i fe m uch easie r by writ inga m acro.

Page 497: Land of Lisp - Barski M.D., Conrad

Weakness

Since L isp m acros a re so powerful , the re is a lways the danger of program m ers abusing them . Overuse of m acros can m ake i t ha rdfor other program m ers to understand your code .

Macros a re discussed in Chapte r 16.

Page 498: Land of Lisp - Barski M.D., Conrad

Restart G uild Armored F ighter

Lisp Dialec tCom m on L isp

Page 499: Land of Lisp - Barski M.D., Conrad

Synopsis

Proper exception handling is extrem ely difficult . T here a re rea l ly only two good approaches: Don’t handle exceptions a t a l l andjust le t your program die when one occurs, or handle every single exception in the m ost direc t and spec ific way possible . But is i tt ruly possible to handle every potentia l exception in your code? If your write Com m on L isp code , i t ’s possible to ge t extrem elyc lose to this idea l goa l .

For exam ple , suppose you write a func tion tha t ra ises the prices on a l ist of widge ts. But then, while the func tion is processingone of the widge ts in the l ist , the re ’s a m em ory a l loca tion e rror. You can’t prepare for this type of e rror ahead of t im e , since i tcould happen anywhere in a program . T his m akes i t im possible to address using tradit iona l exception handling m ethods.

E ven if a func tion lower in the ca l l stack ca tches and resolves the source of the exception, the program st i l l faces an unsolvableproblem : Som e of the widge t prices have been ra ised, while others have not . Com m on L isp, however, has a m echanism foraddressing this problem , ca l led restarts.

In a language tha t supports resta rts, the func tion tha t ra ises the widge t prices can m ake the proc lam ation, “Hey everybody! Ifsom ething bad happens while I’m working on m y widge ts, just use m y resta rt (ca l led try-again) when i t’s sa fe for m e to finish m ywork!” Another func tion, lower in the ca l l t ree , can now handle the e rror, and then ca ll try-again to ensure tha t the widge t priceswon’t becom e corrupt . T his a l lows the func tion to finish ra ising widge t prices a t the exac t point of fa i lure .

In fac t , i f you have a program tha t can’t a fford to shut down (a web se rver, for exam ple), you can st i l l handle a surprisingnum ber of extrem e exceptions in Com m on L isp without ending the program . E ven if the program encounte rs a truly exceptiona lexception, i t can sim ply divert control back to the RE PL . T he program m er can then fix the cause of the exception, access a l ist ofava ilable resta rts, and continue running the program on the spot .

Page 500: Land of Lisp - Barski M.D., Conrad

H ow It K il ls Bugs

By using resta rts and the L isp RE PL , a bug can be fixed in a running program , a l lowing you to “hot sc ript” long-runningapplica t ions with only a negligible inte rruption.

E xam ple A-3. E xam ple(defun raise-widget-prices (widgets)

(when widgets

(loop (restart-case (progn (raise-price (car widgets))

(return))

(try-again () (princ "trying again"))))

(raise-widget-prices (cdr widgets))))

Page 501: Land of Lisp - Barski M.D., Conrad

Explanation

T his is an im plem enta t ion of a func tion tha t ra ises prices on a l ist of widge ts. T he ac tua l work of ra ising the price of a single

widge t is done by the raise-price func tion . T he ca ll to this func tion is protec ted by wrapping i t in a loop and the restart-

case com m and, which dec la res a resta rt ca l led try-again . If the price can be ra ised without problem s, the raise-price

func tion wil l com ple te norm ally, the loop is inte rrupted with a return , and the next i tem in the l ist of widge ts is processed.On the other hand, i f an e rror occurs while ra ising the price on a widge t , another func tion (or the program m er) can a t tem pt to fix

the problem and ca ll the try-again re sta rt to re try the widge t a t the point of fa i lure , which leads to another cyc le through

the loop . T he func tion can then continue down the rest of the l ist , ra ising the prices on the rem aining widge ts .By using resta rts, your code can offe r m ult iple a l te rna tive fol low-up options for coping with an exception, so tha t even the m ost

exceptiona l exceptions can be handled appropria te ly.

Page 502: Land of Lisp - Barski M.D., Conrad

Weakness

E ven though Com m on L isp has one of the m ost advanced exception handling system s in existence , i t is st i l l difficult to handleevery exception appropria te ly in your code . However, resta rts give you the unique abil i ty to fix a running program and a l low i t tocontinue opera t ing, which is usua lly not possible in other languages.

Resta rts a re discussed in Chapte r 14.

Page 503: Land of Lisp - Barski M.D., Conrad
Page 504: Land of Lisp - Barski M.D., Conrad

G ener ic Setter G uild Supply Ship

Lisp Dialec tCom m on L isp

Page 505: Land of Lisp - Barski M.D., Conrad

Synopsis

T o m odify the va lue of a va riable in Com m on L isp, you use setf. However, this com m and a lso has an am az ing spec ia l power:Instead of a va riable nam e, you can pass i t a com plex L isp expression tha t re trieves a va lue . It can then turn tha t expression“ inside out” and use i t to m odify tha t va lue , ra ther than sim ply re trieve i t . T hese types of expressions a re ca l led generic se t ters.

Many com m ands besides setf a lso support generic se t te rs. Using this fea ture , m ost types of da ta struc tures can ge t by without anyspec ific “se t t ing” func tions of the ir own.

Page 506: Land of Lisp - Barski M.D., Conrad

H ow It K il ls Bugs

W hen you have a com plica ted, nested da ta struc ture , i t ’s often easie r to understand code tha t re trieves da ta from a spec ificloca tion than i t is to understand code tha t se ts a va lue a t the sam e loca tion. If you want to se t a va lue a t a spec ific loca tion in acom plica ted struc ture , you usua lly need to work backward through the struc ture to figure out how to change i t . But with genericse t te rs, you can le t L isp handle the hard code for you. Having sim ple r code is a grea t way to fight bugs.

E xam ple A-4. E xam ple

(defparameter foo (list 1 (make-hash-table) 3))

(setf (gethash 'my-key (nth foo 1)) 77)

Page 507: Land of Lisp - Barski M.D., Conrad

Explanation

T he exam ple c rea tes a va riable nam ed foo, which holds a l ist of three i tem s . T he second i tem in the l ist is an em pty hashtable . T hen i t adds a key nam ed my-key with a va lue of 77 to the table inside foo a l l a t once , by putt ing a com plex expression

into setf tha t “ge ts a t” this loca tion .

Page 508: Land of Lisp - Barski M.D., Conrad

Weakness

By m uta ting an exist ing da ta struc ture , generic se t te rs cause a side e ffec t , which viola tes one of the tene ts of func tiona lprogram m ing. T his m eans they can’t be used when program m ing in a pure ly func tiona l style .

Generic se t te rs a re discussed in Chapte r 9.

Page 509: Land of Lisp - Barski M.D., Conrad
Page 510: Land of Lisp - Barski M.D., Conrad
Page 511: Land of Lisp - Barski M.D., Conrad

DSL G uild H ot Rods

Lisp Dialec tCom m on L isp

Page 512: Land of Lisp - Barski M.D., Conrad

Synopsis

Because L isp has such a sim ple syntax (everything is de lim ited with parentheses), i t is easy to use i t to build your own customprogram m ing language , designed for a spec ific dom ain. Such domain-spec if ic languages (DSLs) tend to m ake heavy use of the L ispm acro system . T hey represent an extrem e form of m acro program m ing, t ransform ing L isp into a com ple te ly new program m inglanguage .

Page 513: Land of Lisp - Barski M.D., Conrad

Explanation

T his is an exam ple of code tha t uses a DSL to build an HT ML page . In this case , the page displays “Hello World” in a browser,with the second word rendered in bold. T he html and body com m ands (m acros c rea ted for the HT ML libra ry in Chapte r 16)

genera te opening and c losing tags tha t wil l conta in the body of the page . T hen i t ca l ls the regula r L isp func tion princ to

genera te the text . T he second word is wrapped in another custom DSL com m and, bold , which genera tes opening and c losingbold tags a round the spec ified text .

E xam ple A-5. E xam ple

(html (body (princ "Hello ")

(bold (princ "World!"))))

Page 514: Land of Lisp - Barski M.D., Conrad

Weakness

Since DSL s a re program m ing languages you c rea te a l l by yourse lf, you can defini te ly shoot yourse lf in the foot i f you a ren’tca re ful . It ’s easy to c rea te code in a language tha t is im possible for others (and perhaps even you) to understand.

Chapte r 17 discusses DSL s, inc luding the DSL tha t a l lows you to write HT ML direc tly inside your L isp code , as shown in thisexam ple .

Page 515: Land of Lisp - Barski M.D., Conrad

CLO S G uild Battleship

Lisp Dialec tCom m on L isp

Page 516: Land of Lisp - Barski M.D., Conrad

Synopsis

Com m on L isp has the m ost sophist ica ted objec t-oriented program m ing fram ework of any m ajor program m ing language , ca l ledthe Common Lisp Objec t System (CLOS). It is custom izable a t a fundam enta l leve l using the Metaobjec t P rotocol (MOP ). T here ’srea lly nothing l ike i t anywhere e lse in program m ing. It le ts you c rea te inc redibly com plex software without losing control over thecode .

Page 517: Land of Lisp - Barski M.D., Conrad

H ow It K il ls Bugs

Objec t-oriented programing (OOP ) is a com m only used technique for keeping bugs under control . By writ ing code in an objec t-oriented style , you can decouple diffe rent pa rts of your code . W hen you decouple code , you break your code into logica lcom ponents, which can be tested independently.

E xam ple A-6. E xam ple 1: W rapping Code Around Methods

(defclass widget ()((color :accessor widget-color:initarg :color)))

(defmethod describe-widget ((w widget))(format t "this is a ˜a widget" (widget-color w)))

(defmethod describe-widget :before ((w widget))(add-to-log "Somebody is checking on a widget"))

T he basic concepts behind objec t-oriented program m ing in Com m on L isp a re discussed in Chapte r 9. For de ta i led inform ation onthe design of CL OS, I recom m end reading the CL OS papers com piled a t ht tp: / /www.dream songs. com /CL OS.htm l.

Page 518: Land of Lisp - Barski M.D., Conrad

Explanation

For this exam ple , im agine we run a com pany tha t se l ls widge ts, and we need som e objec t-oriented L isp code to he lp keep track

of them . First , we need to c rea te a new CL OS c lass (ca l led widget) with defclass . It has one property (or slot , in L isp

lingo) describing the widge t’s color. Next, we dec la re a describe-widget, which prints out a desc ript ion of the widge t . Byconvention, a func tion designed to opera te on a spec ific type of objec t is ca l led a method. In this case , the describe-widget isconsidered a m ethod of the widget objec t .

Now suppose we want to write an entry to a log fi le every t im e a use r checks on a widge t . Using the CL OS, we can dec la re one

or m ore be fore methods tha t wil l autom atica l ly be ca l led before the m ain describe-widget m e thod is executed .If we didn’t have before m ethods ava ilable , we would need to dirty up our m ain widge t code to add logging, l ike so:

(defmethod describe-widget ((w widget))

(add-to-log "Somebody is checking on a widget")(format t "this is a ˜a widget" (widget-color w)))

Here , we’ve added the com m and for logging right in the m iddle of the describe-widget m e thod . T his code is a lotuglie r, because writ ing to logs has nothing intrinsica l ly to do with describing a widge t . T he logging in this ve rsion is a lso t ightlycoupled to the m ain code , which m eans we can no longer test the widge t code independently from the debugging code . Using thebefore m ethod leads to c leaner, m ore decoupled code .

Page 519: Land of Lisp - Barski M.D., Conrad

Explanation

T his exam ple dem ontra tes multiple dispatch, a powerful technique for writ ing m ethods tha t a re chosen based on the types of the irparam ete rs.

E xam ple A-7. E xam ple 2: Mult iple Dispa tch

(defclass color () ())

(defclass red (color) ())(defclass blue (color) ())(defclass yellow (color) ())

(defmethod mix ((c1 color) (c2 color))"I don't know what color that makes")

(defmethod mix ((c1 blue) (c2 yellow))"you made green!")

(defmethod mix ((c1 yellow) (c2 red))"you made orange!")

T he exam ple begins by c rea ting a color c lass and a lso defines three derived c lasses: red, green, and blue . T hen wedec la re a mix m e thod, which wil l te l l us wha t happens if we m ix any two colors. By default , when we m ix two colors, i t just says,

“ I don’t know what color tha t m akes” . However, using m ult iple dispa tch, we can de fine more versions of the mix m e thod.

For instance , we can dec la re a version tha t m ixes blue and ye llow , and another version for ye llow and red . Here ’s wha thappens when we ca ll these m ethods with diffe rent colors:> (mix (make-instance 'red) (make-instance 'blue))

"I don't know what color that makes"> (mix (make-instance 'yellow) (make-instance 'red))"you made orange!"

T he im portant thing to note about the exam ple is tha t in order to figure out which m ix m ethod to ca l l in a given si tua tion, theCL OS needs to take into account both of the objec ts passed into the m ethod. It is dispatching to a spec ific im plem enta t ion of them ethod based on the types of multiple objec ts. T his is a fea ture tha t is not ava ilable in tradit iona l objec t-oriented languages, suchas Java or C++.

Page 520: Land of Lisp - Barski M.D., Conrad

Weakness

Opinions vary wide ly in the L isp com m unity as to how la rge a role objec t-oriented techniques should play in program m ing. T hecri t ic s of this style com pla in tha t objec t-oriented techniques force da ta to be hidden away in lot of dispara te places by requiringthem to l ive inside m any diffe rent objec ts. Having da ta loca ted in dispara te places can m ake program s difficult to understand,espec ia l ly if tha t da ta changes over t im e . T here fore , m any L ispers pre fe r to use func tiona l techniques over objec t-orientedtechniques, though the two can often be used toge ther with som e care . None the less, the re a re st i l l m any dom ains in which objec t-oriented techniques a re inva luable , such as in use r inte rface program m ing or sim ula tion program m ing.

Page 521: Land of Lisp - Barski M.D., Conrad
Page 522: Land of Lisp - Barski M.D., Conrad

The Continuation G uild Rocket P ods

Lisp Dialec tSchem e (l im ited support in Com m on L isp with continuation-passing sty le , or through the use of spec ia l l ibra ries)

Page 523: Land of Lisp - Barski M.D., Conrad

Synopsis

In the 1970s, a spec ia l dia lec t of L isp was c rea ted tha t fea tured a part icula rly powerful program m ing fea ture ca l ledcontinuations. Basica l ly, continua tions le t you put “ t im e trave l” into your code . T his a l lows you to do things l ike run program sbackward, sideways, or in other c razy ways. For instance , i t ’s grea t for im plem enting advanced program m ing techniques, such asnonde terminist ic programming. In nonde te rm inist ic program m ing, you write code tha t offe rs the com pute r m ult iple choices for wha tto do next . If one choice isn’t sa t isfac tory, the com pute r can “ roll back t im e” with continua tions to try a diffe rent pa th.

E xam ple A-8. E xam ple(define continuation null)

(define (foo n)

(* (call-with-current-continuation(lambda (c)

(set! continuation c)(+ n 1)))

2))

Note

T his exam ple is in the Schem e L isp dia lec t and won’t run in Com m on L isp.

Page 524: Land of Lisp - Barski M.D., Conrad

H ow It K il ls Bugs

T here a re m any si tua tions where having t im e trave l in your code can m ake the code easie r to understand. T he c lassic exam ple isin a web se rver. Often, a pe rson m ust visi t severa l pages on a web page in order to perform a single ac t ion. W ith a continua tion-aware web se rver, you can write code tha t pre tends these pages were visi ted a l l a t the sam e t im e , m aking your code a lot le ssbuggy. L a te r on, the web se rver uses continua tions to break your code into severa l pa rts (by using the t im e-trave l abil i t ie s ofcontinua tions), taking ca re of a l l the ugly de ta i ls of handling a m ult ipage web ac tion.

Page 525: Land of Lisp - Barski M.D., Conrad

Explanation

In the exam ple , we c rea te a sim ple func tion ca lled foo , which adds one to a num ber, and then doubles i t . For instance ,

running (foo 7) wil l re turn 16. However, inside the func tion, the re is a ca l l to call-with-current-continuation , which

captures the sta te of the func tion before the doubling step. It saves this “m om ent in t im e” in the variable continuation .

T he current sta te of the running program is captured a t this l ine . E verything tha t happens after the continua tion was capturedwill then be executed if we ca ll the captured continua tion. T he only part of the foo com m and tha t happens a fte r the continua tion

was captured is the m ult ipl ica t ion by two . Consequently, the variable continuation is now a t im e m achine tha t we can useto jum p into this past m om ent to switch out the num ber we want to double with another one . So, i f we were to now ca ll(continuation 100), i t would re turn 200 (which is 100 doubled). W e have trave led backward in t im e!

Page 526: Land of Lisp - Barski M.D., Conrad

Weakness

Continua tions a re such an awesom e fea ture tha t they don’t rea l ly have a downside . T he only rea l problem they present is forc rea tors of program m ing languages. T rue continua tions a re technica lly difficult to put into a program m ing language , so fewlanguages support them . Schem e happens to be one of them . T o lea rn m ore about continua tion-based web se rvers, see“Im plem enta t ion and Use of the PL T Schem e W eb Server”by Shriram Krishnam urthi , e t a l .

Page 527: Land of Lisp - Barski M.D., Conrad
Page 528: Land of Lisp - Barski M.D., Conrad
Page 529: Land of Lisp - Barski M.D., Conrad
Page 530: Land of Lisp - Barski M.D., Conrad
Page 531: Land of Lisp - Barski M.D., Conrad

Brevity G uild M icro F ighter

Lisp Dialec tArc L isp (indirec t ly ava ilable in Com m on L isp using custom m acros)

Page 532: Land of Lisp - Barski M.D., Conrad

Synopsis

L isp a l lows you to write code tha t is inc redibly conc ise but doesn’t look l ike your ca t wa lked over your keyboard. (I’m looking a tyou, Perl!) T his is possible because of the various fea tures we’ve a lready m entioned, such as m acros, func tiona l program m ing, andL isp’s dynam ic typing system .

T here is one L isp dia lec t , however, tha t takes this idea to the extrem e: Arc . In fac t , code brevity is the prim ary design goa l forthis language . Paul Graham , the designer of Arc , ana lyzed la rge am ounts of com pute r code in an a t tem pt to figure out whichprim it ive com m ands a re needed to write code tha t is as conc ise as possible , while keeping the code readable .

Page 533: Land of Lisp - Barski M.D., Conrad

H ow It K il ls Bugs

W ith Arc , the goa l is to write program s tha t a re short . It is designed to le t you say what you want to say in the m ost conc ise waypossible , leaving no place for bugs to hide .

E xam ple A-9. E xam ple

(accum a

(for n 1 1000

(unless (some [is 0 (mod n _)] (range 2 (- n 1)))

a.n)))

Note

T his exam ple is in the Arc L isp dia lec t and won’t run in Com m on L isp.

Page 534: Land of Lisp - Barski M.D., Conrad

Explanation

T his exam ple c rea tes a l ist of a l l prim e num bers be tween 1 and 1000, using the na ïve m ethod of checking for sm alle r num berstha t divide evenly into the current loop va lue .

T he accum func tion c rea tes a loca l func tion nam ed a, which is used to collec t any prim es tha t a re found . W e i te ra te

through the integers with a for loop , checking for sm alle r num bers tha t divide evenly into the current va lue of i . If

none a re a re found, i is added to the l ist of prim es , by ca l l ing the func tion a with this new num ber. T he bracke ts, [ ], a re ashortcut for c rea ting a lam bda func tion with one param ete r, which is accessed with the underscore charac te r.

Page 535: Land of Lisp - Barski M.D., Conrad

Weakness

Finding an optim ally conc ise se t of com m ands is difficult . W ith too m any com m ands ava ilable , your code can becom e hard tounderstand, since i t ’s difficult to rem em ber wha t each func tion does. W ith too few com m ands, program s can ge t too bulky. ArcL isp tries to find a happy m edium , a l though there ’s st i l l room for a l te rna tive language designs optim ized for code brevity.

Chapte r 16 dem onstra tes how to use m acros to m ake your code conc ise , and m any other exam ples of L isp’s powers of brevity a reshown in the chapte rs fol lowing tha t discussion.

Page 536: Land of Lisp - Barski M.D., Conrad
Page 537: Land of Lisp - Barski M.D., Conrad

M ulticore G uild F ormation F ighters

Lisp Dialec tClojure L isp (ava ilable in Com m on L isp with the CL -ST M extension)

Page 538: Land of Lisp - Barski M.D., Conrad

Synopsis

Now tha t m ost com pute rs have m ult iple cores, the re is a lot of inte rest in finding e legant ways to write m ult icore /m ult i threadedcode . One popula r approach is to use func tiona l da ta struc tures a long with a software transac tional memory system .

Using software transac tiona l m em ory, you can share com plex da ta struc tures be tween severa l threads, with a guarantee tha t nothread wil l see inconsistent inform ation in the da ta , even if i t t rie s to read shared da ta while another thread is a t tem pting to writeto i t .

Page 539: Land of Lisp - Barski M.D., Conrad

H ow It F ights Bugs

Multi threaded code tends to be very buggy. By using software transac tiona l m em ory, you can grea tly inc rease your odds ofwrit ing bug-free m ult i threaded software .

Page 540: Land of Lisp - Barski M.D., Conrad

Explanation

In this exam ple , we define two bank accounts ca l led checking and savings , with a tota l am ount of $300 be tween them .W e then define a transfer-to-savings func tion, which can be ca l led to m ove m oney from the checking account to the savings

account .E xam ple A-10. E xam ple

(def checking (ref 100))(def savings (ref 200))

(defn transfer-to-savings [n]

(dosync (alter checking - n)(alter savings + n)))

Note

T his exam ple is in the Clojure L isp dia lec t and won’t run in Com m on L isp.

Because this func tion conta ins a dosync block, Clojure wil l m ake sure these two alter opera t ions happen a t the sam em om ent in t im e . Of course , both va lues a ren’t rea l ly a l te red a t the exac t sam e point in t im e , but the language m akes sure i t wil lappear to happen sim ultaneously. If another thread were to read these two accounts a t the sam e t im e , a lso within a dosync block,i t would see exac tly $300 in the com bined accounts, no m atte r how m any t im es e i the r thread checks these va lues.

Page 541: Land of Lisp - Barski M.D., Conrad

Weakness

Software transac tiona l m em ory ca rries a pe rform ance pena lty tha t cance ls out som e of the perform ance ga ins tha t com e withusing m ult iple CPU cores. However, a s the num ber of CPU cores inc reases, this pena lty is le ss of an issue .

Page 542: Land of Lisp - Barski M.D., Conrad

The Lazy G uild F r igate

Page 543: Land of Lisp - Barski M.D., Conrad

Lisp Dialec t

Clojure (ava ilable in Com m on L isp with the Series l ibra ry, CL AZ Y libra ry, or custom m acros)

Page 544: Land of Lisp - Barski M.D., Conrad

Synopsis

A lazy program m ing language wil l pe rform a ca lcula t ion only i f the com pile r de te rm ines i t is absolute ly necessa ry to produce avisible result . Clojure is the m ost popula r L isp dia lec t to inc lude lazy program m ing as a prim ary fea ture . However, l im ited form sof lazy program m ing a re com m on in a l l L isp dia lec ts.

Page 545: Land of Lisp - Barski M.D., Conrad

H ow It K il ls Bugs

L azy languages le t you c rea te infini te ly big da ta struc tures (as long as you don’t t ry to use all of the da ta ), which a l lows m ore ofyour code to be form ula ted as transform ations of la rge da ta struc tures. In genera l , i t is easie r to debug da ta struc tures than i t is todebug a lgori thm s. Algori thm s involve steps tha t unfold over t im e , and to understand them , you usua lly need to watch them as theyexecute . Da ta , on the other hand, exists independently of t im e , which m eans you can find bugs in a da ta struc ture just by lookinga t i t .

E xam ple A-11. E xam ple

(take 20 (filter even? (iterate inc 0) ))

Note

T his exam ple is in the Clojure L isp dia lec t and won’t run in Com m on L isp.

Page 546: Land of Lisp - Barski M.D., Conrad

Explanation

T his code re turns the first 20 even posit ive integers. T o do this, i t fi rst c rea tes an infini te l ist of a l l posi t ive integers ,

using the iterate func tion to c rea te a l ist of integers sta rt ing a t ze ro. T hen i t fi l te rs out the even num bers . Fina lly, i t takes

the first 20 num bers from tha t result . Unti l the fina l take com m and, the da ta struc tures be ing opera ted on a re theore t ica l lyinfini te . However, since Clojure is a lazy language , i t instantia tes these da ta struc tures only on an as-needed basis. T his m eans tha tonly the first 20 such num bers a re ever genera ted. (And even then, they a re genera ted only if we ac tua lly use the fina l va luesom ehow, such as print ing i t to the sc reen. )

Page 547: Land of Lisp - Barski M.D., Conrad

Weakness

Since a lazy program m ing language chooses the order in which your code is run, i t can lead to debugging headaches if you try totrace your code as i t is running.

Chapte r 18 discusses lazy program m ing.

Page 548: Land of Lisp - Barski M.D., Conrad
Page 549: Land of Lisp - Barski M.D., Conrad

Index

A note on the digital index

A link in an index entry is displayed as the sec tion t i t le in which tha t entry appears. Because som e sec tions have m ult iple indexm arkers, i t is not unusua l for an entry to have severa l l inks to the sam e sec tion. Clicking on any l ink wil l take you direc t ly to theplace in the text in which the m arker appears.

Symbols

#S pre fix, for struc tures, W orking with Struc tures, W orking with Struc tures#\newline , Sta rt ing with print and read, Sta rt ing with print and read#\space , Sta rt ing with print and read, Sta rt ing with print and read#\tab, Sta rt ing with print and read, Sta rt ing with print and read& body keyword, How Macros Are T ransform ed, How Macros Are T ransform ed() pa rentheses, T he Guess-My-Num ber Gam e, An Alte rna tive Globa l Variable Definit ion Func tion , An Alte rna tive Globa l

Variable Definit ion Func tion, Basic L isp E tique tte , Defining the sta rt-over Func tion, T he Building Blocks of L isp Syntax, MakingDecisions with Condit ions

em pty l ists, Basic L isp E tique tte , Making Dec isions with Condit ionssym m etry of ni l and, Making Dec isions with Condit ions

for ca l l ing com m ands and func tions, T he Guess-My-Num ber Gam e, An Alte rna tive Globa l Variable Definit ion Func tionfor l ist of dec la red variables in le t , Defining the sta rt-over Func tionfor organiz ing code into l ists, T he Building Blocks of L isp Syntax

*board-sca le* variable , Drawing a T ile , Drawing a T ile*dice-sca le* variable , Drawing the Gam e Board Using the SVG Form at, Drawing the Gam e Board Using the SVG Form at*from -ti le* variable , Handling the Hum an Player, Handling the Hum an Player*num -players* variable , Inc reasing the Num ber of Players, Inc reasing the Num ber of Players*print-c irc le* variable , Circula r L ists, Circula r L ists*standard-output* variable , Building a More Com plica ted SVG E xam ple , Building a More Com plica ted SVG E xam ple*top-offse t* variable , Drawing the Gam e Board Using the SVG Form at, Drawing the Gam e Board Using the SVG Form at404 e rror page , Building a Dynam ic W ebsite:@ flag, for colum ns in tables, Just ifying Output: if-exists keyword param ete r, W orking with Files, W orking with Files: ini t ia l-va lue keyword param ete r, Sequence Func tions for Ite ra t ing Across a Sequence , Sequence Func tions for Ite ra t ing Across a

Sequence:junk-a llowed param ete r, Decoding the Values of Request Param ete rs, Decoding the Values of Request Param ete rs:pre t ty param ete r, Convert ing Node Identifie rs, Convert ing Node Identifie rs:radix param ete r, Decoding the Values of Request Param ete rs, Decoding the Values of Request Param ete rs: test keyword param ete r, T he edges-to-a l ist Func tion, T he edges-to-a l ist Func tion, Growing Plants in Our W orld, Growing Plants

in Our W orldto use equa l , Growing Plants in Our W orld, Growing Plants in Our W orld

˜$ control sequence , T he Control String Param ete r, Control Sequences for Form att ing Floa ting-Point Num bers˜% control sequence , Print ing Mult iple L ines of Output˜& control sequence , Print ing Mult iple L ines of Output˜: ; control sequence , Ite ra t ing T hrough L ists Using Control Sequences˜< control sequence , Just ifying Output˜> control sequence , Just ifying Output˜a control sequence , T he Control String Param ete r, T he Control String Param ete r˜b control sequence , Control Sequences for Form att ing Num bers, Control Sequences for Form att ing Num bers˜d control sequence , Control Sequences for Form att ing Num bers, Control Sequences for Form att ing Num bers˜f control sequence , Control Sequences for Form att ing Floa ting-Point Num bers, Control Sequences for Form att ing Floa ting-Point

Num bers˜t control sequence , Print ing Mult iple L ines of Output , Print ing Mult iple L ines of Output˜x control sequence , Control Sequences for Form att ing Num bers, Control Sequences for Form att ing Num bers˜{ control sequence , Just ifying Output˜} control sequence , Just ifying Output

A

ab-ge t-ra t ings-m ax func tion, Alpha Be ta Pruningab-ge t-ra t ings-m in func tion, Alpha Be ta Pruningab-ra te -posit ion func tion, Alpha Be ta Pruningacadem ic research, W here Did L isp Com e From ?accum func tion, Brevity Guild Micro Fighte raccum ula tor, T a il Ca ll Optim iza tionacross in loop m acro, Using Mult iple for Clauses, De te rm ining the W inneradd func tion, predica tes in, Crea ting Your Own Generic Func tions with T ype Predica tesadd-cops func tion, Building the Fina l E dges for Congestion City, T he edges-to-a l ist Func tionadd-new-dice func tion, Finding the Neighbors, T a il Ca ll Optim iza tion, Im proving the Dice of Doom Reinforcem ent Rulesadd-passing-m ove func tion, Genera ting a Gam e T ree , Mapping and Searching Across L azy L istsadd-plants func tion, Growing Plants in Our W orld, Handling Anim al Reproduc tionadd-two func tion, Code Com posit ion with Im pera tive Code

Page 550: Land of Lisp - Barski M.D., Conrad

add-widge t func tion, Ana tom y of a Program W rit ten in the Func tiona l Style , Ana tom y of a Program W rit ten in the Func tiona lStyle

AI (a rt ific ia l inte l l igence), W here Did L isp Com e From ?alists. See assoc ia t ion l ists (a l ists), ANSI Com m on L ispAllegro Com m on L isp, Ge tt ing Sta rted with CL ISPalpha be ta pruning, W inning by a L ot vs. W inning by a L it t le , Upda ting the AI

and chance nodes, Upda ting the AIa lphanum ericp func tion, Convert ing Node Identifie rsa lways in loop m acro, Using Mult iple for ClausesAm azon S3, Re turning Mult iple Valuesanaphoric m acros, A Macro for Spli t t ing L istsand in loop m acro, Using Mult iple for Clausesand opera tor, Branching with caseannounce-winner func tion, De te rm ining the W innerANSI Com m on L isp (CL ), Ge tt ing Sta rted with L isp, A T a le of T wo L ispsappend func tion, Convert ing the E dges to Descript ions, Convert ing the E dges to Descript ions, Building the Nodes for Congestion

Cityappend in loop m acro, Using Mult iple for Clausesapply func tion, Convert ing the E dges to Descript ionsapt-ge t insta l l c l isp, Insta l l ing CL ISPARC assem bly, W here Did L isp Com e From ?Arc L isp dia lec t , A T a le of T wo L isps, Crea ting the tag Macro, How It Kil ls Bugsare f func tion, W orking with Arrays, Using a Generic Se tte r

and perform ance , Using a Generic Se tte ra rrayp func tion, Sequence Func tions for Ite ra t ing Across a Sequencearrays, Advanced Data types and Generic Program m ing, Using a Generic Se tte r, W hen to Use Struc tures, Sequence Func tions for

Ite ra t ing Across a Sequence , Globa l Variables for the Player and Monste rsfor m onste rs, Globa l Variables for the Player and Monste rssequence func tions for, W hen to Use Struc turessum func tion for, Sequence Func tions for Ite ra t ing Across a Sequencevs. l ists, Using a Generic Se tte r

a rt ific ia l inte l l igence (AI), W here Did L isp Com e From ?as in loop m acro, Using Mult iple for ClausesASCII code , Decoding the Values of Request Param ete rs, Representing the Gam e Board

code-char func tion to convert , Representing the Gam e Boardash (a ri thm etic shift) func tion, Basic L isp E tique tteassem blers, W here Did L isp Com e From ?assem bly languages, W here Did L isp Com e From ?assoc func tion, Describing the Scenery with an Assoc ia t ion L ist , Picking Up Objec ts, Assoc ia t ion L istsassoc ia t ion l ists (a l ists), Basic Requirem ents, Circula r L ists, T he edges-to-a l ist Func tion, T he add-cops Func tion, T he add-cops

Func tion, Drawing a City from Part ia l Knowledge , W orking with Files, Decoding L ists of Request Param ete rs, Crea ting the tagMacro

a ttributes for print-tag, Crea ting the tag Macrofor nodes in c i ty, T he add-cops Func tionfor scenery descript ion, Basic Requirem entsnested, T he add-cops Func tionof known nodes, Drawing a City from Part ia l Knowledgeweb request pa ram ete rs in, Decoding L ists of Request Param ete rswrit ing to fi le , W orking with Files

* (aste risk), in variable nam es, T he Guess-My-Num ber Gam easte risk (*), in variable nam es, T he Guess-My-Num ber Gam e@ (a t), in control sequence param ete rs, T he Control String Param ete ra t (@ ), in control sequence param ete rs, T he Control String Param ete ra t-loc -p func tion, L ist ing Visible Objec tsAttack of the Robots! gam e, A Crazy Form att ing T rick for Crea ting Pre t ty T ables of Da taa ttacking-m oves func tion, Ca lcula t ing Passing Moves, Dice of Doom , Version 2, Building Chance NodesAutocode , W here Did L isp Com e From ?

B

` (backquote ), Describing the Pa ths, How Macros Are T ransform edfor enabling switching from da ta to code m ode , Describing the Pa ths

backquote (`), Describing the Pa ths, How Macros Are T ransform edfor enabling switching from da ta to code m ode , Describing the Pa ths

\ (backslash), for escaped charac te rs, Num bersbackslash (\), for escaped charac te rs, Num bersbe low in loop m acro, T he loop Macrobidirec t iona l stream , Sending a Message over a Socke tbigger func tion, Defining the guess-m y-num ber Func tionbinary sea rch, T he Guess-My-Num ber Gam e, Defining the guess-m y-num ber Func tionbinary, num ber display as, Control Sequences for Form att ing Num bersblocking opera t ion, Sending a Message over a Socke tboard-a rray func tion, Representing the Gam e Boardboard-a t tack func tion, Finding the Neighbors

Page 551: Land of Lisp - Barski M.D., Conrad

board-a t tack-fa i l func tion, Building Chance NodesBoolean va lues, m anipula t ing, Branching with casebranching, T he Com m and T ha t Does It All : cond, T he Com m and T ha t Does It All : cond

with case form , T he Com m and T ha t Does It All : condbreaking out of loop, Breaking out of a L oop E arlybrevity of code , Brevity Guild Micro Fighte rbrightness func tion, Using the tag Macro to Genera te HT MLbug fighte rs, E pilogue , Synopsis, Synopsis, Synopsis, Synopsis, Synopsis, CL OS Guild Ba tt leship, Synopsis, Mult icore Guild

Form ation Fighte rs, Mult icore Guild Form ation Fighte rs, L isp Dia lec tClojure L isp, Mult icore Guild Form ation Fighte rscom ic book, E pilogueCom m on L isp Objec t System (CL OS), CL OS Guild Ba tt leshipcontinua tions, Synopsisdom ain-spec ific language , Synopsisexception handling, Synopsisfunc tiona l program m ing, Synopsisgeneric se t te rs, Synopsislazy eva lua tion, L isp Dia lec tm acros, Synopsis

bugs, func tiona l program m ing to reduce , W hy Func tiona l Program m ing Is Crazyby in loop m acro, Using Mult iple for Clauses

C

C++ language , W here Did L isp Com e From ?, W here Does L isp Get Its Power? , Syntax and Sem antics, T he Magic of L isp Macros#define direc t ive , T he Magic of L isp Macros

cache m isses, pe rform ance im pac t , Re turning Mult iple Valuescached results, c lea ring, Alpha Be ta Pruningcadadar func tion, Nested L istscadadr func tion, Nested L istscadr func tion, T he cons Func tionca lc -pt func tion, Drawing the Gam e Board Using the SVG Form atcapita l ized text , convert ing a l l caps to, W rit ing a gam e-print Func tioncapturing console output , Capturing the Console Outputcar func tion, T he cons Func tion, Convert ing the E dges to Descript ionscase form , branching with, T he Com m and T ha t Does It All : condcase of text , adjust ing, W rit ing a gam e-print Func tioncase-insensit ivi ty, of sym bols, T he Building Blocks of L isp Syntaxcase-sensit ive sym bols, Sta rt ing with print and readcdr func tion, T he cons Func tion, Building the Nodes for Congestion Cityce lls, re trieving i tem from first slot , T he cons Func tioncente red colum ns, Just ifying Outputcha in of cons ce l ls, T he cons Func tion, E xotic L istschance nodes, in gam e tree , Inc reasing the Num ber of Playerschar-downcase func tion, W rit ing a gam e-print Func tionchar-equa l func tion, Com paring Stuff: eq, equa l , and Morechar-upcase func tion, W rit ing a gam e-print Func tioncharac te rp func tion, Sequence Func tions for Ite ra t ing Across a Sequencecharac te rs, Com paring Stuff: eq, equa l , and More , Com paring Stuff: eq, equa l , and More , Sta rt ing with print and read, Control

Sequences for Form att ing Num berscom parison, Com paring Stuff: eq, equa l , and Morefor padding num bers, Control Sequences for Form att ing Num bersl i te ra l , Sta rt ing with print and read

charge func tion, L e t 's Hunt Som e W um pus!chosen-t i le pa ram ete r, Drawing the BoardChurch, Alonzo, W hat Is Func tiona l Program m ing?c irc le func tion, Crea ting SVG-Spec ific Macros and Func tionsc ircula r l ists, Pa irsCISC (com plex instruc tion se t com pute r), W here Did L isp Com e From ?city.dot . png pic ture , Ini t ia l iz ing a New Gam e of Grand T heft W um pusCL (Com m on L isp), Ge tt ing Sta rted with L isp, A T a le of T wo L isps, T a il Ca ll Optim iza tion, Func tiona l Guild Cruise r

basics, Func tiona l Guild Cruise rta i l ca l l optim iza tion support , T a il Ca ll Optim iza tion

c l-socke ts, W orking with Socke tsc l ient , for socke t connec tion, Socke t Connec tionsCL ISP, ANSI Com m on L isp, Ge tt ing Sta rted with CL ISP, Sta rt ing Up CL ISP, Sta rt ing Up CL ISP, Circula r L ists

insta l l ing, ANSI Com m on L ispprint ing of c ircula r l ists, Circula r L istsshutt ing down, Sta rt ing Up CL ISPsta rt ing, Sta rt ing Up CL ISP

Clojure L isp, A T a le of T wo L isps, Adding L azy E va lua tion to L isp, Crea ting a L azy L ists L ibra ry, Mult icore Guild Form ationFighte rs, L isp Dia lec t

and lazy eva lua tion, Adding L azy E va lua tion to L isp, L isp Dia lec tlazy sequences, Crea ting a L azy L ists L ibra ry

Page 552: Land of Lisp - Barski M.D., Conrad

CL OS (Com m on L isp Objec t System ), W hen to Use Struc tures, CL OS Guild Ba tt leshipc losing tag in XML , Crea ting XML and HT ML with the tag Macroc losingp predica te , Crea ting XML and HT ML with the tag Macroc losures, Closures, Crea ting the lazy and force Com m andsClozure CL , Gett ing Sta rted with CL ISPcluste rs, finding in Dice of Doom , Im proving the Dice of Doom Reinforcem ent Rulescm d variable , W rit ing a Custom read Func tionCMUCL , Gett ing Sta rted with CL ISPCOBOL , W here Did L isp Com e From ?code , Num bers, Reading and Print ing Stuff the W ay Hum ans L ike It , Brevity Guild Micro Fighte r, Brevity Guild Micro Fighte r

brevity, Brevity Guild Micro Fighte rsym m etry be tween da ta and, Reading and Print ing Stuff the W ay Hum ans L ike Itvs. da ta , Num bers

code com posit ion, Higher-Order Program m ingcode m ode , How L isp Dist inguishes Be tween Code and Data , Code Mode , Describing the Pa ths

backquote (`) for enabling switching to, Describing the Pa thscode-char func tion, Decoding the Values of Request Param ete rs, Representing the Gam e Boardcoerce func tion, W rit ing a gam e-print Func tion, Decoding the Values of Request Param ete rscollec t c lause in loop, L ooping with the loop Com m and, Breaking out of a L oop E arly: (colon), for keyword param ete rs, W alking Around in Our W orld, Understanding Keyword Param ete rscolon (:), for keyword param ete rs, W alking Around in Our W orld, Understanding Keyword Param ete rscolor, Using the tag Macro to Genera te HT ML , Drawing the Board, Drawing the Board

for dice , Drawing the Boardm anipula t ing, Using the tag Macro to Genera te HT ML

colum ns in table , cente red, Just ifying Outputcom ic book, If L isp Is So Grea t , W hy Don't More People Use It? , E pilogue

bug fighte rs, E piloguecom m and-line inte rface , Inte rac ting with the W orld: Reading and Print ing in L isp, Print ing and Reading T ext

print ing to sc reen, Print ing and Reading T extcom m ands, adding to perm itted l ist , A Com m and for W eldingCom m on L isp (CL ), Ge tt ing Sta rted with L isp, A T a le of T wo L isps, T a il Ca ll Optim iza tion, Func tiona l Guild Cruise r

basics, Func tiona l Guild Cruise rta i l ca l l optim iza tion support , T a il Ca ll Optim iza tion

Com m on L isp HyperSpec , T wo More Im portant Sequence Func tions, A Crazy Form att ing T rick for Crea ting Pre t ty T ables of Da taon control sequences, A Crazy Form att ing T rick for Crea ting Pre t ty T ables of Da ta

Com m on L isp Objec t System (CL OS), W hen to Use Struc tures, CL OS Guild Ba tt leshipcom m unica tion, with other ne twork com pute rs, W orking with Socke tscom parison, Using Func tions T ha t Re turn More than Just the T ruth, Com paring Stuff: eq, equa l , and More , Com paring Stuff: eq,

equa l , and Moreeql for num bers and charac te rs, Com paring Stuff: eq, equa l , and Moreof sym bols, Com paring Stuff: eq, equa l , and More

com pile r, W here Did L isp Com e From ?, Crea ting Your Own Generic Func tions with T ype Predica tesversions of func tion for, Crea ting Your Own Generic Func tions with T ype Predica tes

com plex instruc tion se t com pute r (CISC), W here Did L isp Com e From ?com puta tion, de layed, Crea ting a Pic ture of Our Graphcom pute r, a s gam e opponent, T rying Out the Hum an vs. Hum an Version of Dice of Doomconca tena te com m and, W rit ing a Custom read Func tioncond com m and, T he Com m and T ha t Does It All : cond, Handling Anim al Motioncondit ions, t ricks with, Branching with caseCongestion City, T he Grand T heft W um pus Gam e, T he Grand T heft W um pus Gam e, T he Grand T heft W um pus Gam e, L ooping

with the loop Com m and, Preventing Islands, T he add-cops Func tion, Ini t ia l iz ing a New Gam e of Grand T heft W um pus, Drawing aCity from Part ia l Knowledge , Drawing Only the Known Parts of the City

building fina l edges, Preventing Islandsdefining edges, T he Grand T heft W um pus Gam edrawing m ap, Ini t ia l iz ing a New Gam e of Grand T heft W um pus, Drawing a City from Part ia l Knowledge

from part ia l knowledge , Drawing a City from Part ia l Knowledgenodes for, T he add-cops Func tionpreventing islands, L ooping with the loop Com m andwalking a round town, Drawing Only the Known Parts of the City

connec t-a l l-islands func tion, Preventing Islandsconnec t-edge-l ist func tion, Building the Fina l E dges for Congestion Cityconnec t-with-bridges func tion, Preventing IslandsConrad’s Rule of T hum b for Com paring Stuff, Com paring Stuff: eq, equa l , and Morecons ce l ls, L ists in L isp, Cons Ce lls, Nested L ists, E xotic L ists

in nested l ists, Nested L istscons func tion, Cons Ce llsconses, eq for com paring, Com paring Stuff: eq, equa l , and Moreconsing, T he cons Func tionconsole output , capturing, Capturing the Console Outputconsole stream s, W orking with Stream sconsp func tion, Sequence Func tions for Ite ra t ing Across a Sequenceconstants, for gam e board dim ensions, Drawing the Gam e Board Using the SVG Form atcontinua tions, Synopsiscontrol sequences, Ana tom y of the form at Func tion, Control Sequences for Form att ing Num bers, Print ing Mult iple L ines of

Page 553: Land of Lisp - Barski M.D., Conrad

Output, Just ifying Output , A Crazy Form att ing T rick for Crea ting Pre t ty T ables of Da taCom m on L isp HyperSpec on, A Crazy Form att ing T rick for Crea ting Pre t ty T ables of Da tafor form att ing num bers, Control Sequences for Form att ing Num bersfor new l ines, Print ing Mult iple L ines of Outputi te ra t ing through l ists with, Just ifying Output

control string param ete r, for form at func tion, Ana tom y of the form at Func tioncopy-l ist func tion, Handling Anim al Reproduc tioncopy-struc ture func tion, problem s from , Handling Anim al Reproduc tioncount func tion, W orking with Sequencescount in loop m acro, Using Mult iple for Clausescounting from sta rt ing point to ending point , Counting from a Sta rt ing Point to an E nding Pointcurrenc ies, form att ing, Control Sequences for Form att ing Floa ting-Point Num bers

D

da t a , Num bers, Num bers, Reading and Print ing Stuff the W ay Hum ans L ike It , Visua liz ing T ree-l ike Data , W hen to UseStruc tures

generic process for handling, W hen to Use Struc turessym m etry be tween code and, Reading and Print ing Stuff the W ay Hum ans L ike Itt ree -l ike , Visua liz ing T ree-l ike Datavs. code , Num bers

da ta m ode , How L isp Dist inguishes Be tween Code and Data , Da ta Mode , Describing the Pa thsbackquote (`) for enabling switching to, Describing the Pa ths

da ta struc tures, se lf-re fe rentia l , Circula r L istsdead anim als, in evolving environm ent, Handling Anim al Reproduc tiondead m onste rs, checking for, Monste r Managem ent Func tionsDebian-based L inux m achine , CL ISP on, ANSI Com m on L ispdebugging, W orking with L ong Strings, Func tiona l Guild Cruise r, Func tiona l Guild Cruise r

in func tiona l program m ing, Func tiona l Guild Cruise rstring stream s and, W orking with L ong Strings

decf func tion, T he Monste rsdec im al num ber, va lue displayed as, Control Sequences for Form att ing Num bersdec im al point , and num ber type , Sym bolsdec la ra t ion, of func tion, Defining L oca l Func tions in L ispdecode-param func tion, Request Param ete rsdefault , code m ode as, Code Modedefine-condit ion func tion, E rror Handling in Com m on L ispdefm acro com m and, A Sim ple L isp Macro, Macro E xpansiondefm ethod com m and, Crea ting Your Own Generic Func tions with T ype Predica tes, T he Monste rsdefparam ete r com m and, T he Guess-My-Num ber Gam e, An Alte rna tive Globa l Variable Definit ion Func tion , T he Grand T heft

W um pus Gam edefstruc t com m and, A Faste r Grand T heft W um pus Using Hash T ables, W orking with Struc tures, Crea ting Your Own Generic

Func tions with T ype Predica tes, Globa l Variables for the Player and Monste rs , T he Monste rs, T he Generic Monste r, T he Malic iousHydra , T he Slim y Slim e Mold, T he Slim y Slim e Mold, Handling Anim al Motion

for brigand, T he Slim y Slim e Moldfor hydra , T he Malic ious Hydrafor sl im e m old, T he Slim y Slim e Moldto inc lude m onste r type fie lds, T he Generic Monste r

defun com m and, Basic L isp E tique tte , Defining the guess-m y-num ber Func tiondefvar com m and, An Alte rna tive Globa l Variable Definit ion Func tionde layed com puta tion, Crea ting a Pic ture of Our Graphdepreca ted func tion, Convert ing Node Identifie rsdepth-first sea rch, Alpha Be ta Pruningdescribe-loca tion func tion, Describing the Scenery with an Assoc ia t ion L istdescribe-obj func tion, L ist ing Visible Objec tsdescribe-objec ts func tion, L ist ing Visible Objec tsdescribe-pa th func tion, Describing the Pa thsdescribe-pa ths func tion, Describing the Pa ths, Convert ing the E dges to Descript ions, Joining the Descript ionsdest ina tion param ete r, for form at func tion, Ana tom y of the form at Func tionDewdney, A.K. , “Sim ula ted evolution; where in bugs lea rn to hunt bac te ria” , E verything You E ver W anted to Know About loopDice of Doom gam e, Dice of Doom , a Gam e W rit ten in the Func tiona l Style , T he Rules of Dice of Doom , T he Rules of Dice of

Doom , A Sam ple Gam e of Dice of Doom , Defining Som e Globa l Variables, Representing the Gam e Board, Representing the Gam eBoard, Genera ting a Gam e T ree , Genera ting a Gam e T ree , Ca lcula t ing Passing Moves, Ca lcula t ing Attacking Moves, Finding theNeighbors, Re inforcem ents, Re inforcem ents, T rying Out Our New gam e-tree Func tion, T rying Out Our New gam e-tree Func tion,T he Main L oop, Giving Inform ation About the Sta te of the Gam e, Giving Inform ation About the Sta te of the Gam e, T rying Out theHum an vs. Hum an Version of Dice of Doom , T he Minim ax Algori thm , T he Minim ax Algori thm , T urning Minim ax into Actua lCode , Crea ting a Gam e L oop with an AI Player, Making Dice of Doom Faste r, T a il Ca ll Optim iza tion, T a il Ca ll Optim iza tion inDice of Doom , Mapping and Searching Across L azy L ists, Mapping and Searching Across L azy L ists, Dice of Doom , Version 2 ,T rim m ing the Gam e T ree , W inning by a L ot vs. W inning by a L it t le , W inning by a L ot vs. W inning by a L it t le , Alpha Be taPruning, Crea ting a Graphica l , W eb-Based Version of Dice of Doom , Drawing the Gam e Board Using the SVG Form at, Drawing theGam e Board Using the SVG Form at, Drawing the Gam e Board Using the SVG Form at, Drawing a Die , Drawing the Board, Drawingthe Board, L im ita t ions of Our Gam e W eb Server, L im ita t ions of Our Gam e W eb Server, L im ita t ions of Our Gam e W eb Server,Handling the Com pute r Player, Handling the Com pute r Player, Drawing the SVG Gam e Board from W ithin the HT ML , MakingDice of Doom More Fun, Increasing the Num ber of Players, Building Chance Nodes, Building Chance Nodes, Upda ting the AI,

Page 554: Land of Lisp - Barski M.D., Conrad

Updating the AIa ttacking, Finding the Neighborsca lcula t ing a t tacking m oves, Ca lcula t ing Passing Movesca lcula t ing passing m oves, Genera ting a Gam e T reecom pute r opponent, T rying Out the Hum an vs. Hum an Version of Dice of Doom , T he Minim ax Algori thm , T he Minim ax

Algori thm , T urning Minim ax into Actua l Codegam e loop with AI player, T urning Minim ax into Actua l Codem inim ax a lgori thm , T he Minim ax Algori thmm inim ax a lgori thm code , T he Minim ax Algori thm

decoupling rules from rest of gam e, Representing the Gam e Boardfinding ne ighbors, Ca lcula t ing Attacking Movesgam e board, Representing the Gam e Board, T a il Ca ll Optim iza tion in Dice of Doom , Alpha Be ta Pruning, Drawing the

Gam e Board Using the SVG Form at, Drawing the Gam e Board Using the SVG Form at3-by-3 sam ple gam e, T a il Ca ll Optim iza tion in Dice of Doom5-by-5, Alpha Be ta Pruningconstants for dim ensions, Drawing the Gam e Board Using the SVG Form atusing SVG form at, Drawing the Gam e Board Using the SVG Form at

genera t ing gam e tree , Genera ting a Gam e T reenew gam e-tree func tion, Re inforcem entsperform ance im provem ent, Making Dice of Doom Faste rplaying aga inst another hum an, T rying Out Our New gam e-tree Func tion, T rying Out Our New gam e-tree Func tion, T he

Main L oop, Giving Inform ation About the Sta te of the Gam e, Giving Inform ation About the Sta te of the Gam einput from hum an players, Giving Inform ation About the Sta te of the Gam em ain loop, T he Main L oopsta te of gam e inform ation, T rying Out Our New gam e-tree Func tionwinner de te rm ina tion, Giving Inform ation About the Sta te of the Gam e

playing first hum an vs. com pute r gam e, Crea ting a Gam e L oop with an AI Playerre inforcem ents, Re inforcem entsrules, T he Rules of Dice of Doomsam ple gam e, T he Rules of Dice of Doomta il ca l l optim iza tion, T a il Ca ll Optim iza tionversion 1, A Sam ple Gam e of Dice of Doom , Defining Som e Globa l Variables

globa l va riables, Defining Som e Globa l Variablesversion 2, Mapping and Searching Across L azy L ists, Mapping and Searching Across L azy L ists, Dice of Doom , Version 2 ,

T rim m ing the Gam e T ree , W inning by a L ot vs. W inning by a L it t le , W inning by a L ot vs. W inning by a L it t lea lpha be ta pruning, W inning by a L ot vs. W inning by a L it t lelazy l ists for gam e tree , Mapping and Searching Across L azy L istsscore -board func tion, W inning by a L ot vs. W inning by a L it t lesta rt ing gam e on 4-by-4 board, Dice of Doom , Version 2winning by a lot vs. winning by a l i t t le , T rim m ing the Gam e T ree

version 3 (web-based), Crea ting a Graphica l , W eb-Based Version of Dice of Doom , Drawing the Gam e Board Using the SVGForm at, Drawing a Die , Drawing the Board, Drawing the Board, L im ita t ions of Our Gam e W eb Server, L im ita t ions of OurGam e W eb Server, L im ita t ions of Our Gam e W eb Server, Handling the Com pute r Player, Handling the Com pute r Player,Drawing the SVG Gam e Board from W ithin the HT ML

announc ing winner, L im ita t ions of Our Gam e W eb Serverdrawing die , Drawing the Gam e Board Using the SVG Form atdrawing t i le , Drawing a Diegam e board, Drawing the Boardgam e board in HT ML , Handling the Com pute r Playerhandling com pute r player, Handling the Com pute r Playerhandling hum an player, L im ita t ions of Our Gam e W eb Serverini t ia l iz ing new gam e, L im ita t ions of Our Gam e W eb Serverplaying, Drawing the SVG Gam e Board from W ithin the HT MLweb se rver inte rface , Drawing the Board

version 4, Making Dice of Doom More Fun, Increasing the Num ber of Players, Building Chance Nodes, Building ChanceNodes, Upda ting the AI, Upda ting the AI

ca ll ing dice rol l ing code from gam e engine , Building Chance Nodesim proving re inforcem ent rules, Upda ting the AIincreasing num ber of players, Making Dice of Doom More Funroll ing dice , Inc reasing the Num ber of Playersupda ing AI, Upda ting the AI

dice_of_doom .v2. l isp fi le , Drawing the Gam e Board Using the SVG Form atdigit-char-p func tion, Convert ing Node Identifie rsdigraph com m and (Graphviz ), Crea ting a Graphdirec t-edges func tion, Preventing Islandsdirec ted graph, Crea ting Undirec ted Graphsdirty code , W hat Is Func tiona l Program m ing? , Ana tom y of a Program W rit ten in the Func tiona l Styledividing by ze ro, One T hing a t a T im e with ifdivision func tion, Num bersdo token, Counting from a Sta rt ing Point to an E nding PointDOCT YPE dec la ra t ion, How a W eb Server W orksdod-request-handle r func tion, Drawing the Boarddom ain of func tion, Ram ping L isp Up a Notch with Func tiona l Program m ingdom ain, expla ined, Dom ain-Spec ific L anguages

Page 555: Land of Lisp - Barski M.D., Conrad

dom ain-spec ific language (DSL ), Just ifying Output , Dom ain-Spec ific L anguages, DSL Guild Hot Rods. (dot), for representing cons ce l ls, T he cons Func tiondot (. ), for representing cons ce l ls, T he cons Func tionDOT inform ation genera t ion, Crea ting a Graph, Convert ing Node Identifie rs, Convert ing Node Identifie rs, Adding L abe ls to

Graph Nodes, Convert ing E dges into DOT Form at, Genera ting All the DOT Dataedges conversion, Convert ing E dges into DOT Form atfor nodes, Adding L abe ls to Graph Nodeslabe ls for graph nodes, Convert ing Node Identifie rsnode identifie rs conversion, Convert ing Node Identifie rsturning DOT fi le into pic ture , Genera t ing All the DOT Data

dot->png func tion, Capturing the Console Outputdot-nam e func tion, Convert ing Node Identifie rsdotim es func tion, Hash T able Perform ance , Main Gam e Func tionsdotted l ists, E xotic L ists" (double quotes), for strings, Num bersdouble quotes ("), for strings, Num bersdownfrom in loop m acro, Using Mult iple for Clausesdownto in loop m acro, Using Mult iple for Clausesdraw-board func tion, Representing the Gam e Boarddraw-board-svg func tion, Drawing the Boarddraw-c ity func tion, Ini t ia l iz ing a New Gam e of Grand T heft W um pusdraw-die -svg func tion, Drawing the Gam e Board Using the SVG Form atdraw-dod-page func tion, W rit ing Our W eb Request Handle r, Handling the Com pute r Playerdraw-known-c ity func tion, Known E dges, W alking Around T owndraw-ti le -svg func tion, Drawing a Diedraw-world func tion, Handling Anim al Reproduc tionDSL (dom ain-spec ific language), Just ifying Output , Dom ain-Spec ific L anguages, DSL Guild Hot Rodsdunk func tion, A Com m and for W elding, T he gam e-ac tion Macrodynam ic variable , An Alte rna tive Globa l Variable Definit ion Func tiondynam ic website , Our Grand Fina le : T he se rve Func tion!, Our Grand Fina le : T he se rve Func tion!

test ing request handle r, Our Grand Fina le : T he se rve Func tion!

E

earm uffs, Defining the sm all and big Variablesea t func tion, Handling Anim al T urningedge-pa ir func tion, Genera ting Random E dges, Preventing Islandsedges, Describing the Pa ths, Describing Mult iple Pa ths a t Once , Convert ing E dges into DOT Form at, Crea ting Undirec ted Graphs,

Defining the E dges of Congestion City, A Faste r Grand T heft W um pus Using Hash T ablesconvert ing to descript ions, Describing Mult iple Pa ths a t Onceconvert ing to DOT form at, Convert ing E dges into DOT Form ate rasing duplica te , Crea ting Undirec ted Graphsof Congestion City, Defining the E dges of Congestion Cityreplac ing l ist with hash table , A Faste r Grand T heft W um pus Using Hash T ables

edges->dot func tion, Convert ing E dges into DOT Form atedges-to-a l ist func tion, Preventing Islands, T he edges-to-a l ist Func tionE DSAC Init ia l Orders, W here Did L isp Com e From ?else in loop m acro, Using Mult iple for ClausesE m acs L isp, L isp Dia lec ts Used for Script ingem pty l ists (), T he cons Func tion, E m pty E qua ls Fa lse , E m pty E qua ls Fa lse

as fa lse va lue , E m pty E qua ls Fa lseother expressions as disguises for, E m pty E qua ls Fa lse

end in loop m acro, Using Mult iple for Clausesenergy, in plants, Using loop to E volve!eq func tion, T he Building Blocks of L isp Syntax, T he Com m and T ha t Does It All : cond, Com paring Stuff: eq, equa l , and Moreeql func tion, Com paring Stuff: eq, equa l , and More , Mem oiz ing the ra te -posit ion Func tionequa l func tion, Com paring Stuff: eq, equa l , and More , Mem oiz ing the ra te -posit ion Func tion= (equa l sign) func tion, Com paring Stuff: eq, equa l , and More , Com paring Stuff: eq, equa l , and Moreequa lp func tion, Com paring Stuff: eq, equa l , and More , Mem oiz ing the ne ighbors Func tionerror com m and, E rror Handling in Com m on L ispescaped charac te rs, in strings, Num berseva l com m and, T he Sym m etry Be tween Code and Data in L isp, W rit ing a gam e-eva l Func tion, T he Dangers of read and eva l

danger of, T he Dangers of read and eva lim proving, W rit ing a gam e-eva l Func tion

every func tion, W orking with Sequences, Monste r Managem ent Func tionsevolution func tion, Drawing Our W orldevolving environm ent gam e, E verything You E ver W anted to Know About loop , Using loop to E volve!, Using loop to E volve!,

Growing Plants in Our W orld, Crea ting Anim als, Crea ting Anim als, Ana tom y of an Anim al, Ana tom y of an Anim al, Ana tom y of anAnim al, Ana tom y of an Anim al, Ana tom y of an Anim al, Handling Anim al Motion, Handling Anim al T urning, Handling Anim alReproduc tion, Handling Anim al Reproduc tion, Handling Anim al Reproduc tion, Drawing Our W orld, Crea ting a User Inte rface , L e t 'sW atch Som e E volution!

anim als, Crea ting Anim als, Crea ting Anim als, Ana tom y of an Anim al, Ana tom y of an Anim al, Ana tom y of an Anim al,Ana tom y of an Anim al, Ana tom y of an Anim al, Handling Anim al Motion, Handling Anim al T urning, Handling Anim alReproduc tion

Page 556: Land of Lisp - Barski M.D., Conrad

ana tom y, Crea ting Anim alsea ting process, Handling Anim al T urningenergy, Ana tom y of an Anim alm otion, Ana tom y of an Anim alpropert ies, Ana tom y of an Anim alreproduc tion, Handling Anim al Reproduc tionsta rt ing point , Ana tom y of an Anim altracking genes, Ana tom y of an Anim alturn func tion, Handling Anim al Motion

bim odal distribution in, L e t 's W atch Som e E volution!drawing world, Handling Anim al Reproduc tionplants, Using loop to E volve!, Using loop to E volve!, Growing Plants in Our W orld

energy, Using loop to E volve!growth, Growing Plants in Our W orld

sim ula ting day, Handling Anim al Reproduc tionsta rt ing sim ula tion, Crea ting a User Inte rfaceuser inte rface , Drawing Our W orld

exception handling, W rit ing a Custom read Func tion, L e t 's Crea te a W eb Server!, E rror Handling in Com m on L isp, E rrorHandling in Com m on L isp, Crea ting Custom Condit ions, Crea ting Custom Condit ions, Our Grand Fina le : T he se rve Func tion!,Synopsis

custom condit ions, E rror Handling in Com m on L ispfor web se rver, Our Grand Fina le : T he se rve Func tion!inte rcepting condit ions, Crea ting Custom Condit ionsresources protec ted aga inst unexpec ted condit ions, Crea ting Custom Condit ionssigna ling condit ion, E rror Handling in Com m on L isp

exponent, Code Modeexpressive language , W here Does L isp Get Its Power?expt func tion, Sym bols, Code Mode

F

fa lse va lue , em pty l ist () a s, E m pty E qua ls Fa lsefi le stream s, W orking with Stream sfi les, Using T hunks, Input Stream s, W orking with Files

stream s to write and read, Input Stream swrit ing inform ation to, Using T hunks

find-em pty-node func tion, Building the Nodes for Congestion Cityfind-if func tion, Using Func tions T ha t Re turn More than Just the T ruth, W orking with Sequencesfind-island func tion, Preventing Islandsfind-islands func tion, Preventing IslandsFire fox 3.7 a lpha , for SVG support , Drawing the Gam e Board Using the SVG Form atFire fox, for Dice of Doom gam e, Drawing the SVG Gam e Board from W ithin the HT MLfirst-c lass va lues, func tions as, W hat lam bda Doesfle t func tion, Defining L oca l Func tions in L isp, W rit ing a Custom read Func tion

for loca l func tion defini t ion, W rit ing a Custom read Func tionfloa ting-point num bers, Num bers, Control Sequences for Form att ing Floa ting-Point Num bers

control sequences for form att ing, Control Sequences for Form att ing Floa ting-Point Num bersfor in loop m acro, T he loop Macro, Using Mult iple for Clausesforce com m and, Crea ting the lazy and force Com m andsform at func tion, loop and form at: T he Seedy Underbe lly of L isp, Print ing T ext with the form at Func tion, Ana tom y of the form at

Func tion, Ana tom y of the form at Func tion, Print ing Mult iple L ines of Outputana tom y, Print ing T ext with the form at Func tionand text just ifica t ion, Print ing Mult iple L ines of Outputcontrol string param ete r, Ana tom y of the form at Func tiondestina tion param ete r, Ana tom y of the form at Func tion

form att ing num bers, control sequences for, Control Sequences for Form att ing Num bersform s, Code Mode , Code Mode

nested, Code ModeFORT RAN, W here Did L isp Com e From ?free ing of va riables, Closuresfresh-l ine com m and, Print ing Mult iple L ines of Outputfrom in loop m acro, Using Mult iple for Clausesfunca ll func tion, He lper Func tions for Player Attacks, Closures#' (func tion) opera tor, Convert ing the E dges to Descript ionsfunc tion opera tor, shorthand for, Convert ing the E dges to Descript ionsfunc tion pipe line , Decoupling Dice of Doom 's Rules from the Rest of the Gam efunc tiona l program m ing, One T hing a t a T im e with if, Describing the L oca tion, W hy lam bda Is So Im portant , Ram ping L isp Up

a Notch with Func tiona l Program m ing, W hat Is Func tiona l Program m ing? , W hat Is Func tiona l Program m ing? , Using the Func tiona lStyle , W hy Func tiona l Program m ing Is Crazy, W hy Func tiona l Program m ing Is Fantast ic , Finding the Neighbors, A RecursionMacro, L azy Program m ing, Func tiona l Guild Cruise r

ana tom y of program , W hat Is Func tiona l Program m ing?and loops, Finding the Neighborsbenefi ts, W hy Func tiona l Program m ing Is Fantast ichigher-order, W hy lam bda Is So Im portant

Page 557: Land of Lisp - Barski M.D., Conrad

problem s from , L azy Program m ingreduce func tion, A Recursion Macroside e ffec ts, W hat Is Func tiona l Program m ing? , W hy Func tiona l Program m ing Is Crazyusing, Using the Func tiona l Stylewhat i t is, Ram ping L isp Up a Notch with Func tiona l Program m ing

func tionp func tion, Sequence Func tions for Ite ra t ing Across a Sequencefunc tions, T he Guess-My-Num ber Gam e, T he Guess-My-Num ber Gam e, T he Guess-My-Num ber Gam e, Defining L oca l Func tions

in L isp, Defining L oca l Func tions in L isp, Convert ing the E dges to Descript ions, Convert ing the E dges to Descript ions, lam bda : AFunc tion So Im portant It Deserves Its Own Chapte r, Convert ing Node Identifie rs, Convert ing Node Identifie rs, Using T hunks, T woMore Im portant Sequence Func tions, T idying Up Afte r Ourse lves

ca ll to i tse lf, Defining L oca l Func tions in L ispca ll ing in L isp, T he Guess-My-Num ber Gam ecom prehensive l ist of sequence , T wo More Im portant Sequence Func tionscrea ting with lam bda , lam bda: A Func tion So Im portant It Deserves Its Own Chapte rdepreca ted, Convert ing Node Identifie rsgeneric , Convert ing Node Identifie rshigher-order, Convert ing the E dges to Descript ionsnam es ava ilable in defined func tions, Defining L oca l Func tions in L ispnam espaces for, Convert ing the E dges to Descript ionsnulla ry, Using T hunksparentheses for, T he Guess-My-Num ber Gam esending string stream s to, T idying Up Afte r Ourse lves

G

gam e board, Defining Som e Globa l Variables , T a il Ca ll Optim iza tion in Dice of Doom , Making Our AI W ork on L arger Gam eBoards, Making Our AI W ork on L arger Gam e Boards, Alpha Be ta Pruning, Drawing the Gam e Board Using the SVG Form at,Drawing the Gam e Board Using the SVG Form at

AI adjustm ents for la rger, Making Our AI W ork on L arger Gam e Boardsfor Dice of Doom , Defining Som e Globa l Variables, T a il Ca ll Optim iza tion in Dice of Doom , Alpha Be ta Pruning, Drawing

the Gam e Board Using the SVG Form at, Drawing the Gam e Board Using the SVG Form at3-by-3 sam ple gam e, T a il Ca ll Optim iza tion in Dice of Doom5-by-5, Alpha Be ta Pruningconstants for dim ensions, Drawing the Gam e Board Using the SVG Form atusing SVG form at, Drawing the Gam e Board Using the SVG Form at

gam e tree , Genera ting a Gam e T ree , Mem oiz ing the ne ighbors Func tion, L azy Program m ing, L azy Program m ing, T rim m ing theGam e T ree , Inc reasing the Num ber of Players

branches hidden in c louds, L azy Program m ingchance nodes in, Inc reasing the Num ber of Playersgenera t ing, Genera t ing a Gam e T reem em oiz ing, Mem oiz ing the ne ighbors Func tiontrim m ing, T rim m ing the Gam e T ree

gam e-ac tion m acro, A Com m and for Dunkinggam e-eva l func tion, W rit ing a gam e-eva l Func tion, T he Dangers of read and eva l , T he Dangers of read and eva l

approved l ist of com m ands for, T he Dangers of read and eva ll im it ing com m ands ca l led, W rit ing a gam e-eva l Func tion

gam e-loop func tion, Globa l Variables for the Player and Monste rsgam e-print func tion, W rit ing a gam e-eva l Func tiongam e-read func tion, Se tt ing Up a Custom RE PLgam e-repl func tion, Se tt ing Up a Custom RE PL , Crea ting Custom Gam e Com m ands for W izard's Adventure Gam egam e-tree func tion, Genera ting a Gam e T ree , Re inforcem entsgam es. See a lso Dice of Doom gam e; evolving environm ent gam e; Grand T heft W um pus gam e; Orc Ba tt le gam e; W izard’s

Adventure Gam e, T he Guess-My-Num ber Gam e, A Crazy Form att ing T rick for Crea ting Pre t ty T ables of Da ta , A Crazy Form att ingT rick for Crea ting Pre t ty T ables of Da ta , Crea ting Custom Gam e Com m ands for W izard's Adventure Gam e, T rim m ing the Gam eT ree

Attack of the Robots! gam e, A Crazy Form att ing T rick for Crea ting Pre t ty T ables of Da taGuess-My-Num ber, T he Guess-My-Num ber Gam eloading code from RE PL , Crea ting Custom Gam e Com m ands for W izard's Adventure Gam ewinning by a lot vs. winning by a l i t t le , T rim m ing the Gam e T ree

garbage collec t ion, W here Did L isp Com e From ?, ClosuresGarre t , Ron, How a W eb Server W orksgen-board func tion, Representing the Gam e Boardgenera l ized re fe rence , Using a Generic Se tte rgeneric func tions, Convert ing Node Identifie rs, Sequence Func tions for Ite ra t ing Across a Sequence

crea ting with type predica tes, Sequence Func tions for Ite ra t ing Across a Sequencegeneric se t te rs, Using a Generic Se tte r, Synopsisgensym func tion, Avoiding Variable CaptureGE T request , How a W eb Server W orks, Request Param ete rs

request pa ram ete rs for, Request Param ete rsge t-connec ted func tion, Preventing Islands, Hash T able Perform ance , A Faste r Grand T heft W um pus Using Hash T ables,

Im proving the Dice of Doom Reinforcem ent Rules, Im proving the Dice of Doom Reinforcem ent Rulesge t-connec ted-hash func tion, A Faste r Grand T heft W um pus Using Hash T ablesge t-content-param s func tion, T est ing ge t-header with a String Streamget-header func tion, Parsing the Request Header, Parsing the Request Header

Page 558: Land of Lisp - Barski M.D., Conrad

te st ing, with string stream , Parsing the Request Headerge t-ra t ings func tion, T urning Minim ax into Actua l Code , W inning by a L ot vs. W inning by a L it t le , Alpha Be ta Pruning,

Upda ting the AInew versions, Alpha Be ta Pruning

ge thash func tion, Using a Generic Se tte r, W orking with Hash T ables, Re turning Mult iple Values, A Faste r Grand T heft W um pusUsing Hash T ables

globa l func tions, de fining, Basic L isp E tique ttegloba l va riables, T he Guess-My-Num ber Gam e, Defining the guess-m y-num ber Func tion, Defining the guess-m y-num ber Func tion,

One T hing a t a T im e with if, Describing It All , Globa l Variables for the Player and Monste rs, T he gam e-ac tion Macrochanging va lue , Defining the guess-m y-num ber Func tiondefining, T he Guess-My-Num ber Gam efor player and m onste rs, Globa l Variables for the Player and Monste rsin look func tion, Describing It Allm acros and, T he gam e-ac tion Macrose tt ing inside condit iona l branch, One T hing a t a T im e with if

Google BigT able , Re turning Mult iple ValuesGraham , Paul , A T a le of T wo L isps, Crea ting the tag Macro

Arc L isp dia lec t , Crea ting the tag MacroGrand T heft W um pus gam e. See a lso Congestion City, T he Grand T heft W um pus Gam e, T he Grand T heft W um pus Gam e,

Building the Fina l E dges for Congestion City, Building the Nodes for Congestion City, Building the Nodes for Congestion City,Ini t ia l iz ing a New Gam e of Grand T heft W um pus, Drawing a City from Part ia l Knowledge , W alking Around T own, A Faste r GrandT heft W um pus Using Hash T ables

basics, T he Grand T heft W um pus Gam eclues, Building the Nodes for Congestion Citydrawing m ap, Ini t ia l iz ing a New Gam e of Grand T heft W um pus, Drawing a City from Part ia l Knowledge

from part ia l knowledge , Drawing a City from Part ia l Knowledgeinit ia l iz ing new gam e, Building the Nodes for Congestion Cityplaying gam e, W alking Around T ownpolice roadblocks, Building the Fina l E dges for Congestion Citywith hash tables, A Faste r Grand T heft W um pus Using Hash T ables

graph uti l i t ie s, loading, T he Grand T heft W um pus Gam egraph->dot func tion, Crea ting a Pic ture of Our Graphgraph-uti l . l isp fi le , Crea ting Undirec ted Graphsgraphs, Visua liz ing T ree-l ike Data , Visua liz ing T ree-l ike Data , Visua liz ing Graphs, Convert ing Node Identifie rs, Capturing the

Console Output , Crea ting a Pic ture of Our Graph, Crea ting Undirec ted Graphscrea ting, Visua liz ing T ree-l ike Datacrea ting pic ture of, Capturing the Console Outputdirec ted, Crea ting Undirec ted Graphslabe ls for nodes, Convert ing Node Identifie rsundirec ted, Crea ting a Pic ture of Our Graphvisua liz ing, Visua liz ing T ree-l ike Data

Graphviz , Crea ting a GraphGraphviz DOT fi le , Crea ting a Graph, Convert ing Node Identifie rs, Convert ing Node Identifie rs, Adding L abe ls to Graph Nodes,

Convert ing E dges into DOT Form at, Convert ing E dges into DOT Form at, Genera ting All the DOT Dataedges conversion, Convert ing E dges into DOT Form atfor graph drawing l ibra ry, Crea ting a Graphfor nodes, Adding L abe ls to Graph Nodeslabe ls for graph nodes, Convert ing Node Identifie rsnode identifie rs conversion, Convert ing Node Identifie rsturning DOT fi le into pic ture , Genera t ing All the DOT Data

guess-m y-num ber func tion, Basic L isp E tique tteGuess-My-Num ber gam e, T he Guess-My-Num ber Gam eGuile Schem e, L isp Dia lec ts Used for Script ing

H

hackers, T he Dangers of read and eva l , T he Dangers of read and eva l , Parsing the Request Headerand dangerous com m ands, T he Dangers of read and eva land read com m and, Parsing the Request Header

handle -com pute r func tion, T urning Minim ax into Actua l Code , T rim m ing the Gam e T ree , Alpha Be ta Pruning, Handling theCom pute r Player, Ca ll ing the Dice Roll ing Code from Our Gam e E ngine

handle -direc t ion func tion, Drawing Only the Known Parts of the Cityhandle -hum an func tion, Giving Inform ation About the Sta te of the Gam e, Dice of Doom , Version 2 , Ca ll ing the Dice Roll ing

Code from Our Gam e E nginehandle -new-place func tion, W alking Around T ownhandle r-case func tion, E rror Handling in Com m on L isphash coll isions, Hash T able Perform ance# (hash m ark), for a rray, W orking with Arrayshash m ark (#), for a rray, W orking with Arrayshash tables, Using a Generic Se tte r, Hash T ables, Re turning Mult iple Values , Re turning Mult iple Values , Re turning Mult iple

Values, Hash T able Perform ance , Growing Plants in Our W orldfor plants, Growing Plants in Our W orldGrand T heft W um pus gam e with, Hash T able Perform anceineffic iency for sm all tables, Re turning Mult iple Values

Page 559: Land of Lisp - Barski M.D., Conrad

perform ance , Re turning Mult iple Valuesre turning m ult iple va lues, Re turning Mult iple Values

hash-edges func tion, A Faste r Grand T heft W um pus Using Hash T ableshash-table -p func tion, Sequence Func tions for Ite ra t ing Across a SequenceHaske ll , A T a le of T wo L isps, Ana tom y of a Program W rit ten in the Func tiona l Style , Adding L azy E va lua tion to L isp

and lazy eva lua tion, Adding L azy E va lua tion to L isphave func tion, A Com m and for W eldinghea lth m ete r, for m onste rs, T he Monste rshe llo-request-handle r func tion, Our Grand Fina le : T he se rve Func tion!heurist ics, Applying Heurist icshexadec im al, num ber display as, Control Sequences for Form att ing Num bersHickey, Rich, A T a le of T wo L ispshidden sta te , Code Com posit ion with Im pera tive Codehie ra rchica l da ta , Visua liz ing T ree-l ike Datahigher-order func tions, Convert ing the E dges to Descript ionshigher-order program m ing, W hat lam bda Does, Higher-Order Program m inghom oiconic program m ing code , Reading and Print ing Stuff the W ay Hum ans L ike ItHT ML code , W rit ing a gam e-print Func tion, Our Grand Fina le : T he se rve Func tion!, Crea ting the tag Macro, Drawing the Gam e

Board Using the SVG Form atem bedding SVG pic tures, Drawing the Gam e Board Using the SVG Form atpage ske le ton, Our Grand Fina le : T he se rve Func tion!tag m acro to genera te , Crea ting the tag Macro

htm l tags, How a W eb Server W orksHT ML 5 standard, Drawing the Gam e Board Using the SVG Form atHT T P (Hypertext T ransfe r Protocol), Protec ting Resources Aga inst Unexpec ted Condit ionsHT T P escape codes, Decoding the Values of Request Param ete rshttp-char func tion, Decoding the Values of Request Param ete rshttp. l isp fi le , How a W eb Server W orksHughes, John, “W hy Func tiona l Program m ing Matte rs” , Decoupling Dice of Doom 's Rules from the Rest of the Gam eHunt the W um pus, T his Ain't Your Daddy's W um pushyperl inks, in SVG im age , Using the tag Macro to Genera te HT MLHypertext T ransfe r Protocol (HT T P), Protec ting Resources Aga inst Unexpec ted Condit ions

I

i f com m and, E m pty E qua ls Fa lse , T he Four Disguises of ()if in loop m acro, Using Mult iple for Clausesim pera tive code , W hat Is Func tiona l Program m ing? , Ana tom y of a Program W rit ten in the Func tiona l Style

code com posit ion with, Ana tom y of a Program W rit ten in the Func tiona l Styleim pera tive gam e engine , Decoupling Dice of Doom 's Rules from the Rest of the Gam eim plic i t progn, Going Beyond if: T he when and unless Alte rna tivesin in loop m acro, Using Mult iple for Clausesincf func tion, Monste r Managem ent Func tionsindenta t ion of code , Defining the sta rt-over Func tioninfini te loop, Se tt ing Up a Custom RE PL , Se tt ing Up a Custom RE PL , Circula r L ists

ge tt ing out of, Se tt ing Up a Custom RE PLpreventing, Circula r L ists

infini ty, posi t ive and nega tive , Alpha Be ta PruningInform ation Processing L anguage , W here Did L isp Com e From ?init-m onste rs func tion, He lper Func tions for Player Attacksinput stream s, Stream s by Direc tion, Input Stream sinput-stream -p com m and, Output Stream sinsta l l ing CL ISP, ANSI Com m on L ispinstruc tion se t of processor, W here Did L isp Com e From ?integers, Num bers, Control Sequences for Form att ing Num bers

control sequences for form att ing, Control Sequences for Form att ing Num bersinte rn com m and, Parsing the Request Headerinte rpre te r, W here Did L isp Com e From ?, Crea ting Your Own Generic Func tions with T ype Predica tes

versions of func tion for, Crea ting Your Own Generic Func tions with T ype Predica tesinte rsec tion func tion, T he edges-to-a l ist Func tioninto in loop m acro, Using Mult iple for Clausesinventory func tion, Checking Our InventoryIP address, in socke t address, W orking with Socke tsislands, preventing, L ooping with the loop Com m andisom orphic i tem , Com paring Stuff: eq, equa l , and Morei te ra t ing, W orking with Sequences, W orking with Sequences, Counting from a Sta rt ing Point to an E nding Point , Just ifying

Outputacross sequence , W orking with Sequencesthrough l ist va lues, Counting from a Sta rt ing Point to an E nding Pointthrough l ists, with form at control sequences, Just ifying Output

J

Java language , W here Does L isp Get Its Power?

Page 560: Land of Lisp - Barski M.D., Conrad

Jones, Sim on Peyton, Using the Func tiona l Stylejust ified text , Print ing Mult iple L ines of Output

K

key/va lue pa ir, Assoc ia t ion L ists, Assoc ia t ion L ists, Hash T able Perform ancere turning for a l ist , Assoc ia t ion L istsstorage , Hash T able Perform ance

keyword param ete r, W alking Around in Our W orld, Convert ing Node Identifie rs, Understanding Keyword Param ete rsfor find func tion, W alking Around in Our W orld

known-c ity-edges func tion, Drawing a City from Part ia l Knowledgeknown-c ity-nodes func tion, Drawing a City from Part ia l Knowledgeknown-c ity.dot-png fi le , Drawing Only the Known Parts of the City

Page 561: Land of Lisp - Barski M.D., Conrad

L

labe ls func tion, Defining L oca l Func tions in L isp, L ist ing Visible Objec ts, W rit ing a Custom read Func tionfor loca l func tion defini t ion, W rit ing a Custom read Func tion

labe ls, for graph nodes, Convert ing Node Identifie rslam bda ca lculus, W here Did L isp Com e From ?, W hy lam bda Is So Im portant , W hat Is Func tiona l Program m ing?lam bda func tion, lam bda: A Func tion So Im portant It Deserves Its Own Chapte r, W hat lam bda Does, He lper Func tions for Player

Attacks, Monste r Managem ent Func tions, Crea ting Custom Condit ions, Ca lcula t ing Attacking Moves, Making Dice of Doom Faste rand c losures, Making Dice of Doom Faste rim portance , W hat lam bda Doespurpose , lam bda: A Func tion So Im portant It Deserves Its Own Chapte r

la rgest-c luste r-size func tion, Im proving the Dice of Doom Reinforcem ent Ruleslaunching website , T est ing the Request Handle rlazy com m and, Crea ting the lazy and force Com m andslazy eva lua tion, L azy Program m ing, Im proving the Dice of Doom Reinforcem ent Rules, L isp Dia lec tlazy gam e tree , Decoupling Dice of Doom 's Rules from the Rest of the Gam elazy l ists, Crea ting a L azy L ists L ibra ry, Crea ting a L azy L ists L ibra ry, Convert ing Be tween Regula r L ists and L azy L ists,

Mapping and Searching Across L azy L ists, Mapping and Searching Across L azy L ists, Making Our AI W ork on L arger Gam e Boards,Making Our AI W ork on L arger Gam e Boards

adjust ing AI func tions to use , Making Our AI W ork on L arger Gam e Boardsconvert ing be tween regula r l ists and, Crea ting a L azy L ists L ibra ryconvert ing to regula r l ists, Convert ing Be tween Regula r L ists and L azy L istsfor Dice of Doom gam e tree , Mapping and Searching Across L azy L istsl ibra ry for, Crea ting a L azy L ists L ibra rym apping and searching, Mapping and Searching Across L azy L ists

lazy-car com m and, Crea ting the lazy and force Com m andslazy-cdr com m and, Crea ting the lazy and force Com m andslazy-cons com m and, Crea ting the lazy and force Com m andslazy-find-if func tion, Mapping and Searching Across L azy L istslazy-m apcan func tion, Mapping and Searching Across L azy L ists, Dice of Doom , Version 2lazy-m apcar func tion, Mapping and Searching Across L azy L istslazy-nil func tion, Crea ting a L azy L ists L ibra ry, Dice of Doom , Version 2lazy-nth func tion, Mapping and Searching Across L azy L istslazy-null func tion, Crea ting a L azy L ists L ibra rylega l-t i le s pa ram ete r, Drawing the Boardlega li ty of gam e m ove , Drawing Only the Known Parts of the Citylength func tion, W hen to Use Struc tures< (less-than) func tion, with sort , Sequence Func tions for Ite ra t ing Across a Sequenceless-than (<) func tion, with sort , Sequence Func tions for Ite ra t ing Across a Sequencele t com m and, Defining the sta rt-over Func tion, Capturing the Console Output , Building the Fina l E dges for Congestion City,

Closures, T he Magic of L isp Macros, How Macros Are T ransform edprogn com m and and, How Macros Are T ransform ed

le t* com m and, Building the Fina l E dges for Congestion Citylexica l va riable , Capturing the Console Output , Closuresl ibra ry, for lazy l ists, Crea ting the lazy and force Com m andslim it-tree -depth func tion, T rim m ing the Gam e T ree , Alpha Be ta Pruning, Upda ting the AIline breaks, Defining L oca l Variables in L isplinking da ta pieces, cons func tion for, Cons Ce llsL isp. See a lso Com m on L isp (CL ), W hat Makes L isp So Cool and Unusua l? , W hat Makes L isp So Cool and Unusua l? , If L isp Is

So Grea t , W hy Don't More People Use It? , W here Does L isp Get Its Power? , L isp Dia lec ts, A T a le of T wo L isps, L isp Dia lec ts Usedfor Script ing, T he Guess-My-Num ber Gam e, An Alte rna tive Globa l Variable Definit ion Func tion , An Alte rna tive Globa l VariableDefinit ion Func tion, E pilogue

basic e t ique tte , An Alte rna tive Globa l Variable Definit ion Func tiondia lec ts, L isp Dia lec ts, L isp Dia lec ts Used for Script ing

for sc ript ing, L isp Dia lec ts Used for Script ingfea tures, W hat Makes L isp So Cool and Unusua l?Guess-My-Num ber gam e, T he Guess-My-Num ber Gam eorigins, If L isp Is So Grea t , W hy Don't More People Use It?source of power, W here Does L isp Get Its Power?technologies support ing, com ic book, E pilogueup-and-com ing dia lec ts, A T a le of T wo L ispsva lid expression exam ple , W hat Makes L isp So Cool and Unusua l?

L ispW orks, Ge tt ing Sta rted with CL ISPlist func tion, T he ca r and cdr Func tions, Crea ting the tag Macrolist-length func tion, W orking with Sequenceslistp func tion, Sequence Func tions for Ite ra t ing Across a Sequence , Output Stream slists, T he Building Blocks of L isp Syntax, Da ta Mode , L ist Func tions, T he cons Func tion, Nested L ists, E m pty E qua ls Fa lse ,

E m pty E qua ls Fa lse , E m pty E qua ls Fa lse , Using the Stea lth Condit iona ls and and or, Describing the Scenery with an Assoc ia t ionL ist , Convert ing the E dges to Descript ions, Joining the Descript ions, E xotic L ists, Pa irs, Pa irs, Circula r L ists, Using a GenericSe tte r, W hen to Use Struc tures, W hen to Use Struc tures, Sequence Func tions for Ite ra t ing Across a Sequence , Counting from aSta rt ing Point to an E nding Point , Just ifying Output , More Com plex Macros

assoc ia t ion, Circula r L istsbenefi ts of using, Describing the Scenery with an Assoc ia t ion L istca lcula t ing length, E m pty E qua ls Fa lse

Page 562: Land of Lisp - Barski M.D., Conrad

checking for m em bership, Using the Stea lth Condit iona ls and and orc ircula r, Pa irscontrol sequences for i te ra t ing through, Just ifying Outputdotted, E xotic L istsem pty, T he cons Func tion, E m pty E qua ls Fa lse , E m pty E qua ls Fa lse

as fa lse va lue , E m pty E qua ls Fa lseother expressions as disguises for, E m pty E qua ls Fa lse

func tions, L ist Func tionsite ra t ing through with loop, Counting from a Sta rt ing Point to an E nding Pointjoining m ult iple into one , Convert ing the E dges to Descript ionsm acro for spli t t ing, More Com plex Macrosnested, Nested L istsof objec ts, Joining the Descript ionspa irs, Pa irssequence func tions for, W hen to Use Struc turessum func tion for, Sequence Func tions for Ite ra t ing Across a Sequencevs. a rrays, Using a Generic Se tte rvs. struc tures, W hen to Use Struc tures

l i t va riable , and capita l iza t ion rules, W rit ing a gam e-print Func tionli te ra l charac te rs, Sta rt ing with print and readload com m and, T he Grand T heft W um pus Gam eloca l func tions, de fining, Defining L oca l Func tions in L isploca l va riables, Defining the sta rt-over Func tion, Defining L oca l Variables in L isp, Saying Hello to the User

defining, Defining the sta rt-over Func tionfor va lue re turned by read func tion, Saying Hello to the User

log inform ation, stream s for, T idying Up Afte r Ourse lveslong strings, W orking with L ong Stringslook func tion, Describing It All , Se tt ing Up a Custom RE PLlookup key, of hash table , W orking with Hash T ablesloop m acro, Se tt ing Up a Custom RE PL , Genera ting Random E dges, loop and form at: T he Seedy Underbe lly of L isp, L ooping

with the loop Com m and, Counting from a Sta rt ing Point to an E nding Point , Counting from a Sta rt ing Point to an E nding Point ,Counting from a Sta rt ing Point to an E nding Point , Counting from a Sta rt ing Point to an E nding Point , Breaking out of a L oopE arly, Breaking out of a L oop E arly, Breaking out of a L oop E arly, Using Mult iple for Clauses

breaking out , Breaking out of a L oop E arlycollec t c lause , Breaking out of a L oop E arlycounting from sta rt ing point to ending point , Counting from a Sta rt ing Point to an E nding Pointdo token, Counting from a Sta rt ing Point to an E nding Pointi te ra t ing through l ist va lues, Counting from a Sta rt ing Point to an E nding Pointnested, Using Mult iple for Clauseswhen token, Counting from a Sta rt ing Point to an E nding Pointwith m ult iple for c lauses, Breaking out of a L oop E arly

loops, Se tt ing Up a Custom RE PL , Circula r L ists, Main Gam e Func tions, Main Gam e Func tions, E verything You E ver W anted toKnow About loop, Finding the Neighbors

and func tiona l program m ing, Finding the Neighborsfor evolving environm ent, E verything You E ver W anted to Know About loopge tt ing out of infini te , Se tt ing Up a Custom RE PLpreventing infini te , Circula r L istswith dotim es func tion, Main Gam e Func tions

M

m achine language , W here Did L isp Com e From ?m acro expansion, A Sim ple L isp Macrom acroexpand com m and, Using the Sim ple Macro, Avoiding Repea ted E xecution in Macros, Avoiding Variable Capturem acros, One T hing a t a T im e with if, W alking Around in Our W orld, T he Dangers of read and eva l , W hat lam bda Does, T he

Magic of L isp Macros, T he Magic of L isp Macros, Macro E xpansion, More Com plex Macros, A Macro for Spli t t ing L ists, AvoidingRepea ted E xecution in Macros, Avoiding Variable Capture , A Recursion Macro, W rit ing a Macro Helper Func tion, Using the tagMacro to Genera te HT ML , T he gam e-ac tion Macro, Crea ting the lazy and force Com m ands, Synopsis

avoiding repea ted execution, A Macro for Spli t t ing L istsavoiding variable capture , Avoiding Repea ted E xecution in Macrosdangers and a l te rna tives, A Recursion Macrofor defining new func tion, T he gam e-ac tion Macrofor spli t t ing l ists, More Com plex Macroshe lper func tion, W rit ing a Macro Helper Func tionreader, T he Dangers of read and eva lrecursive , Avoiding Variable Capturesim ple exam ple , T he Magic of L isp Macrossvg, Using the tag Macro to Genera te HT MLto im plem ent lazy com m and, Crea ting the lazy and force Com m andstransform ation, Macro E xpansion

m ain-loop func tion, Ana tom y of a Program W rit ten in the Func tiona l Style , Ana tom y of a Program W rit ten in the Func tiona lStyle

m ake-a rray com m and, W orking with Arraysm ake-c ity-edges func tion, Preventing Islands, Building the Fina l E dges for Congestion City

Page 563: Land of Lisp - Barski M.D., Conrad

m ake-c ity-nodes func tion, Building the Nodes for Congestion Citym ake-edge-l ist func tion, Genera ting Random E dges, Building the Fina l E dges for Congestion Citym ake-hash-table com m and, Arrays vs. L ists, Hash T able Perform ancem ake-lazy func tion, Crea ting a L azy L ists L ibra rym ake-orc func tion, T he Generic Monste rm ake-person func tion, W orking with Struc tures, W hen to Use Struc turesm ake-string-input-stream func tion, T idying Up Afte r Ourse lves, T est ing ge t-header with a String Streamm ake-string-output-stream com m and, T idying Up Afte r Ourse lvesm ap func tion, Sequence Func tions for Ite ra t ing Across a Sequencem ap of c i ty, Ini t ia l iz ing a New Gam e of Grand T heft W um pus, Ini t ia l iz ing a New Gam e of Grand T heft W um pus, Drawing a

City from Part ia l Knowledgedrawing, Ini t ia l iz ing a New Gam e of Grand T heft W um pusshowing only visi ted nodes, Drawing a City from Part ia l Knowledge

m apc func tion, Adding L abe ls to Graph Nodes, Preventing Islands, A Faste r Grand T heft W um pus Using Hash T ablesm apcan func tion, Drawing a City from Part ia l Knowledge , Known E dges, Ca lcula t ing Attacking Moves, Building a More

Com plica ted SVG E xam plem apcar func tion, Describing Mult iple Pa ths a t Once , T he edges-to-a l ist Func tion, Crea ting the tag Macrom aplist func tion, Crea ting Undirec ted Graphsm apping lazy l ists, Mapping and Searching Across L azy L istsm athem atica l func tions, propert ies, W hat Is Func tiona l Program m ing?m athem atica l se ts, hash tables for, Growing Plants in Our W orldm athem atica l syntax, languages using, W here Did L isp Com e From ?m ax func tion, Handling Anim al Reproduc tionm axim ize in loop m acro, Using Mult iple for ClausesMcCarthy, John, W here Did L isp Com e From ?, W here Did L isp Com e From ?

“Recursive Func tions of Sym bolic E xpressions and T he ir Com puta tion by Machine” , W here Did L isp Com e From ?m em ber func tion, Using the Stea lth Condit iona ls and and or, W rit ing a gam e-eva l Func tionm em oiza tion, Mem oiza tionm em ory, W here Did L isp Com e From ?, Using a Generic Se tte r, Synopsis

software transac tiona l , SynopsisMetaobjec t Protocol (MOP), CL OS Guild Ba tt leshipm inim ax a lgori thm code , gam e tree ana lysis with, Alpha Be ta Pruningm inim ize in loop m acro, Using Mult iple for Clausesm od (rem ainder) func tion, Handling Anim al Motionm oneta ry floa ting-point va lue , T he Control String Param ete rm onste r-a t tack func tion, T he Generic Monste r, T he W icked Orc , T he Slim y Slim e Mold

for orcs, T he W icked Orcfor sl im e m old, T he Slim y Slim e Mold

m onste r-hit func tion, Player Managem ent Func tions, T he Malic ious Hydram onste r-show func tion, for orcs, T he W icked Orcm onste rs. See Orc Ba tt le gam e, T he W icked OrcMOP (Metaobjec t Protocol), CL OS Guild Ba tt leshipm ost-nega tive-fixnum , Alpha Be ta Pruningm ost-posit ive -fixnum , Alpha Be ta Pruningm ove func tion, Ana tom y of an Anim alm ove in gam e, checking lega li ty, Drawing Only the Known Parts of the Citym ult ipa radigm language , ANSI Com m on L ispm ult iple dispa tch, E xplana tionm ult iple -va lue-bind com m and, Re turning Mult iple Valuesm uta tions, W hen to Use Struc tures, Handling Anim al Reproduc tion, Handling Anim al Reproduc tion

with reproduce func tion, Handling Anim al Reproduc tionm y-length func tion, Mem oiz ing the ra te -posit ion Func tion, Using the Sim ple Macro, Avoiding Variable Capture

custom , Using the Sim ple Macroim proving, Avoiding Variable Capture

N

nam es of func tions, ava ilable in defined func tions, Defining L oca l Func tions in L ispnam espaces, for va riables and func tions, Convert ing the E dges to Descript ionsnconc in loop m acro, Using Mult iple for Clausesnea to com m and (Graphviz ), Crea ting a Graphnega tive infini ty, Alpha Be ta Pruningne ighbors func tion, T he add-cops Func tion, Ca lcula t ing Attacking Moves, Mem oiza tionnested a l ists, T he add-cops Func tionnested form s, Code Modenested l ists, T he ca r and cdr Func tionsnested loop m acro, Using Mult iple for Clausesnested tags, in XML , W rit ing SVG Filesne twork com pute rs, com m unica tion be tween, W orking with Socke tsnever in loop m acro, Using Mult iple for Clausesnew l ine , Print ing to the Screen, Control Sequences for Form att ing Floa ting-Point Num bers, Print ing Mult iple L ines of Output ,

Print ing Mult iple L ines of Outputbefore print ing, Print ing to the Screencontrol sequences for, Print ing Mult iple L ines of Output

Page 564: Land of Lisp - Barski M.D., Conrad

in printed output , Control Sequences for Form att ing Floa ting-Point Num bersnew-gam e func tion, Building the Nodes for Congestion City, Known E dges

to draw known c ity, Known E dgesnil , Cons Ce lls, T he cons Func tion, Making Dec isions with Condit ions, T he Four Disguises of (), E xotic L ists, Dotted L ists

l ists not ending with, Dotted L istssym m etry of () and, Making Dec isions with Condit ions

nodes, Convert ing Node Identifie rs, Adding L abe ls to Graph Nodes, T he add-cops Func tionfor Congestion City, T he add-cops Func tionidentifie rs, convert ing, Convert ing Node Identifie rs

nodes->dot func tion, Adding L abe ls to Graph Nodes, Convert ing E dges into DOT Form atnonde te rm inist ic program m ing, Synopsisnonvisible charac te rs, l i te ra ls for, Sta rt ing with print and readnth func tion, Using a Generic Se tte rnull func tion, Using Func tions T ha t Re turn More than Just the T ruthnulla ry func tions, Using T hunksnum berp func tion, Sequence Func tions for Ite ra t ing Across a Sequencenum bers, Num bers, Com paring Stuff: eq, equa l , and More , Control Sequences for Form att ing Num bers

com parison, Com paring Stuff: eq, equa l , and Morecontrol sequences for form att ing, Control Sequences for Form att ing Num bers

O

objec t-oriented program m ing (OOP) languages, W here Did L isp Com e From ?, A Faste r Grand T heft W um pus Using Hash T ables,W hen to Use Struc tures, CL OS Guild Ba tt leship

vs. L isp, W hen to Use Struc turesobjec ts, Joining the Descript ions, Joining the Descript ions, Joining the Descript ions, L ist ing Visible Objec ts, W alking Around in

Our W orld, Picking Up Objec tsdescript ions, Joining the Descript ions, Joining the Descript ions, L ist ing Visible Objec ts

a t spec ific loca tion, Joining the Descript ionsvisible , L ist ing Visible Objec ts

inventory check, Picking Up Objec tspicking up, W alking Around in Our W orld

objec ts-a t func tion, L ist ing Visible Objec ts, W alking Around in Our W orld, Picking Up Objec tson in loop m acro, Using Mult iple for ClausesOOP (objec t-oriented program m ing) languages, W here Did L isp Com e From ?, A Faste r Grand T heft W um pus Using Hash T ables,

W hen to Use Struc tures, CL OS Guild Ba tt leshipvs. L isp, W hen to Use Struc tures

optim iz ing func tiona l code , Making Dice of Doom Faste r, Making Dice of Doom Faste r, Mem oiza tion, T a il Ca ll Optim iza tionc losures, Making Dice of Doom Faste rm em oiza tion, Mem oiza tionta i l ca l l optim iza tion, T a il Ca ll Optim iza tion

or opera tor, Branching with caseOrc Ba tt le gam e, T he Orc Ba tt le Gam e, Globa l Variables for the Player and Monste rs , Globa l Variables for the Player and

Monste rs, Globa l Variables for the Player and Monste rs , Main Gam e Func tions, Player Managem ent Func tions, He lper Func tions forPlayer Attacks, Monste r Managem ent Func tions, Monste r Managem ent Func tions, T he Monste rs, T he Generic Monste r, T heMalic ious Hydra , T he Malic ious Hydra , T he Slim y Slim e Mold, T o Ba tt le !

globa l va riables for player and m onste rs, Globa l Variables for the Player and Monste rshe lper func tions for player a t tacks, Player Managem ent Func tionsm ain gam e func tions, Globa l Variables for the Player and Monste rsm onste r m anagem ent func tions, He lper Func tions for Player Attacksm onste rs, Globa l Variables for the Player and Monste rs , Monste r Managem ent Func tions, Monste r Managem ent Func tions,

T he Monste rs, T he Generic Monste r, T he Malic ious Hydra , T he Malic ious Hydra , T he Slim y Slim e Moldchecking for dead, Monste r Managem ent Func tionsCunning Brigand, T he Slim y Slim e Moldfunc tions for building, Globa l Variables for the Player and Monste rsgeneric , T he Monste rshydra , T he Malic ious HydraSlim y Slim e Mold, T he Malic ious HydraW icked Orc , T he Generic Monste r

player m anagem ent func tions, Main Gam e Func tionssta rt ing gam e, T o Ba tt le !

orc da ta type , T he Generic Monste rorc -ba tt le func tion, Globa l Variables for the Player and Monste rs, T o Ba tt le !orthogona l issues, Making Our AI W ork on L arger Gam e Boardsoutput stream s, Stream s by Direc tion, Output Stream s, Input Stream s

with-open-fi le com m and for, Input Stream soutput-stream -p func tion, Output Stream s

P

padded va lue , for form at func tion, T he Control String Param ete rpadding param ete r, for num ber width, Control Sequences for Form att ing Num berspa irs, Pa irspa irs func tion, A Recursion Macro, Crea ting the tag Macro

Page 565: Land of Lisp - Barski M.D., Conrad

para lle l gam es, web se rver for m ult iple , L im ita t ions of Our Gam e W eb Serverparam ete rs, quoting, W rit ing a Custom read Func tionparam etric polym orphism , W here Did L isp Com e From ?paranoid stra tegy, Increasing the Num ber of Players() pa rentheses, T he Guess-My-Num ber Gam eparentheses (), T he Guess-My-Num ber Gam e, T he Guess-My-Num ber Gam e, An Alte rna tive Globa l Variable Definit ion Func tion ,

An Alte rna tive Globa l Variable Definit ion Func tion, Basic L isp E tique tte , Defining the sta rt-over Func tion, T he Building Blocks ofL isp Syntax, Making Dec isions with Condit ions

em pty l ists, Basic L isp E tique tte , Making Dec isions with Condit ionssym m etry of ni l and, Making Dec isions with Condit ions

for ca l l ing com m ands and func tions, T he Guess-My-Num ber Gam e, An Alte rna tive Globa l Variable Definit ion Func tionfor l ist of dec la red variables in le t , Defining the sta rt-over Func tionfor organiz ing code into l ists, T he Building Blocks of L isp Syntax

parse -integer func tion, Decoding the Values of Request Param ete rsparse -param s func tion, Decoding L ists of Request Param ete rsparse -url func tion, Decoding L ists of Request Param ete rspa th descript ions, Describing the Pa ths, Describing the Pa ths, Describing the Pa ths

in gam e, Describing the Pa thsm ult iple a t once , Describing the Pa ths

perform ance , Coping with Com plica ted Data , Using a Generic Se tte r, Using a Generic Se tte r, Re turning Mult iple Values , AFaste r Grand T heft W um pus Using Hash T ables, Using the Func tiona l Style , Making Dice of Doom Faste r, T a il Ca ll Optim iza tion

arrays vs. l ists, Using a Generic Se tte rcons ce l ls and, Coping with Com plica ted Datafor Dice of Doom gam e, Making Dice of Doom Faste rfunc tiona l program m ing and, Using the Func tiona l Stylehash tables and, Re turning Mult iple Values, A Faste r Grand T heft W um pus Using Hash T ablesta i l ca l ls and, T a il Ca ll Optim iza tion

perm itted com m ands, adding to l ist , A Com m and for W eldingperson-age func tion, W orking with Struc turespi constant , Control Sequences for Form att ing Floa ting-Point Num berspick-chance-branch func tion, Building Chance Nodespick-m onste r func tion, Player Managem ent Func tionspickup func tion, W alking Around in Our W orldpic ture , from DOT fi le , Genera ting All the DOT Dataplay-vs-com pute r func tion, T urning Minim ax into Actua l Code , T rim m ing the Gam e T reeplay-vs-hum an func tion, Dice of Doom , Version 2player func tion, Ca lcula t ing Attacking Movesplayer-a t tack func tion, Player Managem ent Func tions, Player Managem ent Func tionspolice roadblocks, Building the Fina l E dges for Congestion Citypolygon func tion, Crea ting SVG-Spec ific Macros and Func tionspolygons, for die , Drawing the Gam e Board Using the SVG Form atport , W orking with Socke ts, W orking with Socke ts, Socke t Connec tions

num ber in socke t address, W orking with Socke tstaking control of, Socke t Connec tions

port 80, Our Grand Fina le : T he se rve Func tion!port 8080, Our Grand Fina le : T he se rve Func tion!posit ion func tion, W orking with Sequences, Decoding L ists of Request Param ete rsposit ive infini ty, Alpha Be ta PruningPOST request , How a W eb Server W orkspower, loop and form at: T he Seedy Underbe lly of L isppredica tes, L ist ing Visible Objec ts, Convert ing Node Identifie rsprin1 func tion, Print ing to the Screenprin1-to-string func tion, W rit ing a gam e-print Func tion, Convert ing Node Identifie rsprinc func tion, Num bers, Reading and Print ing Stuff the W ay Hum ans L ike It , Ana tom y of the form at Func tion, T he Control

String Param ete rprint func tion, Print ing and Reading T ext, Saying Hello to the User

priori ty use , Saying Hello to the Userprint-tag func tion, Crea ting XML and HT ML with the tag Macroprinted representa t ion, c rea ting objec t from , W orking with Struc turesprint ing. See a lso form at func tion, Print ing and Reading T ext, Using T hunks, Using T hunks, Control Sequences for Form att ing

Floa ting-Point Num bers, Print ing Mult iple L ines of Outputc rea ting stream for func tions, Using T hunksm ult iple l ines of output , Control Sequences for Form att ing Floa ting-Point Num berstext just ifica t ion, Print ing Mult iple L ines of Outputto sc reen, Print ing and Reading T ext

problem solving, W hat You've L earnedprogn com m and, One T hing a t a T im e with ifprogram m ing, T rim m ing the Gam e T ree , Applying Heurist ics, Synopsis

heurist ic techniques, T rim m ing the Gam e T reenonde te rm inist ic , Synopsis

program m ing language . See a lso m acros, W hat Makes L isp So Cool and Unusua l? , Ana tom y of a Program W rit ten in theFunc tiona l Style , Higher-Order Program m ing

higher-order, Higher-Order Program m inglearning, W hat Makes L isp So Cool and Unusua l?

Page 566: Land of Lisp - Barski M.D., Conrad

propert ies in struc tures, A Faste r Grand T heft W um pus Using Hash T ablespush func tion, W alking Around in Our W orld, Assoc ia t ion L ists, Preventing Islands, A Faste r Grand T heft W um pus Using Hash

T ables, Output Stream sfor hash table va lues, A Faste r Grand T heft W um pus Using Hash T ables

pushnew com m and, A Com m and for W elding, T he gam e-ac tion MacroPython, W here Did L isp Com e From ?

Q

quasiquoting, Describing the Pa thsquit com m and, Sta rt ing Up CL ISPquote com m and, W rit ing a Custom read Func tionquote -i t func tion, W rit ing a Custom read Func tionquoting, Da ta Mode

R

ra ise -price func tion, How It Kil ls BugsRAM, Arrays vs. L istsrandom edges, T he Grand T heft W um pus Gam e, T he Grand T heft W um pus Gam e, L ooping with the loop Com m and

and island prevention, L ooping with the loop Com m andgenera ting, T he Grand T heft W um pus Gam e

random func tion, Player Managem ent Func tions, Representing the Gam e Board, Building a More Com plica ted SVG E xam plerandom num bers, genera t ing, Player Managem ent Func tionsrandom walk, Building a More Com plica ted SVG E xam plerandom -m onste r func tion, Player Managem ent Func tionsrandom -node func tion, Genera ting Random E dgesrandom -plant func tion, Growing Plants in Our W orldrandva l func tion, Player Managem ent Func tions, T he Monste rsrange of func tion, Ram ping L isp Up a Notch with Func tiona l Program m ingra te -posit ion func tion, T he Minim ax Algori thm , Mem oiz ing the ne ighbors Func tion, W inning by a L ot vs. W inning by a L it t le ,

Alpha Be ta Pruningnew versions, Alpha Be ta Pruning

ra tiona l num ber, func tion re turning, Sym bolsRDF (Resource Descript ion Fram ework), W hat Makes L isp So Cool and Unusua l?read func tion, Saying Hello to the User, T he Dangers of read and eva l , T he Dangers of read and eva l

danger of, T he Dangers of read and eva lloca l va riable for va lue re turned by, Saying Hello to the User

read-char com m and, Input Stream sread-eva l-print loop (RE PL ), Sta rt ing Up CL ISP, T he Guess-My-Num ber Gam e, Se tt ing Up a Custom RE PL , W rit ing a gam e-print

Func tion, Crea ting Custom Gam e Com m ands for W izard's Adventure Gam eloading gam e code from , Crea ting Custom Gam e Com m ands for W izard's Adventure Gam ese tt ing up custom , Se tt ing Up a Custom RE PLtest ing, W rit ing a gam e-print Func tion

read-from -string func tion, W rit ing a Custom read Func tion, L im ita t ions of Our Gam e W eb Serverread-l ine func tion, Reading and Print ing Stuff the W ay Hum ans L ike Itreader, T he Building Blocks of L isp Syntaxreader m acros, T he Dangers of read and eva lreading da ta , input stream s for, Output Stream srecurse m acro, Avoiding Variable Capturerecursion, Defining L oca l Func tions in L isp, E m pty E qua ls Fa lse , T a il Ca ll Optim iza tion, Avoiding Variable Capture

in m acros, Avoiding Variable Capturereduce func tion, W orking with Sequences, Sequence Func tions for Ite ra t ing Across a Sequence

init ia l va lue for, Sequence Func tions for Ite ra t ing Across a Sequencereduced instruc tion se t com pute r (RISC) hardware a rchitec ture , W here Did L isp Com e From ?refe rence , genera l ized, Using a Generic Se tte rre fe rentia l t ransparency, W hat Is Func tiona l Program m ing? , Func tiona l Program m ing Reduces Bugsre inforcem ents, rules for choosing num ber in Dice of Doom , Im proving the Dice of Doom Reinforcem ent Rulesrem hash func tion, Handling Anim al T urningrem ove-duplica tes func tion, T he edges-to-a l ist Func tion, De te rm ining the W innerrem ove-if func tion, De te rm ining the W innerrem ove-if-not func tion, L ist ing Visible Objec ts, Preventing IslandsRE PL . See read-eva l-print loop (RE PL ), Handling Anim al Reproduc tionreproduce func tion, Handling Anim al Reproduc tion, Handling Anim al Reproduc tion

m uta tions with, Handling Anim al Reproduc tionrequest body, How a W eb Server W orks, T est ing ge t-header with a String Stream

parsing, T est ing ge t-header with a String Streamrequest handle r, te st ing, Our Grand Fina le : T he se rve Func tion!request header, How a W eb Server W orks, Decoding L ists of Request Param ete rs

parsing, Decoding L ists of Request Param ete rsrequest pa ram ete rs, How a W eb Server W orks, Request Param ete rs, Decoding the Values of Request Param ete rs , Decoding the

Values of Request Param ete rsdecoding l ists of, Decoding the Values of Request Param ete rsdecoding va lues for HT T P, Request Param ete rs

Page 567: Land of Lisp - Barski M.D., Conrad

for web se rver, How a W eb Server W orksrequest-handle r func tion, Our Grand Fina le : T he se rve Func tion!request-handle r pa ram ete r, Our Grand Fina le : T he se rve Func tion!Resource Descript ion Fram ework (RDF), W hat Makes L isp So Cool and Unusua l?resources, free ing up, Sending a Message over a Socke tresponse body, How a W eb Server W orksresponse header, How a W eb Server W orksresta rts, Synopsisre turn va lue , for com m and, Basic L isp E tique ttereverse func tion, Ana tom y of the form at Func tionRISC (reduced instruc tion se t com pute r) ha rdware a rchitec ture , W here Did L isp Com e From ?roll-dice func tion, Building Chance Nodesround func tion, Re turning Mult iple ValuesRuby, W here Did L isp Com e From ?rule engine , Decoupling Dice of Doom 's Rules from the Rest of the Gam eruntim e , Macro E xpansion

S

say-he llo func tion, Print ing to the ScreenSBCL (Stee l Bank Com m on L isp), ANSI Com m on L ispsca lable vec tor graphics (SVG). See SVG im ages, Basic Requirem entsscenery descript ion, assoc ia t ion l ist for, Basic Requirem entsSchem e, L isp Dia lec ts, Convert ing the E dges to Descript ions, T a il Ca ll Optim iza tion

nam espace for, Convert ing the E dges to Descript ionsta i l ca l l optim iza tion in, T a il Ca ll Optim iza tion

score -board func tion, W inning by a L ot vs. W inning by a L it t lesc reen, print ing to, Print ing and Reading T extScript-Fu Schem e, L isp Dia lec ts Used for Script ingscript ing, L isp dia lec ts for, A T a le of T wo L ispssearching, W orking with Sequences, Mapping and Searching Across L azy L ists, Mapping and Searching Across L azy L ists

lazy l ists, Mapping and Searching Across L azy L istssequence func tions for, W orking with Sequences

securi ty, eva l func tion and, T he Sym m etry Be tween Code and Data in L ispse lf func tion, A Recursion Macrose lf-re fe rentia l da ta struc tures, Circula r L istsSem antic W eb, If L isp Is So Grea t , W hy Don't More People Use It?sem antics, E xploring the Syntax of L isp Codesending m essage over socke t , Socke t Connec tionssequence func tions, W hen to Use Struc tures, W orking with Sequences

for sea rching, W orking with Sequencessequences, W hen to Use Struc tures, W orking with Sequences

ite ra t ing ac ross, W orking with Sequencesse rve func tion, T est ing ge t-header with a String Streamserver, for socke t connec tion, Socke t Connec tionsse t-diffe rence func tion, Preventing Islandsse tf func tion, Defining the guess-m y-num ber Func tion, Picking Up Objec ts, Circula r L ists, W orking with Arrays, W orking with

Struc tures, Mem oiza tion, Generic Se tte r Guild Supply Shipfor a rray, W orking with Arraysto change struc ture property, W orking with Struc tures

sha llow copy of struc ture , Handling Anim al Reproduc tionShort Code , W here Did L isp Com e From ?shortcut Boolean eva lua tion, Using the Stea lth Condit iona ls and and orshow-m onste rs func tion, Monste r Managem ent Func tionsshutt ing down CL ISP, Sta rt ing Up CL ISPside e ffec ts, W hat Is Func tiona l Program m ing? , W hy Func tiona l Program m ing Is Crazy, Synopsis

of func tiona l program m ing, W hat Is Func tiona l Program m ing? , W hy Func tiona l Program m ing Is Crazysigna ling condit ion, for e rror handling, E rror Handling in Com m on L ispsin func tion, W hat Is Func tiona l Program m ing?' (single quote ), a s da ta indica tor, Da ta Modesingle quote ('), a s da ta indica tor, Da ta Modeslots, W orking with Struc turessm alle r func tion, Defining the guess-m y-num ber Func tionsocke t stream s, W orking with Stream ssocke t , se rve func tion c rea tion of, Our Grand Fina le : T he se rve Func tion!socke t-accept com m and, Sending a Message over a Socke tsocke t-connec t com m and, Sending a Message over a Socke tsocke t-se rver func tion, Socke t Connec tionssocke t-se rver-c lose com m and, T idying Up Afte r Ourse lvessocke ts, W orking with Files, W orking with Socke ts, Socke t Connec tions, Socke t Connec tions

addresses, W orking with Socke tsconnec tions, Socke t Connec tionssending m essage over, Socke t Connec tions

software transac tiona l m em ory, Synopsis

Page 568: Land of Lisp - Barski M.D., Conrad

som e func tion, W orking with Sequencessort func tion, Sequence Func tions for Ite ra t ing Across a Sequencespec ia l form , One T hing a t a T im e with if, One T hing a t a T im e with if, T he Magic of L isp Macros

if as, One T hing a t a T im e with ifle t com m and as, T he Magic of L isp Macros

spec ia l va riable , An Alte rna tive Globa l Variable Definit ion Func tionsplash com m and, T he gam e-ac tion Macrospli t m acro, More Com plex Macrosspli t t ing l ists, m acro for, More Com plex Macrossta rt-over func tion, Defining the sta rt-over Func tionsta rt ing CL ISP, Sta rt ing Up CL ISPsta t ist ics, of dice rol ls, Upda ting the AIStee l Bank Com m on L isp (SBCL ), Ge tt ing Sta rted with CL ISPStee le , Guy L . , L isp Dia lec tsstream s, Crea ting a Stream , W orking with Stream s, T ypes of Stream s, Input Stream s, Input Stream s, Sending a Message over a

Socke t , Sending a Message over a Socke tbidirec t iona l , Sending a Message over a Socke tc losing on ne twork com pute r, Sending a Message over a Socke tcom m ands to inte rac t with, Input Stream sfor fi le s, Input Stream stypes, T ypes of Stream s

string builders, W orking with L ong Stringsstring da ta type , Describing the Scenery with an Assoc ia t ion L iststring stream s, Stream s by T ype of Resource , T idying Up Afte r Ourse lves, W orking with L ong Strings, Parsing the Request Header

debugging and, W orking with L ong Stringsge t-header func tion test ing with, Parsing the Request Header

string-downcase func tion, Crea ting XML and HT ML with the tag Macrostring-equa l func tion, Com paring Stuff: eq, equa l , and Morestringp func tion, Sequence Func tions for Ite ra t ing Across a Sequencestrings, Num bers, W rit ing a gam e-print Func tion, W hen to Use Struc tures

convert ing sym bol l ist to, W rit ing a gam e-print Func tionsequence func tions for, W hen to Use Struc tures

Stroustrup, Bja rne , W here Does L isp Get Its Power?struc tures, A Faste r Grand T heft W um pus Using Hash T ables, W hen to Use Struc tures, W hen to Use Struc tures

vs. l ists in L isp code , W hen to Use Struc tureswhen to use , W hen to Use Struc tures

subseq func tion, Sequence Func tions for Ite ra t ing Across a Sequencesubsti tute -if func tion, Convert ing Node Identifie rssubsti tute -if-not func tion, Convert ing Node Identifie rssum func tion, for a rrays and l ists, Sequence Func tions for Ite ra t ing Across a Sequencesum in loop m acro, T he loop Macro, Using Mult iple for Clausessuspension, Using T hunksSussm an, Gera ld Jay, L isp Dia lec tsSVG im ages, W hat Is a Dom ain? , Using the tag Macro to Genera te HT ML , Crea ting SVG-Spec ific Macros and Func tions,

Crea ting SVG-Spec ific Macros and Func tions, Crea ting SVG-Spec ific Macros and Func tions, Drawing the Gam e Board Using theSVG Form at

a ttributes for, Using the tag Macro to Genera te HT MLcirc les, Crea ting SVG-Spec ific Macros and Func tionsDice of Doom gam e board using, Drawing the Gam e Board Using the SVG Form atpolygons, Crea ting SVG-Spec ific Macros and Func tionswrit ing, W hat Is a Dom ain?

svg m acro, Using the tag Macro to Genera te HT MLSVG W eb, W rit ing SVG Filessvg-style func tion, Crea ting SVG-Spec ific Macros and Func tionssym bol-func tion com m and, Mem oiza tionsym bolp func tion, Sequence Func tions for Ite ra t ing Across a Sequencesym bols, T he Building Blocks of L isp Syntax, Com paring Stuff: eq, equa l , and More , Describing the Scenery with an Assoc ia t ion

L ist , W rit ing a gam e-print Func tionbenefi ts of using, Describing the Scenery with an Assoc ia t ion L istcom paring, Com paring Stuff: eq, equa l , and Moreconvert ing l ist to string, W rit ing a gam e-print Func tion

sym m etry, Making Dec isions with Condit ions, Making Dec isions with Condit ions, Reading and Print ing Stuff the W ay Hum ansL ike It

be tween code and da ta , Reading and Print ing Stuff the W ay Hum ans L ike Itof () and nil , Making Dec isions with Condit ions

syntax, E xploring the Syntax of L isp Code , Syntax and Sem antics, Syntax and Sem anticsand sem antics, E xploring the Syntax of L isp Codebuilding blocks for L isp, Syntax and Sem antics

T

tab variable , Mem oiz ing the ra te -posit ion Func tiontables, Print ing Mult iple L ines of Output , Just ifying Output , Ite ra t ing T hrough L ists Using Control Sequences

output as, Print ing Mult iple L ines of Output

Page 569: Land of Lisp - Barski M.D., Conrad

t rick for c rea ting pre t ty, Ite ra t ing T hrough L ists Using Control Sequencestag m acro, Crea ting the tag Macro, Crea ting the tag Macro

to genera te HT ML , Crea ting the tag Macrota i l ca l l , T a il Ca ll Optim iza tionta i l ca l l optim iza tion, T a il Ca ll Optim iza tiontake func tion, Convert ing Be tween Regula r L ists and L azy L iststake-a l l func tion, Convert ing Be tween Regula r L ists and L azy L istsT CP packe ts, Socke t AddressesT CP/IP, How a W eb Server W orkstechnologies support ing L isp, com ic book, E piloguete rpri func tion, Control Sequences for Form att ing Floa ting-Point Num berstest func tions, Convert ing Node Identifie rstest ing, W rit ing a gam e-print Func tion, Parsing the Request Header, Parsing the Request Header

ge t-header func tion with string stream , Parsing the Request Headeruser inte rface , W rit ing a gam e-print Func tion

text gam e inte rface , Adding a Custom Inte rface to Our Gam e E ngine , W rit ing a gam e-print Func tiontest ing, W rit ing a gam e-print Func tion

text . See a lso strings, Building a T ext Gam e E ngine , W rit ing a gam e-print Func tion, Print ing Mult iple L ines of Output , Ite ra t ingT hrough L ists Using Control Sequences, Ite ra t ing T hrough L ists Using Control Sequences

breaking into equa l length pieces, Ite ra t ing T hrough L ists Using Control Sequencesconvert ing a l l caps to capita l ized, W rit ing a gam e-print Func tionjust ified, Print ing Mult iple L ines of Outputprocessing, Building a T ext Gam e E ngine

then in loop m acro, Using Mult iple for Clausesthere is in loop m acro, Using Mult iple for Clausesthrea tened func tion, W inning by a L ot vs. W inning by a L it t lethrea tened hex, in Dice of Doom , W inning by a L ot vs. W inning by a L it t lethree -way-if m acro, Macro Guild Melee Fighte rsthunks, Genera ting All the DOT Data , Capturing the Console Output

for c rea ting graph pic ture , Capturing the Console Output˜ (t i lde ), for control sequences, T he Control String Param ete rt i lde (˜), for control sequences, T he Control String Param ete rt im e com m and, Hash T able Perform anceto in loop m acro, Using Mult iple for Clausestop-leve l de fini t ion of va riable , T he Guess-My-Num ber Gam etree -l ike da ta , Coping with Com plica ted Datatrue /fa lse func tions, L ist ing Visible Objec tsturn func tion, for anim als, Handling Anim al Motiontweak-text func tion, W rit ing a gam e-print Func tiontype dispa tching, Crea ting Your Own Generic Func tions with T ype Predica testype predica tes, for generic func tions, Sequence Func tions for Ite ra t ing Across a Sequencetype-checking, W orking with Sequences, W orking with Sequences

in generic func tions, W orking with Sequencestype-of func tion, T he Monste rs

U

uedges->dot func tion, Crea ting Undirec ted Graphsugraph->dot func tion, Crea ting Undirec ted Graphsugraph->png func tion, Crea ting Undirec ted Graphs, Ini t ia l iz ing a New Gam e of Grand T heft W um pusundirec ted graphs, Crea ting a Pic ture of Our Graphunless, Going Beyond if: T he when and unless Alte rna tives, Using Mult iple for Clauses

in loop m acro, Using Mult iple for Clausesunwind-protec t func tion, Protec ting Resources Aga inst Unexpec ted Condit ions, Our Grand Fina le : T he se rve Func tion!upda te -world func tion, Handling Anim al Reproduc tionupfrom in loop m acro, Using Mult iple for Clausesupto in loop m acro, Using Mult iple for ClausesURL s for web pages, nam e/va lue pa irs in, Decoding the Values of Request Param ete rsuser inte rface , Inte rac ting with the W orld: Reading and Print ing in L isp, Inte rac ting with the W orld: Reading and Print ing in

L isp, Print ing and Reading T ext, T he Sym m etry Be tween Code and Data in L isp, W rit ing a gam e-print Func tion, Drawing OurW orld

com m and-line , Inte rac ting with the W orld: Reading and Print ing in L isp, Print ing and Reading T extprint ing to sc reen, Print ing and Reading T ext

for evolving environm ent gam e, Drawing Our W orldfor W izard’s Adventure Gam e, T he Sym m etry Be tween Code and Data in L isptest ing, W rit ing a gam e-print Func tion

usocke t , W orking with Socke ts

V

vacuum -tube com pute r system s, If L isp Is So Grea t , W hy Don't More People Use It?va lues func tion, Re turning Mult iple Valuesvariable capture , Avoiding Repea ted E xecution in Macrosvariable shadowing, T a il Ca ll Optim iza tion

Page 570: Land of Lisp - Barski M.D., Conrad

variables. See a lso globa l va riables; loca l va riables, T he Guess-My-Num ber Gam e, T he Guess-My-Num ber Gam e, Defining thesta rt-over Func tion, Basic Requirem ents, Convert ing the E dges to Descript ions, Capturing the Console Output , T he m ake-c ity-edgesFunc tion, W hat Is Func tiona l Program m ing? , Func tiona l Program m ing Reduces Bugs, Closures, Closures, Avoiding VariableCapture , Generic Se tte r Guild Supply Ship

aste risks (*) in nam es, T he Guess-My-Num ber Gam edec la ra t ion in le t com m and, Defining the sta rt-over Func tiondefining, T he m ake-c ity-edges Func tiondestruc tion, Closuresfor loca tion descript ions, Basic Requirem entsfunc tion to c rea te unique nam e, Avoiding Variable Capturein func tiona l program m ing, W hat Is Func tiona l Program m ing? , Func tiona l Program m ing Reduces Bugslexica l , Capturing the Console Output , Closuresm odifying va lue , Generic Se tte r Guild Supply Shipnam espaces for, Convert ing the E dges to Descript ions

versions of func tion, Crea ting Your Own Generic Func tions with T ype Predica tes| (ve rt ica l pipe), for case -sensit ive sym bols, Sta rt ing with print and readvert ica l pipe (|), for case -sensit ive sym bols, Sta rt ing with print and readvirtua l m em ory paging, pe rform ance im pac t , Re turning Mult iple Valuesvisible objec ts, desc ribing, L ist ing Visible Objec tsvisua l noise , A Sim ple L isp Macrovisua liz ing graphs, Visua liz ing T ree-l ike Data

W

walk func tion, W alking Around in Our W orld, Drawing Only the Known Parts of the Cityweb form s, Request Param ete rsweb resources, If L isp Is So Grea t , W hy Don't More People Use It? , ANSI Com m on L isp, ANSI Com m on L isp, Crea ting a Graph

downloading CL ISP insta l le r, ANSI Com m on L ispfor Graphviz , Crea ting a GraphL isp projec ts, If L isp Is So Grea t , W hy Don't More People Use It?

web se rver, Protec ting Resources Aga inst Unexpec ted Condit ions, W rit ing a W eb Server from Scra tch, How a W eb Server W orks,Decoding L ists of Request Param ete rs, T est ing ge t-header with a String Stream , T esting ge t-header with a String Stream , Drawingthe Board, W rit ing Our W eb Request Handle r, L im ita t ions of Our Gam e W eb Server, Handling the Com pute r Player, How It Kil lsBugs

continua tion-aware , How It Kil ls Bugshow it works, Protec ting Resources Aga inst Unexpec ted Condit ionsinte rface for Dice of Doom , Drawing the Board, W rit ing Our W eb Request Handle r, L im ita t ions of Our Gam e W eb Server,

Handling the Com pute r Playerfor com pute r player, Handling the Com pute r Playerfor hum an player, L im ita t ions of Our Gam e W eb Serverl im ita t ions, W rit ing Our W eb Request Handle r

parsing request body, T est ing ge t-header with a String Streamparsing request header, Decoding L ists of Request Param ete rsrequest pa ram ete rs, How a W eb Server W orksserve func tion, T est ing ge t-header with a String Stream

web-announce-winner func tion, L im ita t ions of Our Gam e W eb Serverweb-handle -hum an func tion, L im ita t ions of Our Gam e W eb Serverweb-init ia l ize func tion, W rit ing Our W eb Request Handle r, L im ita t ions of Our Gam e W eb Serverwebserver. l isp fi le , Drawing the Gam e Board Using the SVG Form atwebsite , Our Grand Fina le : T he se rve Func tion!, Our Grand Fina le : T he se rve Func tion!, T est ing the Request Handle r

dynam ic , Our Grand Fina le : T he se rve Func tion!launching, T est ing the Request Handle r

weld func tion, A Com m and for W elding, T he gam e-ac tion Macrowhen in loop m acro, Using Mult iple for Clauseswhen token, Going Beyond if: T he when and unless Alte rna tives, Counting from a Sta rt ing Point to an E nding Pointwinners func tion, Giving Inform ation About the Sta te of the Gam ewith-open-fi le com m and, Using T hunks, Understanding Keyword Param ete rs, Capturing the Console Output , Input Stream swith-open-stream m acro, Our Grand Fina le : T he se rve Func tion!with-output-to-string m acro, W orking with L ong StringsW izard’s Adventure Gam e, Building a T ext Gam e E ngine , Our Gam e W orld, Our Gam e W orld, Basic Requirem ents, Describing

the Scenery with an Assoc ia t ion L ist , Describing the Pa ths, Joining the Descript ions, Describing Visible Objec ts, W alking Around inOur W orld, W alking Around in Our W orld, Picking Up Objec ts, T he Sym m etry Be tween Code and Data in L isp, Visua liz ing T ree-l ike Data , Convert ing E dges into DOT Form at, Crea ting Custom Gam e Com m ands for W izard's Adventure Gam e, Crea ting NewGam e Com m ands by Hand, A Com m and for W elding, A Com m and for Dunking, T he gam e-ac tion Macro

basic requirem ents, Our Gam e W orldcustom gam e com m ands, Crea ting Custom Gam e Com m ands for W izard's Adventure Gam e, Crea ting New Gam e Com m ands

by Hand, A Com m and for W elding, A Com m and for Dunkingdunk, A Com m and for W eldinggam e-ac tion m acro, A Com m and for Dunkingwelding, Crea ting New Gam e Com m ands by Hand

custom inte rface , T he Sym m etry Be tween Code and Data in L ispDOT inform ation for, Convert ing E dges into DOT Form atloca tion descript ions, Describing the Scenery with an Assoc ia t ion L istlook com m and, Describing Visible Objec ts

Page 571: Land of Lisp - Barski M.D., Conrad

m ap of house in a l ists, Visua liz ing T ree-l ike Dataobjec t desc ript ions a t spec ific loca tion, Joining the Descript ionsobjec t inventory check, Picking Up Objec tspa th descript ions, Describing the Pa thspicking up objec ts, W alking Around in Our W orldplaying com ple ted version, T he gam e-ac tion Macroscenery descript ion with assoc ia t ion l ist , Basic Requirem entswalk func tion, W alking Around in Our W orldworld for, Building a T ext Gam e E ngine

write -char com m and, Output Stream s

X

XML , Visua liz ing T ree-l ike DataXML form at, W rit ing SVG Files, W rit ing SVG Files, W rit ing SVG Files

and SVG form at, W rit ing SVG Filesnested tags, W rit ing SVG Files

xm lns a t tribute , Using the tag Macro to Genera te HT ML

Z

zero, dividing by, One T hing a t a T im e with if

Page 572: Land of Lisp - Barski M.D., Conrad

About the Author

Conrad Barski has an M.D. from the Universi ty of Miam i, and nearly 20 years of program m ing experience . T his inc ludes a st intdeve loping an obscure Ata ri Jaguar gam e, and working on m any m edica l software projec ts. Barski is a lso an avid ca rtoonist , havingcrea ted the popula r a l ien L isp m ascot and m any graphica l tutoria ls. He currently deve lops ca rdiology software and l ives inW ashington, D.C.

Page 573: Land of Lisp - Barski M.D., Conrad

Updates

Visit ht tp: / /www.nosta rch. com /lisp.htm for upda tes, e rra ta , and other inform ation.Land of Lisp is se t in New Baskervil le , T heSansMono Condensed, Futura , and Dogm a.T his book was printed and bound by T ranscontinenta l , Inc . a t T ranscontinenta l Gagné in L ouisevil le , Quebec , Canada . T he paper

is Dom tar Husky 60# Sm ooth, which is ce rt ified by the Forest Stewardship Counc il (FSC). T he book has an Otabind binding, whicha llows i t to lay fla t when open.

Page 574: Land of Lisp - Barski M.D., Conrad