Vanlig Lisp

Vanlig Lisp
Lisp logo.svg
Paradigm Multiparadigm : procedurmässigt , funktionellt , objektorienterat , meta , reflekterande , generiskt
Familj Läspa
Designad av Scott Fahlman , Richard P. Gabriel , David A. Moon , Kent Pitman , Guy Steele , Dan Weinreb
Utvecklare ANSI X3J13- kommittén
Dök först upp 1984 (39 år sedan) ( 1984 ) , 1994 (29 år sedan) ( 1994 ) för ANSI Common Lisp
Maskinskrivningsdisciplin Dynamisk , stark
Omfattning Lexikal, valfritt dynamisk
OS Cross-plattform
Filnamnstillägg .lisp, .lsp, .l, .cl, .fasl
Hemsida common-lisp .net
Stora implementeringar
Allegro CL , ABCL , Clasp, , Clozure CL , CMUCL , ECL , GCL , LispWorks , Scieneer CL, SBCL , Symbolics Common Lisp
Influenced CLISP Lisp , Lisp Machine Schema , Lisp Machine Schema Interlisp
Dialects
CLtL1, CLtL2, ANSI Common Lisp
by
Influenced
Clojure , Dylan , Emacs Lisp , EuLisp , ISLISP , *Lisp , AutoLisp , Julia , Moose , R , SKILL , SubL

Common Lisp ( CL ) är en dialekt av programmeringsspråket Lisp , publicerad i ANSI standarddokument ANSI INCITS 226-1994 (S20018) (tidigare X3.226-1994 (R1999) ) . Common Lisp HyperSpec , en hyperlänkad HTML- version, har härletts från ANSI Common Lisp-standarden.

Common Lisp-språket utvecklades som en standardiserad och förbättrad efterföljare till Maclisp . I början av 1980-talet var flera grupper redan igång med olika efterföljare till MacLisp: Lisp Machine Lisp (aka ZetaLisp), Spice Lisp , NIL och S-1 Lisp . Common Lisp försökte förena, standardisera och utöka funktionerna i dessa MacLisp-dialekter. Common Lisp är inte en implementering, utan snarare en språkspecifikation . Flera implementeringar av Common Lisp-standarden är tillgängliga, inklusive gratis och öppen källkod och proprietära produkter. Common Lisp är ett multiparadigm programmeringsspråk för allmänna ändamål . Den stöder en kombination av procedurmässiga , funktionella och objektorienterade programmeringsparadigm . Som ett dynamiskt programmeringsspråk underlättar det evolutionär och inkrementell mjukvaruutveckling , med iterativ kompilering till effektiva körtidsprogram. Denna inkrementella utveckling görs ofta interaktivt utan att avbryta den pågående applikationen.

Den stöder också valfri typkommentar och casting, som kan läggas till vid behov vid senare profilerings- och optimeringsstadier, för att tillåta kompilatorn att generera mer effektiv kod. Till exempel fixnum innehålla ett heltal utan förpackning i ett intervall som stöds av hårdvaran och implementeringen, vilket möjliggör effektivare aritmetik än på stora heltal eller godtyckliga precisionstyper. På samma sätt kan kompilatorn få besked per modul eller per funktion vilken typ av säkerhetsnivå som önskas, med hjälp av optimeringsdeklarationer .

Common Lisp inkluderar CLOS , ett objektsystem som stöder multimetoder och metodkombinationer. Det implementeras ofta med ett Metaobject Protocol.

Common Lisp kan utökas med standardfunktioner som Lisp- makron (kodtransformationer) och läsarmakron (indataparsers för tecken).

Common Lisp ger delvis bakåtkompatibilitet med Maclisp och John McCarthys original Lisp . Detta gör att äldre Lisp-programvara kan portas till Common Lisp.

Historia

Arbetet med Common Lisp startade 1981 efter ett initiativ av ARPA-chefen Bob Engelmore för att utveckla en enda gemenskapsstandard Lisp-dialekt. Mycket av den ursprungliga språkdesignen gjordes via e-post. 1982 Guy L. Steele Jr. den första översikten av Common Lisp vid 1982 ACM Symposium om LISP och funktionell programmering.

Den första språkdokumentationen publicerades 1984 som Common Lisp the Language (känd som CLtL1), första upplagan. En andra utgåva (känd som CLtL2), publicerad 1990, innehöll många ändringar av språket, som gjordes under ANSI Common Lisp-standardiseringsprocessen: utökad LOOP-syntax, Common Lisp Object System, Condition System för felhantering, ett gränssnitt till snygg skrivare och mycket mer. Men CLtL2 beskriver inte den slutliga ANSI Common Lisp-standarden och är därför inte en dokumentation av ANSI Common Lisp. Den slutliga ANSI Common Lisp-standarden publicerades sedan 1994. Sedan dess har ingen uppdatering av standarden publicerats. Olika tillägg och förbättringar av Common Lisp (exempel är Unicode, Concurrency, CLOS-baserad IO) har tillhandahållits av implementeringar och bibliotek.

Syntax

Common Lisp är en dialekt av Lisp. Den använder S-uttryck för att beteckna både kod och datastruktur. Funktionsanrop, makroformulär och specialformulär skrivs som listor, med namnet på operatören först, som i dessa exempel:

               (  +22  )  ;  _  _  lägger till 2 och 2, vilket ger 4. Funktionens namn är '+'. Lisp har inga operatörer som sådana.  
        
                   
                   
                   
                   
       (  defvar  *x*  )  ; Säkerställer att en variabel *x* finns,   ; utan att ge det ett värde.  Asteriskerna är en del av   ; namnet, enligt konvention som betecknar en speciell (global) variabel.   ; Symbolen *x* förses också härmed med egenskapen att   ; efterföljande bindningar av det är dynamiska, snarare än lexikaliska.   (  setf  *x*  42.1  )  ; Ställer in variabeln *x* till flyttalsvärdet 42,1  
 
   
      ;; Definiera en funktion som kvadrerar ett tal:   (  defun  square  (  x  )  (  *  x  x  )) 
 
           ;; Utför funktionen:   (  ruta  3  )  ; Returnerar 9  
 
 
 
 
 
 
   
        
              ;; "Låt"-konstruktionen skapar ett utrymme för lokala variabler.  Här   ;; variabeln 'a' är bunden till 6 och variabeln 'b' är bunden   ;; till 4. Inuti 'let' finns en 'body', där det senast beräknade värdet returneras.   ;; Här returneras resultatet av att lägga till a och b från uttrycket 'låt'.   ;; Variablerna a och b har lexikalisk omfattning, såvida inte symbolerna har varit   ;; markerade som speciella variabler (till exempel av en tidigare DEFVAR).   (  låt  ((  a  6  )  (  b  4  ))  (  +  a  b  ))  ; returnerar 10  

Datatyper

Common Lisp har många datatyper .

Skalära typer

Taltyper inkluderar heltal , kvoter , flyttal och komplexa tal . Common Lisp använder bignums för att representera numeriska värden av godtycklig storlek och precision. Förhållandetypen representerar bråkdelar exakt, en funktion som inte är tillgänglig på många språk. Common Lisp tvingar automatiskt numeriska värden bland dessa typer efter behov.

Teckentypen Common Lisp är inte begränsad till ASCII- tecken. De flesta moderna implementeringar tillåter Unicode- tecken.

Symboltypen är gemensam för Lisp-språk, men i stort sett okänd utanför dem . En symbol är ett unikt namngivet dataobjekt med flera delar: namn, värde, funktion, egenskapslista och paket. Av dessa värdecell och funktionscell de viktigaste. Symboler i Lisp används ofta på samma sätt som identifierare på andra språk: för att hålla värdet på en variabel; men det finns många andra användningsområden. Normalt, när en symbol utvärderas, returneras dess värde. Vissa symboler utvärderar sig själva, till exempel är alla symboler i nyckelordspaketet självutvärderande. Booleska värden i Common Lisp representeras av de självutvärderande symbolerna T och NIL. Common Lisp har namnutrymmen för symboler, kallade "paket".

Ett antal funktioner finns tillgängliga för att avrunda skalära numeriska värden på olika sätt. Funktionen rundar av argumentet till närmaste heltal, med halvvägs fall avrundade till det jämna heltal. Funktionerna trunkera , golv och tak avrunda mot noll, ner respektive uppåt. Alla dessa funktioner returnerar den kasserade bråkdelen som ett sekundärt värde. Till exempel, (golv -2,5) ger −3, 0,5; (tak -2,5) ger −2, −0,5; (omgång 2,5) ger 2, 0,5; och (omgång 3,5) ger 4, -0,5.

Data struktur

Sekvenstyper i Common Lisp inkluderar listor, vektorer, bitvektorer och strängar. Det finns många operationer som kan fungera på vilken sekvenstyp som helst.

Som i nästan alla andra Lisp-dialekter är listor i Common Lisp sammansatta av conses , ibland kallade cons-celler eller par . En nackdel är en datastruktur med två platser, som kallas dess bil och cdr . En lista är en länkad kedja av fördelar eller den tomma listan. Varje nackdelars bil hänvisar till en medlem i listan (eventuellt en annan lista). Varje nackdelars cdr hänvisar till nästa nackdelar – förutom de sista nackdelarna i en lista, vars cdr refererar till nollvärdet . Conses kan också enkelt användas för att implementera träd och andra komplexa datastrukturer; även om det vanligtvis rekommenderas att använda struktur- eller klassinstanser istället. Det är också möjligt att skapa cirkulära datastrukturer med conses.

Common Lisp stöder flerdimensionella arrayer och kan dynamiskt ändra storlek på justerbara arrayer om det behövs. Flerdimensionella arrayer kan användas för matrismatematik. En vektor är en endimensionell array. Arrayer kan bära vilken typ som helst som medlemmar (även blandade typer i samma array) eller kan vara specialiserade för att innehålla en specifik typ av medlemmar, som i en vektor av bitar. Vanligtvis stöds bara ett fåtal typer. Många implementeringar kan optimera arrayfunktioner när arrayen som används är typspecialiserad. Två typspecialiserade arraytyper är standard: en sträng är en vektor av tecken, medan en bitvektor är en vektor av bitar .

Hash-tabeller lagrar associationer mellan dataobjekt. Vilket objekt som helst kan användas som nyckel eller värde. Hash-tabeller ändras automatiskt efter behov.

Paket är samlingar av symboler, som främst används för att separera delarna av ett program i namnutrymmen . Ett paket kan exportera vissa symboler och markera dem som en del av ett offentligt gränssnitt. Paket kan använda andra paket.

Strukturer , som liknar C -strukturer och Pascal- poster, representerar godtyckliga komplexa datastrukturer med valfritt antal och typ av fält (kallade slots ). Strukturer tillåter enkelarv.

Klasser liknar strukturer, men erbjuder mer dynamiska funktioner och multipelt arv. (Se STÄNG ). Klasser har lagts till sent i Common Lisp och det finns en viss begreppsmässig överlappning med strukturer. Objekt skapade av klasser kallas Instanser . Ett specialfall är Generic Functions. Generiska funktioner är både funktioner och instanser.

Funktioner

Common Lisp stöder förstklassiga funktioner . Det är till exempel möjligt att skriva funktioner som tar andra funktioner som argument eller också returnera funktioner. Detta gör det möjligt att beskriva mycket generella operationer.

Common Lisp-biblioteket är starkt beroende av sådana funktioner av högre ordning. Till exempel tar sorteringsfunktionen en relationsoperator som ett argument och nyckelfunktionen som ett valfritt nyckelordsargument. Detta kan användas inte bara för att sortera vilken typ av data som helst, utan också för att sortera datastrukturer enligt en nyckel.

 
            
             ;; Sorterar listan med funktionen > och < som relationsoperator.   (  sortera  (  lista  5  2  6  3  1  4  )  #'  >  )  ; Returnerar (6 5 4 3 2 1)   (  sortera  (  lista  5  2  6  3  1  4  )  #'  <  )  ; Returer (1 2 3 4 5 6)  
 
               ;; Sorterar listan enligt det första elementet i varje underlista.   (  sortera  (  lista  '  (  9  A  )  '  (  3  B  )  '  (  4  C  ) )  # '  <  : nyckel  # '  först  )  ; Returnerar ((3 B) (4 C) (9 A))  

Utvärderingsmodellen för funktioner är mycket enkel. När utvärderaren stöter på ett formulär (f a1 a2...) då förutsätter den att symbolen med namnet f är en av följande:

  1. En speciell operatör (enkelt kontrolleras mot en fast lista)
  2. En makrooperator (måste ha definierats tidigare)
  3. Namnet på en funktion (standard), som antingen kan vara en symbol eller en underform som börjar med symbolen lambda .

Om f är namnet på en funktion, så utvärderas argumenten a1, a2, ..., an i ordning från vänster till höger, och funktionen hittas och anropas med de värden som anges som parametrar.

Definiera funktioner

Makro defun definierar funktioner där en funktionsdefinition ger namnet funktionen, namnen på eventuella argument och en funktionskropp:

   
      (  defun  square  (  x  )  (  *  x  x  )) 

Funktionsdefinitioner kan inkludera kompilatordirektiv , kända som deklarationer , som ger kompilatorn tips om optimeringsinställningar eller datatyper av argument. De kan också inkludera dokumentationssträngar (docstrings), som Lisp-systemet kan använda för att tillhandahålla interaktiv dokumentation:

   
   
          0  
        (  defun  square  (  x  )  "Beräknar kvadraten av single-float x."  (  declare  (  single-float  x  )  (  optimize  (  speed  3  )  (  debug  )  (  safety  1  )))  (  single  -float  (  *  x  x )  ))) 

Anonyma funktioner ( funktionsliterals ) definieras med lambda- uttryck, t.ex. (lambda (x) (* x x)) för en funktion som kvadrerar sitt argument. Lisp programmeringsstil använder ofta högre ordningsfunktioner för vilka det är användbart att tillhandahålla anonyma funktioner som argument.

Lokala funktioner kan definieras med flet och etiketter .

   
            
     (  flet  ((  square  (  x  )  (  *  x  x  )))  (  ruta  3  )) 

Det finns flera andra operatörer relaterade till definition och manipulering av funktioner. Till exempel kan en funktion kompileras med kompileringsoperatorn . (Vissa Lisp-system kör funktioner med hjälp av en tolk som standard om de inte uppmanas att kompilera; andra kompilerar varje funktion).

Definiera generiska funktioner och metoder

Makro defgeneric definierar generiska funktioner . Generiska funktioner är en samling metoder . Makrodefmetoden definierar metoder .

Metoder kan specialisera sina parametrar över CLOS standardklasser , systemklasser , strukturklasser eller enskilda objekt. För många typer finns det motsvarande systemklasser .

När en generisk funktion anropas kommer multipelutskick att avgöra vilken effektiv metod som ska användas.

     (  defgeneric  add  (  a  b  )) 
      
      (  defmethod  add  ((  ett  nummer  )  (  b  nummer  ))  (  +  a  b  )) 
      
           (  defmethod  add  ((  a  vektor  )  (  b  nummer  ))  (  map  'vector  (  lambda  (  n  )  (  +  n  b  ))  a  )) 
      
        (  defmethod  add  ((  a  vektor  )  (  b  vektor  ))  (  map  'vector  #'  +  a  b  )) 
     
      (  defmethod  add  ((  en  sträng  )  (  b  sträng  ))  (  sammanfoga  'sträng  a  b  )) 
                      
                
          
          (  lägg till  2  3  )  ; returnerar 5   (  lägg till  #(  1  2  3  4  )  7  )  ; returnerar #(8 9 10 11)   (  lägg till  #(  1  2  3  4  )  #(  4  3  2  1  ))  ; returnerar #(5 5 5 5)   (  lägg  till  "COMMON "  "LISP" )  ; returnerar "COMMON LISP"  

Generiska funktioner är också en förstklassig datatyp . Det finns många fler funktioner för allmänna funktioner och metoder än vad som beskrivs ovan.

Funktionens namnutrymme

Namnutrymmet för funktionsnamn är skilt från namnområdet för datavariabler. Detta är en nyckelskillnad mellan Common Lisp och Scheme . För Common Lisp inkluderar operatorer som definierar namn i funktionsnamnutrymmet defun , flet , labels , defmethod och defgeneric .

För att skicka en funktion med namn som ett argument till en annan funktion måste man använda funktionen specialoperator , vanligen förkortad som #' . Det första sorteringsexemplet ovan hänvisar till funktionen som namnges av symbolen > i funktionsnamnrymden, med koden #'> . Omvänt, för att anropa en funktion som skickats på ett sådant sätt, skulle man använda funcall- operatorn på argumentet.

Schemes utvärderingsmodell är enklare: det finns bara ett namnområde, och alla positioner i formuläret utvärderas (i valfri ordning) – inte bara argumenten. Kod skriven på en dialekt är därför ibland förvirrande för programmerare som är mer erfarna i den andra. Till exempel gillar många Common Lisp-programmerare att använda beskrivande variabelnamn som listor eller strängar som kan orsaka problem i Scheme, eftersom de lokalt skulle skugga funktionsnamn.

Huruvida ett separat namnutrymme för funktioner är en fördel är en källa till diskussion i Lisp-gemenskapen. Det brukar kallas Lisp-1 vs Lisp-2 debatten . Lisp-1 hänvisar till Schemes modell och Lisp-2 hänvisar till Common Lisps modell. Dessa namn myntades i en artikel från 1988 av Richard P. Gabriel och Kent Pitman , som utförligt jämför de två tillvägagångssätten.

Flera returvärden

Common Lisp stöder konceptet med flera värden , där alla uttryck alltid har ett enda primärt värde , men det kan också ha valfritt antal sekundära värden , som kan tas emot och inspekteras av intresserade uppringare. Detta koncept skiljer sig från att returnera ett listvärde, eftersom de sekundära värdena är helt valfria och skickas via en dedikerad sidokanal. Detta innebär att uppringare kan förbli helt omedvetna om de sekundära värdena som finns där om de inte har något behov av dem, och det gör det bekvämt att använda mekanismen för att kommunicera information som ibland är användbar, men inte alltid nödvändig. Till exempel,

  • Funktionen TRUNCATE avrundar det givna talet till ett heltal mot noll. Men det returnerar också en rest som ett sekundärt värde, vilket gör det mycket enkelt att avgöra vilket värde som trunkerades. Den stöder också en valfri divisorparameter, som kan användas för att utföra euklidisk division trivialt:
  
       
    
        
          

 (  let  ((  x  1266778  )  (  y  458  ))  (  multiple-value-bind  (  quotient  rest  )  (  trunkate  x  y  )  (  format  noll  "~A dividerat med ~A är ~A rest ~A"  x  y  quotient  rest  )) )  ;;;; => "1266778 dividerat med 458 är 2765 resten 408"  
  • GETHASH returnerar värdet av en nyckel i en associativ karta , eller standardvärdet annars, och en sekundär boolean som indikerar om värdet hittades. Således kan kod som inte bryr sig om huruvida värdet hittades eller tillhandahålls som standard helt enkelt använda det som det är, men när en sådan distinktion är viktig kan den inspektera den sekundära boolean och reagera på lämpligt sätt. Båda användningsfallen stöds av samma samtal och varken belastas eller begränsas i onödan av den andra. Att ha denna funktion på språknivå tar bort behovet av att kontrollera existensen av nyckeln eller jämföra den med null som skulle göras på andra språk.
  
     

  
      


  
    
       
      
        
        
 (  defun  get-answer  (  library  )  (  gethash  'answer  library  42  ))  (  defun  the-answer-1  (  library  )  (  format  noll  "Svaret är ~A"  (  get-answer  library  )))  ;;;; Returnerar "Svaret är 42" om SVAR inte finns i LIBRARY   (  defun  the-answer-2  (  library  )  (  multiple-value-bind  (  svara  säker-p  )  (  få-svar-  bibliotek  )  (  om  (  inte  säker-p  )  " Jag vet inte"  (  format  noll  "Svaret är ~A"  svar  ))))  ;;;; Returnerar "Jag vet inte" om SVAR inte finns i LIBRARY  

Flera värden stöds av en handfull standardformulär, varav de vanligaste är specialformen MULTIPLE-VALUE-BIND för att komma åt sekundära värden och VALUES för att returnera flera värden:

  
  
     


 (  defun  magic-eight-ball  ()  "Returnera en utsiktsförutsägelse, med sannolikheten som ett sekundärt värde"  (  värden  "Outlook good"  (  slumpmässigt  1,0  )))  ;;;; => "Utsikterna är bra"   ;;;; => 0,3187  

Andra typer

Andra datatyper i Common Lisp inkluderar:

  • Sökvägar representerar filer och kataloger i filsystemet . Funktionen Common Lisp-sökvägsnamn är mer allmän än de flesta operativsystems filnamnkonventioner, vilket gör Lisp-programs åtkomst till filer brett portabel över olika system.
  • In- och utströmmar representerar källor och sänkor för binära eller textdata, såsom terminalen eller öppna filer.
  • Common Lisp har en inbyggd pseudo-slumptalsgenerator (PRNG). Slumpmässiga tillståndsobjekt representerar återanvändbara källor för pseudoslumptal, vilket gör att användaren kan seed PRNG eller få den att spela upp en sekvens.
  • Villkor är en typ som används för att representera fel, undantag och andra "intressanta" händelser som ett program kan svara på.
  • Klasser är förstklassiga objekt och är själva instanser av klasser som kallas metaobjektklasser ( kortas metaklasser ).
  • Lästabeller är en typ av objekt som styr hur Common Lisps läsare analyserar texten i källkoden. Genom att styra vilken lästabell som används när kod läses in kan programmeraren ändra eller utöka språkets syntax.

Omfattning

Liksom program i många andra programmeringsspråk använder Common Lisp-program namn för att referera till variabler, funktioner och många andra typer av enheter. Namngivna referenser är föremål för omfattning.

Sambandet mellan ett namn och den enhet som namnet hänvisar till kallas en bindning.

Omfattning hänvisar till den uppsättning omständigheter under vilka ett namn fastställs ha en viss bindning.

Bestämmare av omfattning

De omständigheter som avgör omfattningen i Common Lisp inkluderar:

  • platsen för en referens i ett uttryck. Om det är positionen längst till vänster i en förening hänvisar det till en speciell operator eller en makro- eller funktionsbindning, annars till en variabelbindning eller något annat.
  • vilken typ av uttryck som referensen sker i. Till exempel (gå x) överföra kontroll till etiketten x , medan (skriv ut x) hänvisar till variabeln x . Båda omfång av x kan vara aktiva i samma region av programtext, eftersom tagbody-etiketter finns i ett separat namnområde från variabelnamn. En speciell form eller makroform har fullständig kontroll över betydelsen av alla symboler i sin syntax. Till exempel, i (defclass x (ab) ()) , en klassdefinition, är (ab) en lista över basklasser, så dessa namn slås upp i utrymmet för klassnamn, och x är inte en referens till en befintlig bindning, men namnet på en ny klass kommer från a och b . Dessa fakta kommer enbart från semantiken i defclass . Det enda generiska faktumet om detta uttryck är att defclass refererar till en makrobindning; allt annat är upp till defclass .
  • platsen för referensen i programtexten. Till exempel, om en referens till variabel x är innesluten i en bindande konstruktion, såsom en let som definierar en bindning för x , är referensen inom det omfång som skapas av den bindningen.
  • för en variabelreferens, oavsett om en variabelsymbol, lokalt eller globalt, har förklarats speciell eller inte. Detta avgör om referensen löses inom en lexikal miljö eller inom en dynamisk miljö.
  • den specifika instans av miljön där referensen löses. En miljö är en runtime-ordbok som mappar symboler till bindningar. Varje typ av referens använder sin egen typ av miljö. Referenser till lexikaliska variabler löses i en lexikal miljö, et cetera. Mer än en miljö kan associeras med samma referens. Till exempel, tack vare rekursion eller användningen av flera trådar, kan flera aktiveringar av samma funktion existera samtidigt. Dessa aktiveringar delar samma programtext, men var och en har sin egen lexikaliska miljöinstans.

För att förstå vad en symbol refererar till måste Common Lisp-programmeraren veta vilken typ av referens som uttrycks, vilken typ av räckvidd den använder om det är en variabel referens (dynamisk kontra lexikal räckvidd), och även körtidssituationen: i vilken miljö är referensen löst, var infördes bindningen i miljön, et cetera.

Typer av miljö

Global

Vissa miljöer i Lisp är globalt genomträngande. Till exempel, om en ny typ definieras, är den känd överallt därefter. Referenser till den typen slår upp det i denna globala miljö.

Dynamisk

En typ av miljö i Common Lisp är den dynamiska miljön. Bindningar som etablerats i den här miljön har en dynamisk omfattning, vilket innebär att en bindning etableras i början av exekveringen av någon konstruktion, till exempel ett låtblock, och försvinner när den konstruktionen slutfört exekvering: dess livslängd är bunden till den dynamiska aktiveringen och deaktiveringen av ett block. En dynamisk bindning är dock inte bara synlig inom det blocket; det är också synligt för alla funktioner som anropas från det blocket. Denna typ av synlighet är känd som obestämd räckvidd. Bindningar som uppvisar dynamisk omfattning (livstid kopplad till aktivering och deaktivering av ett block) och obestämd omfattning (synliga för alla funktioner som anropas från det blocket) sägs ha dynamisk omfattning.

Common Lisp har stöd för dynamiskt omfångade variabler, som även kallas specialvariabler. Vissa andra typer av bindningar är också nödvändigtvis dynamiskt omfångade, såsom omstarter och catch-taggar. Funktionsbindningar kan inte omfångas dynamiskt med hjälp av flet (som endast tillhandahåller funktionsbindningar med lexikalt omfattning), men funktionsobjekt (ett objekt på första nivån i Common Lisp) kan tilldelas dynamiskt omfångade variabler, bindas med let in dynamiskt omfång, sedan anropas med funcall eller ANSÖK .

Dynamiskt omfång är extremt användbart eftersom det lägger till referenstydlighet och disciplin till globala variabler . Globala variabler är ogillade inom datavetenskap som potentiella felkällor, eftersom de kan ge upphov till ad-hoc, hemliga kommunikationskanaler mellan moduler som leder till oönskade, överraskande interaktioner.

I Common Lisp beter sig en specialvariabel som bara har en toppnivåbindning precis som en global variabel i andra programmeringsspråk. Ett nytt värde kan lagras i det, och det värdet ersätter helt enkelt det som finns i bindningen på toppnivå. Slarvig ersättning av värdet på en global variabel är kärnan i buggar som orsakas av användningen av globala variabler. Ett annat sätt att arbeta med en speciell variabel är dock att ge den en ny, lokal bindning inom ett uttryck. Detta kallas ibland för att "binda om" variabeln. Genom att binda en variabel med dynamisk omfattning skapas tillfälligt en ny minnesplats för den variabeln och associerar namnet med den platsen. Medan den bindningen är i kraft hänvisar alla referenser till den variabeln till den nya bindningen; den tidigare bindningen är dold. När exekveringen av bindningsuttrycket avslutas är den tillfälliga minnesplatsen borta och den gamla bindningen avslöjas, med det ursprungliga värdet intakt. Naturligtvis kan flera dynamiska bindningar för samma variabel kapslas.

I Common Lisp-implementeringar som stöder multithreading är dynamiska omfång specifika för varje exekveringstråd. Därför fungerar speciella variabler som en abstraktion för lokal lagring av trådar. Om en tråd binder om en speciell variabel har denna återbindning ingen effekt på den variabeln i andra trådar. Värdet som lagras i en bindning kan endast hämtas av tråden som skapade den bindningen. Om varje tråd binder någon speciell variabel *x* , beter sig *x* som trådlokal lagring. Bland trådar som inte binder om *x* , beter sig den som en vanlig global: alla dessa trådar hänvisar till samma toppnivåbindning av *x* .

Dynamiska variabler kan användas för att utöka exekveringskontexten med ytterligare kontextinformation som implicit skickas från funktion till funktion utan att behöva visas som en extra funktionsparameter. Detta är särskilt användbart när kontrollöverföringen måste passera genom lager av orelaterade kod, som helt enkelt inte kan utökas med extra parametrar för att skicka ytterligare data. En situation som denna kräver vanligtvis en global variabel. Den globala variabeln måste sparas och återställas, så att schemat inte bryts under rekursion: dynamisk variabel ombindning tar hand om detta. Och den variabeln måste göras trådlokal (annars måste en stor mutex användas) så att schemat inte bryts under trådar: dynamiska scope-implementationer kan också ta hand om detta.

I Common Lisp-biblioteket finns det många vanliga specialvariabler. Till exempel lagras alla standard I/O-strömmar i toppnivåbindningarna för välkända specialvariabler. Standardutgångsströmmen lagras i *standardutgång*.

Antag att en funktion foo skriver till standardutdata:

    
       (  defun  foo  ()  (  format  t  "Hej världen")  ) 

För att fånga dess utdata i en teckensträng kan *standardoutput* bindas till en strängström och anropas:

   
     (  med-utgång-till-sträng  (  *standard-utgång*  )  (  foo  )) 
-> "Hej världen" ; samlad utdata returneras som en sträng

Lexikalisk

Common Lisp stöder lexikaliska miljöer. Formellt har bindningarna i en lexikal miljö lexikal räckvidd och kan ha antingen en obestämd utsträckning eller dynamisk utsträckning, beroende på typen av namnutrymme. Lexikal räckvidd innebär att synligheten fysiskt begränsas till det block där bindningen är etablerad. Referenser som inte är textuellt (dvs. lexikalt) inbäddade i det blocket ser helt enkelt inte den bindningen.

Taggarna i en TAGBODY har lexikal räckvidd. Uttrycket (GO X) är felaktigt om det inte är inbäddat i en TAGBODY som innehåller en etikett X. Emellertid försvinner etikettbindningarna när TAGBODY avslutar sin exekvering, eftersom de har dynamisk utsträckning. Om det kodblocket matas in igen genom anropet av en lexikal stängning är det ogiltigt för den stängningens brödtext att försöka överföra kontrollen till en tagg via GO:

    

  
         
      
   
     
   
    (  defvar  *stashed*  )  ;; kommer att hålla en funktion   (  tagbody  (  setf  *stashed*  (  lambda  ()  (  go  some-label  )))  (  go  end-label  )  ;; hoppa över (skriv ut "Hej")  någon etikett  (  skriv ut  "Hej"  )  slut- etikett  )  ->  NIL 

När TAGBODY exekveras, utvärderar den först setf-formuläret som lagrar en funktion i den speciella variabeln *stashed*. Sedan överför (gå till slutetiketten) kontrollen till slutetiketten och hoppar över koden (skriv ut "Hej"). Eftersom ändetiketten är i slutet av etikettkroppen, avslutas etikettkroppen, vilket ger NIL. Antag att den tidigare ihågkomna funktionen nu kallas:

     (  funcall  *stashed*  )  ;; Fel!  

Denna situation är felaktig. En implementerings svar är ett feltillstånd som innehåller meddelandet "GO: tagbody för taggen SOME-LABEL har redan lämnats". Funktionen försökte utvärdera (go some-label), som är lexikalt inbäddad i taggkroppen, och löser sig till etiketten. Emellertid körs inte taggkroppen (dess omfattning har upphört), så kontrollöverföringen kan inte ske.

Lokala funktionsbindningar i Lisp har lexical scope och variabla bindningar har också lexical scope som standard. Till skillnad från GO-etiketter har båda dessa obestämd omfattning. När en lexikal funktion eller variabelbindning etableras, fortsätter den bindningen att existera så länge som referenser till den är möjliga, även efter att konstruktionen som fastställde att bindningen har upphört. Referenser till lexikaliska variabler och funktioner efter att deras etablerande konstruktion upphört är möjliga tack vare lexikaliska stängningar .

Lexikal bindning är standardbindningsläget för Common Lisp-variabler. För en enskild symbol kan den växlas till dynamiskt omfång, antingen genom en lokal deklaration, genom en global deklaration. Det senare kan ske implicit genom användning av en konstruktion som DEFVAR eller DEFPARAMETER. Det är en viktig konvention inom Common Lisp-programmering att speciella (dvs. dynamiskt omfångade) variabler har namn som börjar och slutar med en asterisk sigil * i det som kallas " hörselkåpskonventionen ". Om den följs skapar denna konvention ett separat namnutrymme för speciella variabler, så att variabler som är avsedda att vara lexikaliska inte av misstag görs speciella.

Lexikalisk omfattning är användbar av flera skäl.

För det första kan referenser till variabler och funktioner kompileras till effektiv maskinkod, eftersom runtime-miljöstrukturen är relativt enkel. I många fall kan den optimeras för att stapla lagring, så att öppna och stänga lexikaliska scopes har minimal omkostnad. Även i de fall där fullständiga stängningar måste genereras är tillgången till stängningens miljö fortfarande effektiv; typiskt sett blir varje variabel en förskjutning till en vektor av bindningar, och så blir en variabelreferens en enkel laddnings- eller lagringsinstruktion med en bas-plus-offset- adresseringsmod .

För det andra ger lexikal räckvidd (kombinerat med obestämd omfattning) upphov till den lexikala stängningen , vilket i sin tur skapar ett helt paradigm av programmering centrerat kring användningen av funktioner som är förstklassiga objekt, vilket är roten till funktionell programmering.

För det tredje, kanske viktigast av allt, även om lexikaliska stängningar inte utnyttjas, isolerar användningen av lexikalisk omfattning programmoduler från oönskade interaktioner. På grund av deras begränsade synlighet är lexikaliska variabler privata. Om en modul A binder en lexikal variabel X, och anropar en annan modul B, kommer referenser till X i B inte av misstag att lösas till X-gränsen i A. B har helt enkelt ingen tillgång till X. För situationer där disciplinerade interaktioner genom en variabel är önskvärt, ger Common Lisp speciella variabler. Specialvariabler tillåter en modul A att sätta upp en bindning för en variabel X som är synlig för en annan modul B, anropad från A. Att kunna göra detta är en fördel, och att kunna förhindra att det händer är också en fördel; Följaktligen stöder Common Lisp både lexikal och dynamisk räckvidd .

Makron

Ett makro i Lisp liknar ytligt en funktion i användning. Men snarare än att representera ett uttryck som utvärderas, representerar det en transformation av programmets källkod. Makrot hämtar källan den omger som argument, binder dem till sina parametrar och beräknar en ny källform. Denna nya form kan också använda ett makro. Makroexpansionen upprepas tills det nya källformuläret inte använder ett makro. Den slutliga beräknade formen är källkoden som körs vid körning.

Typiska användningsområden för makron i Lisp:

  • nya kontrollstrukturer (exempel: slingkonstruktioner, grenkonstruktioner)
  • scoping och bindande konstruktioner
  • förenklad syntax för komplex och upprepad källkod
  • toppnivådefinierande former med biverkningar vid kompilering
  • datadriven programmering
  • inbäddade domänspecifika språk (exempel: SQL , HTML , Prolog )
  • implicita slutförandeformulär

Olika vanliga Common Lisp-funktioner måste också implementeras som makron, till exempel:

  • standard setf -abstraktion, för att tillåta anpassade kompileringstidsexpansioner av uppdrags-/åtkomstoperatörer
  • with-accessors , with-slots , with-open-file och andra liknande MED makron
  • Beroende på implementering, om eller villkor är ett makro byggt på den andra, den speciella operatören; när och om inte består av makron
  • Det kraftfulla slingdomänspecifika språket

Makron definieras av defmacro -makrot. Den speciella operatorn makrolet tillåter definition av lokala (lexikalt omfångade) makron. Det är också möjligt att definiera makron för symboler med hjälp av definiera-symbol-makro och symbol-makrolett .

Paul Grahams bok On Lisp beskriver användningen av makron i Common Lisp i detalj. Doug Hoytes bok Let Over Lambda utökar diskussionen om makron och hävdar "Makron är den enskilt största fördelen som lisp har som programmeringsspråk och den enskilt största fördelen med något programmeringsspråk." Hoyte ger flera exempel på iterativ utveckling av makron.

Exempel med användning av ett makro för att definiera en ny kontrollstruktur

Makron tillåter Lisp-programmerare att skapa nya syntaktiska former i språket. En typisk användning är att skapa nya kontrollstrukturer. Exempelmakrot tillhandahåller en tills looping-konstruktion. Syntaxen är:

(tills provformuläret*)

Makrodefinitionen för tills :

    
     
            
     
                 
               
               
               (  defmacro  tills  (  test  &body  body  )  (  låt  ((  start-tag  (  gensym  "START"  ))  (  end-tag  (  gensym  "END"  )))  `  (  tagbody  ,  start-tag  (  när  ,  test  (  go  ,  end- tag  ))  (  progn  ,@  body  )  (  go  ,  start-tag  )  ,  end-tag  ))) 

tagbody är en primitiv Common Lisp specialoperator som ger möjlighet att namnge taggar och använda go -formuläret för att hoppa till dessa taggar. Backquote ` tillhandahåller en notation som tillhandahåller kodmallar, där värdet av formulär som föregås av ett kommatecken fylls i. Formulär som föregås av komma och at-tecken skarvas in. Tagbody -formuläret testar slutvillkoret. Om villkoret är sant, hoppar det till sluttaggen. Annars exekveras den angivna kroppskoden och sedan hoppar den till starttaggen.

Ett exempel på att använda ovanstående till makro:

    0
    (  tills  (  =  (  slumpmässigt  10  )  )  (  skrivrad  "Hej" )  ) 

Koden kan utökas med funktionen macroexpand-1 . Expansionen för exemplet ovan ser ut så här:


 
    
    
   
  
  (  TAGBODY  #:START1136  (  NÄR  (  ZEROP  (  RANDOM  10  ))  (  GO  #:END1137  ))  (  PROGN  (  WRITE-LINE  "hej"  ))  (  GO  #:START1136  )  #:END1137  ) 

är värdet på variabeltestet ( = (slumpmässigt 10) 0) och värdet på variabelkroppen är ( (skrivrad "Hej")) . Kroppen är en lista över former.

Symboler förstoras vanligtvis automatiskt. Expansionen använder TAGBODY med två etiketter. Symbolerna för dessa etiketter beräknas av GENSYM och är inte inbyggda i något paket. Två go- formulär använder dessa taggar för att hoppa till. Eftersom tagbody är en primitiv operator i Common Lisp (och inte ett makro), kommer den inte att utökas till något annat. Det utökade formuläret använder när- makrot, som också kommer att utökas. Att helt expandera en källform kallas kodvandring .

I den helt expanderade ( gående ) formen ersätts när- formen med den primitiva om :


 
    
       
   
   
  
  (  TAGBODY  #:START1136  (  IF  (  ZEROP  (  RANDOM  10  ))  (  PROGN  (  GO  #:END1137  ))  NIL  )  (  PROGN  (  WRITE-LINE  "hej"  ))  (  GO  #:START1136  ))  #:END1137  ) 

Alla makron måste expanderas innan källkoden som innehåller dem kan utvärderas eller kompileras normalt. Makron kan betraktas som funktioner som accepterar och returnerar S-uttryck – liknande abstrakta syntaxträd , men inte begränsat till dessa. Dessa funktioner anropas innan utvärderaren eller kompilatorn för att producera den slutliga källkoden. Makron skrivs i normal Common Lisp och kan använda vilken Common Lisp (eller tredjeparts) som helst som är tillgänglig.

Variabel fångst och skuggning

Vanliga Lisp-makro är kapabla till vad som vanligtvis kallas variabel capture , där symboler i makroexpansionskroppen sammanfaller med dem i anropssammanhanget, vilket gör att programmeraren kan skapa makron där olika symboler har speciell betydelse. Termen variabelfångst är något missvisande, eftersom alla namnområden är sårbara för oönskad infångning, inklusive operatören och funktionen namnutrymme, tagkroppsetiketten namnutrymme, catch-tagg, villkorshanterare och omstart av namnutrymmen.

Variabel insamling kan introducera programvarufel. Detta sker på ett av följande två sätt:

  • På det första sättet kan en makroexpansion oavsiktligt göra en symbolisk referens som makroskrivaren antog kommer att lösa i en global namnrymd, men koden där makrot expanderas råkar ge en lokal, skuggande definition som stjäl den referensen. Låt detta kallas typ 1-fångst.
  • Det andra sättet, typ 2-infångning, är precis tvärtom: några av makrots argument är bitar av kod som tillhandahålls av makroanroparen, och dessa bitar av kod är skrivna så att de gör referenser till omgivande bindningar. Makrot infogar dock dessa kodbitar i en expansion som definierar sina egna bindningar som av misstag fångar några av dessa referenser.

Schema-dialekten av Lisp tillhandahåller ett makro-skrivsystem som ger referenstransparensen som eliminerar båda typerna av fångstproblem. Denna typ av makrosystem kallas ibland för "hygieniska", i synnerhet av dess förespråkare (som betraktar makrosystem som inte automatiskt löser detta problem som ohygieniska). [ citat behövs ]

I Common Lisp säkerställs makrohygienen på ett av två olika sätt.

Ett tillvägagångssätt är att använda gensyms: garanterat unika symboler som kan användas i en makroexpansion utan hot om att fångas. Användningen av gensyms i en makrodefinition är en manuell syssla, men makron kan skrivas som förenklar instansieringen och användningen av gensyms. Gensyms löser lätt typ 2-infångning, men de är inte tillämpliga på typ 1-infångning på samma sätt, eftersom makroexpansionen inte kan byta namn på de störande symbolerna i den omgivande koden som fångar dess referenser. Gensyms skulle kunna användas för att tillhandahålla stabila alias för de globala symboler som makroexpansionen behöver. Makroexpansionen skulle använda dessa hemliga alias snarare än de välkända namnen, så omdefiniering av de välkända namnen skulle inte ha någon negativ effekt på makrot.

Ett annat tillvägagångssätt är att använda paket. Ett makro definierat i sitt eget paket kan helt enkelt använda interna symboler i det paketet i sin expansion. Användningen av paket handlar om typ 1 och typ 2 capture.

Paketen löser dock inte typ 1-infångningen av referenser till vanliga Common Lisp-funktioner och -operatorer. Anledningen är att användningen av paket för att lösa fångstproblem kretsar kring användningen av privata symboler (symboler i ett paket, som inte importeras till eller på annat sätt synliggörs i andra paket). Medan Common Lisp-bibliotekssymbolerna är externa och ofta importerade till eller görs synliga i användardefinierade paket.

Följande är ett exempel på oönskad infångning i operatörens namnutrymme, som inträffar i expansionen av ett makro:

 
     
      

 
       
       0   ;; expansion av UNTIL gör liberal användning av DO   (  defmacro  tills  (  expression  &body  body  )  `  (  do  ( )  (  ,  expression  )  ,@  body  ))  ;; macrolet etablerar lexikal operatorbindning för DO   (  macrolet  ((  do  (  ...  )  ...  något  annat  ...  ))  (  tills  (  =  (  slumpmässigt  10  )  )  (  skrivrad  "Hej"  ))) 

Makrot tills kommer att expandera till en form som anrop gör som är avsedd att referera till standardmakrot Common Lisp . Men i detta sammanhang do ha en helt annan innebörd, så tills kanske inte fungerar korrekt.

Common Lisp löser problemet med skuggning av standardoperatörer och funktioner genom att förbjuda omdefiniering av dem. Eftersom den omdefinierar standardoperatören do , är det föregående faktiskt ett fragment av icke-överensstämmande Common Lisp, som tillåter implementeringar att diagnostisera och avvisa den.

Konditionssystem

Villkorssystemet ansvarar för undantagshantering i Common Lisp . Det ger villkor , hanterare och omstarter . Villkor är objekt som beskriver en exceptionell situation (till exempel ett fel). Om ett tillstånd signaleras, söker Common Lisp-systemet efter en hanterare för denna tillståndstyp och anropar hanteraren. Hanteraren av information som tillståndstyp och all relevant information som tillhandahålls som en del av tillståndsobjektet, och anropa lämplig omstartsfunktion.

Dessa omstarter, om de inte hanteras av kod, kan presenteras för användare (som en del av ett användargränssnitt, det för en debugger till exempel), så att användaren kan välja och anropa en av de tillgängliga omstarterna. Eftersom villkorshanteraren anropas i samband med felet (utan att avveckla stacken), är fullständig felåterställning möjlig i många fall, där andra undantagshanteringssystem redan skulle ha avslutat den aktuella rutinen. Själva debuggern kan också anpassas eller ersättas med den dynamiska variabeln *debugger-hook* . Kod som hittas i avvecklingsskyddsformulär som slutbehandlare kommer också att exekveras vid behov trots undantaget.

I följande exempel (med Symbolics Genera ) försöker användaren öppna en fil i ett Lisp-funktionstest som anropas från Read-Eval-Print-LOOP ( REPL ), när filen inte finns. Lisp-systemet presenterar fyra omstarter. Användaren väljer Försök ÖPPNA igen med en omstart av ett annat sökväg och anger ett annat sökvägsnamn (lispm-init.lisp istället för lispm-int.lisp). Användarkoden innehåller ingen felhanteringskod. Hela felhanterings- och omstartskoden tillhandahålls av Lisp-systemet, som kan hantera och reparera felet utan att avsluta användarkoden.

Kommando: (test ">zippy>lispm-int.lisp") Fel: Filen hittades inte. För lispm:>zippy>lispm-int.lisp.newest LMFS:OPEN-LOCAL-LMFS-1 Arg 0: #P"lispm:>zippy>lispm-int.lisp.newest" sA, : Försök igen OPEN av lispm:>zippy>lispm-int.lisp.newest sB: Försök igen OPEN med ett annat sökvägsnamn sC, : Återgå till Lisp toppnivå i en TELNET-server sD: Starta om processen TELNET-terminal -> Försök igen ÖPPNA med ett annat sökvägsnamn Använd vilket sökvägsnamn istället [default lispm:>zippy>lispm-int.lisp.newest]: lispm:>zippy>lispm -init.lisp.newest ...programmet fortsätter

Common Lisp Object System (CLOS)

Common Lisp innehåller en verktygslåda för objektorienterad programmering , Common Lisp Object System eller CLOS . Peter Norvig förklarar hur många Design Patterns som är enklare att implementera i ett dynamiskt språk med funktionerna i CLOS (Multiple Inheritance, Mixins, Multimethods, Metaclasses, Method-kombinationer, etc.). Flera tillägg till Common Lisp för objektorienterad programmering har föreslagits att inkluderas i ANSI Common Lisp-standarden, men så småningom antogs CLOS som standardobjektsystemet för Common Lisp. CLOS är ett dynamiskt objektsystem med multipel sändning och multipel arv , och skiljer sig radikalt från OOP-faciliteterna som finns i statiska språk som C++ eller Java . Som ett dynamiskt objektsystem tillåter CLOS ändringar vid körning av generiska funktioner och klasser. Metoder kan läggas till och tas bort, klasser kan läggas till och omdefinieras, objekt kan uppdateras för klassändringar och klassen av objekt kan ändras.

CLOS har integrerats i ANSI Common Lisp. Generiska funktioner kan användas som vanliga funktioner och är en förstklassig datatyp. Varje CLOS-klass är integrerad i systemet av typen Common Lisp. Många vanliga Lisp-typer har en motsvarande klass. Det finns mer potentiell användning av CLOS för Common Lisp. Specifikationen säger inte om villkor är implementerade med CLOS. Sökvägar och strömmar skulle kunna implementeras med CLOS. Dessa ytterligare användningsmöjligheter för CLOS för ANSI Common Lisp är inte en del av standarden. Faktiska Common Lisp-implementeringar använder CLOS för sökvägar, strömmar, input–output, villkor, implementeringen av själva CLOS och mer.

Kompilator och tolk

En Lisp-tolk exekverar direkt Lisp-källkoden som tillhandahålls som Lisp-objekt (listor, symboler, siffror, ...) som läses från s-uttryck. En Lisp-kompilator genererar bytekod eller maskinkod från Lisp-källkoden. Common Lisp tillåter både individuella Lisp-funktioner att kompileras i minnet och kompilering av hela filer till externt lagrad kompilerad kod ( fasl -filer).

Flera implementeringar av tidigare Lisp-dialekter gav både en tolk och en kompilator. Tyvärr var semantiken ofta annorlunda. Dessa tidigare Lisps implementerade lexikal scoping i kompilatorn och dynamisk scoping i tolken. Common Lisp kräver att både tolken och kompilatorn använder lexical scoping som standard. Common Lisp-standarden beskriver både tolkens och en kompilators semantik. Kompilatorn kan anropas med funktionen kompilering för enskilda funktioner och med funktionen kompileringsfil för filer. Common Lisp tillåter typdeklarationer och ger sätt att påverka kompilatorns kodgenereringspolicy. För de senare kan olika optimeringskvaliteter ges värden mellan 0 (inte viktigt) och 3 (viktigast): hastighet , utrymme , säkerhet , felsökning och kompileringshastighet .

Det finns också en funktion för att utvärdera Lisp-kod: eval . eval tar kod som förberedda s-uttryck och inte, som på vissa andra språk, som textsträngar. På så sätt kan koden konstrueras med de vanliga Lisp-funktionerna för att konstruera listor och symboler och sedan kan denna kod utvärderas med funktionen eval . Flera Common Lisp-implementationer (som Clozure CL och SBCL) implementerar eval med sin kompilator. På detta sätt kompileras koden, även om den utvärderas med funktionen eval .

Filkompilatorn anropas med funktionen compile-file . Den genererade filen med kompilerad kod kallas en fasl (från fast load ) fil. Dessa fasl- filer och även källkodsfiler kan laddas med funktionen laddas in i ett körande Common Lisp-system. Beroende på implementeringen genererar filkompilatorn byte-kod (till exempel för Java Virtual Machine ), C-språkkod (som sedan kompileras med en C-kompilator) eller, direkt, inbyggd kod.

Vanliga Lisp-implementeringar kan användas interaktivt, även om koden blir helt kompilerad. Idén om ett tolkat språk gäller alltså inte för interaktiv Common Lisp.

Språket gör en åtskillnad mellan lästid, kompileringstid, laddningstid och körtid, och tillåter användarkod att också göra denna åtskillnad för att utföra den önskade typen av bearbetning vid det önskade steget.

Vissa speciella operatörer tillhandahålls speciellt för att passa interaktiv utveckling; till exempel defvar endast att tilldela ett värde till den angivna variabeln om den inte redan var bunden, medan defparameter alltid kommer att utföra tilldelningen. Denna distinktion är användbar när du interaktivt utvärderar, kompilerar och laddar kod i en levande bild.

Vissa funktioner tillhandahålls också för att hjälpa skrivande kompilatorer och tolkar. Symboler består av objekt på första nivån och är direkt manipulerbara med användarkod. Specialoperatorn progv tillåter att skapa lexikaliska bindningar programmatiskt, medan paket också är manipulerbara. Lisp-kompilatorn är tillgänglig under körning för att kompilera filer eller enskilda funktioner. Dessa gör det enkelt att använda Lisp som en mellankompilator eller tolk för ett annat språk.

Kodexempel

Födelsedag paradox

Följande program beräknar det minsta antalet personer i ett rum för vilka sannolikheten för unika födelsedagar är mindre än 50% (födelsedagsparadoxen, där sannolikheten för 1 person uppenbarligen är 100%, för 2 är den 364/365, etc. ). Svaret är 23.

Enligt konvention omges konstanter i Common Lisp med +-tecken.

  

   
        
                               
                            
       
         
            (  defconstant  +year-size+  365  )  (  defun  födelsedag-paradox  (  sannolikhet  antal personer  )  (  låt  ((  ny-sannolikhet  (  *  (  /  (  -  +årsstorlek+  antal personer  ) )  +årsstorlek+  )  sannolikhet  )))  (  if  (  <  ny-sannolikhet  0,5  )  (  1+  antal personer  )  (  födelsedag-paradox  ny-sannolikhet  (  1+  antal personer  ))))) 

Anropa exempelfunktionen med REPL (Read Eval Print Loop):

CL-USER > (födelsedagsparadox 1.0 1) 23

Sortera en lista med personobjekt

Vi definierar en klassperson och en metod för att visa namn och ålder på en person. Därefter definierar vi en grupp av personer som en lista över personobjekt . Sedan itererar vi över den sorterade listan.

  
      
         
   

    
  
     
        

 
          
             
               
  

    
                      
                       
    
   (  defclass  person  ()  ((  namn  :initarg  :name  :accessor  person-name  )  (  age  :initarg  :age  :accessor  person-age  ))  (  :dokumentation  "Klassen PERSON med platser NAMN och ÅLDER." )  )  (  defmetod  display  ((  objekt  person  )  stream  )  "Visar ett PERSON-objekt till en utgångsström."  ( with-slots ( namn ålder ) objekt ( formatström  "  ~  a  (  ~  a  )  "  namn  ålder  )  )  )  (  defparameter  *grupp*  (  lista  (  make-instance  'person  :name  "Bob"  : ålder  33  )  (  make-instance  'person  :name  "Chris"  : ålder  16  )  (  make-instans  'person  :name  "Ash"  :ålder  23  ))  "En lista med PERSON-objekt."  )  (  dolist  (  person  (  sortera  (  kopieringslista  *grupp*  )  #'  >  :nyckel  #'  person-ålder  ))  (  visa  person  *standardutgång*  )  (  terpri  )) 

Den skriver ut de tre namnen med fallande ålder.

Bob (33) Ash (23) Chris (16)

Exponentiera genom att kvadrera

Användning av LOOP-makrot visas:

   
      
          
                
             
                    
           (  defun  power  (  x  n  )  (  loop  med  resultat  =  1  medan  (  plusp  n  )  när  (  udda  n  )  gör  (  setf  resultat  (  *  resultat  x  ))  gör  (  setf  x  (  *  x  x  )  n  (  trunkate  n  2  ))  slutligen  (  returnera  resultat  ))) 

Exempel på användning:

    
 CL-USER  >  (  effekt  2  200  )  1606938044258990275541962092341162602522202993782792835301376 

Jämför med den inbyggda exponentieringen:

        
 CL-USER  >  (  =  (  expt  2  200  )  (  effekt  2  200  ))  T 

Hitta listan över tillgängliga skal

WITH-OPEN-FILE är ett makro som öppnar en fil och ger en ström. När formuläret kommer tillbaka stängs filen automatiskt. FUNCALL anropar ett funktionsobjekt. LOOP samlar alla linjer som matchar predikatet.

   
  

    
           
           
             
            (  defun list-matching   -  lines  (  filpredikat  )  "Returnerar en lista med rader i filen, för vilka predikatet som tillämpas på  raden returnerar T." (  med  -öppen-fil  (  stream  -fil  )  (  loop  för  rad  =  (  read- linjeström  noll  noll  )  medan  linje  när  (  funcall  predikat  linje  )  samla  den )   )  ) 

Funktionen AVAILABLE-SHELLS anropar ovanstående funktion LIST-MATCHING-LINES med ett sökvägsnamn och en anonym funktion som predikat. Predikatet returnerar sökvägen för ett skal eller NIL (om strängen inte är filnamnet på ett skal).

    
  
   
    
        
             0 
          
               (  defun  available-shells  (  &valfritt  (  fil  #p"/etc/shells"  ))  (  list-matching-lines  file  (  lambda  (  line  )  (  och  (  plusp  (  length  line  ))  (  char=  (  char  line  )  #\/  )  (  sökväg  (  sträng-höger-trim  '  (  #\mellanslag  #\tab  )  rad )   ))))) 

Exempelresultat (på Mac OS X 10.6):

  
      CL-USER  >  (  tillgängliga-skal  )  (  #P"/bin/bash"  #P"/bin/csh"  #P"/bin/ksh"  #P"/bin/sh"  #P"/bin/tcsh"  #P"/bin/zsh"  ) 

Jämförelse med andra Lisps

Common Lisp jämförs oftast med, och kontrasteras till, Scheme — om än bara för att de är de två mest populära Lisp-dialekterna. Scheme är före CL och kommer inte bara från samma Lisp-tradition utan från några av samma ingenjörer – Guy Steele , som Gerald Jay Sussman designade Scheme med, var ordförande för standardkommittén för Common Lisp.

Common Lisp är ett allmänt programmeringsspråk, till skillnad från Lisp-varianter som Emacs Lisp och AutoLISP som är tilläggsspråk inbäddade i särskilda produkter (GNU Emacs respektive AutoCAD). Till skillnad från många tidigare Lisps, använder Common Lisp (som Scheme ) lexikal variabel omfattning som standard för både tolkad och kompilerad kod.

De flesta av Lisp-systemen vars design bidrog till Common Lisp - som ZetaLisp och Franz Lisp - använde dynamiskt omfångade variabler i sina tolkar och lexikalt omfångade variabler i sina kompilatorer. Scheme introducerade enbart användningen av lexikalt omfångade variabler till Lisp; en inspiration från ALGOL 68 . CL stöder även variabler med dynamisk omfattning, men de måste uttryckligen deklareras som "speciella". Det finns inga skillnader i omfattning mellan ANSI CL-tolkare och kompilatorer.

Common Lisp benämns ibland en Lisp-2 och Schema en Lisp-1 , hänvisar till CL:s användning av separata namnutrymmen för funktioner och variabler. (Faktum är att CL har många namnutrymmen, till exempel de för go-taggar, blocknamn och loop -nyckelord). Det finns en långvarig kontrovers mellan CL och Scheme-förespråkare angående kompromisserna som är involverade i flera namnområden. I Schema är det (i stort sett) nödvändigt att undvika att ge variabler namn som krockar med funktioner; Schemafunktioner har ofta argument som heter lis , lst eller lyst för att inte komma i konflikt med systemfunktionslistan . Men i CL är det nödvändigt att uttryckligen hänvisa till funktionen namnutrymme när man skickar en funktion som ett argument – ​​vilket också är en vanlig förekomst, som i sorteringsexemplet ovan .

CL skiljer sig också från Scheme i sin hantering av booleska värden. Schema använder specialvärdena #t och #f för att representera sanning och falskhet. CL följer den äldre Lisp-konventionen att använda symbolerna T och NIL, där NIL också står för den tomma listan. I CL behandlas alla icke-NIL-värden som sanna av villkor, till exempel om , medan i Schema behandlas alla icke-#f-värden som sanna. Dessa konventioner tillåter vissa operatorer på båda språken att fungera både som predikat (besvarar en booleskt värderad fråga) och som returnerar ett användbart värde för vidare beräkning, men i Schema utvärderas värdet '() som är ekvivalent med NIL i Common Lisp till sant i ett booleskt uttryck.

Slutligen kräver Scheme-standarddokumenten tail-call-optimering , vilket CL-standarden inte gör. De flesta CL-implementeringar erbjuder tail-call-optimering, men ofta bara när programmeraren använder ett optimeringsdirektiv. Icke desto mindre gynnar inte vanlig CL-kodningsstil den allestädes närvarande användningen av rekursion som Scheme-stilen föredrar – vad en Scheme-programmerare skulle uttrycka med tail-rekursion, skulle en CL-användare vanligtvis uttrycka med ett iterativt uttryck i do , dolist , loop , eller ( nyligen ) med iterate -paketet.

Genomföranden

Se Kategori Common Lisp-implementeringarna .

Common Lisp definieras av en specifikation (som Ada och C ) snarare än av en implementering (som Perl ). Det finns många implementeringar, och standarddetaljområdena där de kan skilja sig åt.

Dessutom tenderar implementeringar att komma med tillägg, som ger funktionalitet som inte täcks av standarden:

  • Interaktiv toppnivå (REPL)
  • Skräp samling
  • Debugger, Stepper och Inspector
  • Svaga datastrukturer (hashtabeller)
  • Utökningsbara sekvenser
  • Utdragbar LOOP
  • Tillgång till miljö
  • CLOS Meta-object Protocol
  • CLOS-baserade utdragbara strömmar
  • CLOS-baserat tillståndssystem
  • Nätverksströmmar
  • Ihållande STÄNGNING
  • Unicode-stöd
  • Gränssnitt för främmande språk (ofta till C)
  • Operativsystems gränssnitt
  • Java-gränssnitt
  • Trådar och multibearbetning
  • Applikationsleverans (applikationer, dynamiska bibliotek)
  • Spara bilder

med gratis och öppen källkod har skapats för att stödja tillägg till Common Lisp på ett bärbart sätt, och de finns framför allt i arkiven för Common-Lisp.net och CLOCC (Common Lisp Open Code Collection).

Vanliga Lisp-implementeringar kan använda valfri blandning av inbyggd kodkompilering, bytekodkompilering eller tolkning. Common Lisp har utformats för att stödja inkrementella kompilatorer , filkompilatorer och blockkompilatorer. Standarddeklarationer för att optimera kompilering (såsom funktionsinlining eller typspecialisering) föreslås i språkspecifikationen. De vanligaste Lisp-implementeringarna kompilerar källkod till inbyggd maskinkod . Vissa implementeringar kan skapa (optimerade) fristående applikationer. Andra kompilerar till tolkad bytecode , som är mindre effektiv än inbyggd kod, men underlättar portabilitet av binär kod. Vissa kompilatorer kompilerar Common Lisp-kod till C-kod. Missuppfattningen att Lisp är ett rent tolkat språk beror mest troligt på att Lisp-miljöer tillhandahåller en interaktiv prompt och att koden kompileras en efter en, på ett stegvis sätt. Med Common Lisp används inkrementell kompilering i stor utsträckning.

Vissa Unix - baserade implementeringar ( CLISP , SBCL ) kan användas som ett skriptspråk ; det vill säga anropas av systemet transparent på det sätt som en Perl- eller Unix- skaltolk är.

Lista över implementeringar

Kommersiella implementeringar

Allegro Common Lisp
för Microsoft Windows, FreeBSD, Linux, Apple macOS och olika UNIX-varianter. Allegro CL tillhandahåller en integrerad utvecklingsmiljö (IDE) (för Windows och Linux) och omfattande möjligheter för applikationsleverans.
Liquid Common Lisp
kallades tidigare Lucid Common Lisp. Endast underhåll, inga nya releaser.
LispWorks
för Microsoft Windows, FreeBSD, Linux, Apple macOS, iOS, Android och olika UNIX-varianter. LispWorks tillhandahåller en integrerad utvecklingsmiljö (IDE) (tillgänglig för de flesta plattformar, men inte för iOS och Android) och omfattande möjligheter för applikationsleverans.
mocl
för iOS, Android och macOS.
Öppna Genera
för DEC Alpha.
Scieneer Common Lisp
som är designad för högpresterande vetenskaplig beräkning.

Fritt omdistribuerbara implementeringar

Armed Bear Common Lisp (ABCL)
En CL-implementering som körs på Java Virtual Machine . Den innehåller en kompilator till Java-bytekod och tillåter åtkomst till Java-bibliotek från CL. Det var tidigare bara en del av Armed Bear J Editor.
Clasp
En LLVM-baserad implementering som sömlöst samverkar med C++-bibliotek. Körs på flera Unix- och Unix-liknande system (inklusive macOS ).
CLISP
En bytekod-kompilerande implementering, portabel och körs på flera Unix- och Unix-liknande system (inklusive macOS ), samt Microsoft Windows och flera andra system.
Clozure CL (CCL)
Ursprungligen en gratis och öppen källkodsgaffel av Macintosh Common Lisp. Som historien antyder skrevs CCL för Macintosh, men Clozure CL körs nu på macOS , FreeBSD , Linux , Solaris och Windows . 32 och 64 bitars x86- portar stöds på varje plattform. Dessutom finns det Power PC-portar för Mac OS och Linux. CCL var tidigare känt som OpenMCL, men det namnet används inte längre, för att undvika förväxling med öppen källkodsversionen av Macintosh Common Lisp.
CMUCL
Ursprungligen från Carnegie Mellon University , nu underhållen som fri programvara med öppen källkod av en grupp volontärer. CMUCL använder en snabb kompilator för inbyggd kod. Den är tillgänglig på Linux och BSD för Intel x86; Linux för Alpha; macOS för Intel x86 och PowerPC; och Solaris, IRIX och HP-UX på sina inhemska plattformar.
Corman Common Lisp
för Microsoft Windows. I januari 2015 publicerades Corman Lisp under MIT-licens.
Embeddable Common Lisp (ECL)
ECL inkluderar en bytekodtolkare och kompilator. Den kan också kompilera Lisp-kod till maskinkod via en C-kompilator. ECL kompilerar sedan Lisp-koden till C, kompilerar C-koden med en C-kompilator och kan sedan ladda den resulterande maskinkoden. Det är också möjligt att bädda in ECL i C- program och C-kod i Common Lisp-program.
GNU Common Lisp (GCL)
GNU - projektets Lisp-kompilator. Ännu inte helt ANSI-kompatibel, GCL är dock implementeringen av valet för flera stora projekt inklusive de matematiska verktygen Maxima , AXIOM och (historiskt) ACL2 . GCL körs på Linux under elva olika arkitekturer, och även under Windows, Solaris och FreeBSD .
Macintosh Common Lisp (MCL)
version 5.2 för Apple Macintosh-datorer med en PowerPC-processor som kör Mac OS X är öppen källkod. RMCL (baserat på MCL 5.2) körs på Intel-baserade Apple Macintosh-datorer med hjälp av den binära översättaren Rosetta från Apple.
ManKai Common Lisp (MKCL)
En gren av ECL . MKCL betonar tillförlitlighet, stabilitet och övergripande kodkvalitet genom ett kraftigt omarbetat, inbyggt flertrådigt runtime-system. På Linux har MKCL ett helt POSIX-kompatibelt runtime-system.
Movitz
Implementerar en Lisp-miljö för x86- datorer utan att förlita sig på något underliggande operativsystem.
Poplog
Poplog implementerar en version av CL, med POP-11 , och eventuellt Prolog , och Standard ML (SML), som tillåter blandad språkprogrammering. För alla är implementeringsspråket POP-11, som kompileras inkrementellt. Den har också en integrerad Emacs- liknande editor som kommunicerar med kompilatorn.
Steel Bank Common Lisp (SBCL)
En filial från CMUCL . "I stort sett skiljer sig SBCL från CMU CL genom en större betoning på underhållbarhet." SBCL körs på de plattformar som CMUCL gör, förutom HP/UX; dessutom körs den på Linux för AMD64, PowerPC, SPARC, MIPS, Windows x86 och har experimentellt stöd för att köra på Windows AMD64. SBCL använder inte en tolk som standard; alla uttryck kompileras till inbyggd kod om inte användaren slår på tolken. SBCL-kompilatorn genererar snabb inbyggd kod enligt en tidigare version av The Computer Language Benchmarks Game .
Ufasoft Common Lisp
-port av CLISP för Windows-plattform med kärna skriven i C++.

Andra implementeringar

Austin Kyoto Common Lisp
en utveckling av Kyoto Common Lisp av Bill Schelter
Butterfly Common Lisp
en implementering skriven i Schema för BBN Butterfly -multiprocessordatorn
CLICC
en Common Lisp to C-kompilator
CLOE
Common Lisp för PCs av Symbolics
Codemist Common Lisp
som används för den kommersiella version av datoralgebrasystemet Axiom
ExperCommon Lisp
en tidig implementering för Apple Macintosh av ExperTelligence
Golden Common Lisp
en implementering för PC av GoldHill Inc.
Ibuki Common Lisp
en kommersialiserad version av Kyoto Common Lisp
Kyoto Common Lisp
den första Common Lisp-kompilatorn som använde C som målspråk. GCL, ECL och MKCL kommer från denna Common Lisp-implementering.
L
en liten version av Common Lisp för inbyggda system utvecklade av IS Robotics, nu tillhandahåller iRobot
Lisp Machines (från Symbolics , TI och Xerox)
implementeringar av Common Lisp utöver deras inhemska Lisp-dialekt (Lisp Machine Lisp eller Interlisp). CLOS var också tillgängligt. Symbolics tillhandahåller en förbättrad version Common Lisp.
Procyon Common Lisp
en implementering för Windows och Mac OS, som används av Franz för deras Windows-port av Allegro CL
Star Sapphire Common LISP
en implementering för PC
SubL
en variant av Common Lisp som används för implementering av det Cyc kunskapsbaserade systemet
Top Level Common Lisp
en tidig implementering för samtidig körning
WCL
en implementering av ett delat bibliotek
VAX Common Lisp
Digital Equipment Corporations implementering som kördes på VAX -system som kör VMS eller ULTRIX
XLISP
en implementering skriven av David Betz

Ansökningar

Common Lisp används för att utveckla forskningsapplikationer (ofta inom artificiell intelligens ), för snabb utveckling av prototyper eller för utplacerade applikationer.

Common Lisp används i många kommersiella tillämpningar, inklusive Yahoo! Webbhandelswebbplats för butik, som ursprungligen involverade Paul Graham och som senare skrevs om i C++ och Perl . Andra anmärkningsvärda exempel inkluderar:

Det finns också applikationer med öppen källkod skrivna i Common Lisp, som:

Se även

Bibliografi

En kronologisk lista över böcker publicerade (eller på väg att publiceras) om Common Lisp (språket) eller om programmering med Common Lisp (särskilt AI-programmering).

externa länkar