Friday, June 16. 2006Eksempel på CLIMFor et stykke tid siden skrev jeg om CLIM og var især begejstret for presentation types. Det er jeg stadigvæk, og for at konkretisere hvorfor jeg synes de er så geniale, vil jeg nu komme med et eksempel på hvorledes jeg har brugt dem i løbet af de sidste uger. Jeg arbejder i min fritid på Climacs - en næstegenerations Emacs skrevet i Common Lisp. Derudover arbejder jeg også på CLIM-desktop - integration mellem en række forskellige CLIM-programmer. I CLIM-desktop har jeg skrevet kode der udnytter presentation types på en ret interessant måde - en standard presentation type er symbol, som bliver brugt når et program ønsker at vise brugeren et almindeligt Lisp-symbol. Ved at definere en presentation command translator har jeg kædet alle presentations af denne typer sammen med en kommando, der får Climacs til at redigere definitionen af symbolet. Resultatet af dette er at når et program producerer output, kan det blot sige at et givent stykke output repræsenterer et givent symbol, hvorefter brugeren til hver en tid kan holde ALT inde, klikke på symbolet, og blive taget til den fil, og position i filen, hvor symbolets funktion, klasse, type, osv., bliver defineret. Dette sker fuldstændigt uden at applikationsprogrammøren skal tænke nærmere over det, han kan blot gå ud fra at miljøet giver brugeren nogle relevante interaktionsmuligheder for en given presentation type (selvom programmet selvfølgelig også kan definere sine egne). På samme måde er andre presentation types også kædet til kommandoer - filnavne giver mulighed for at åbne filen, klassenavne mulighed for at redigere i filen, og kommandoer og kommandonavne mulighed for at redigere kommandoens definition. Det er ret komfortabelt at bruge et miljø hvor man, hvis man pludselig opdager en bug i en kommando, som regel bare kan holde ALT inde og trykke på kommandoen, og fikse den. Og denne funktionalitet kommer fuldstændigt gratis for applikationsprogrammøren, hvor det i de fleste toolkits ville være ganske besværligt at implementere. Sunday, April 30. 2006Climacs pr0n og jubilæumFor præcist et år siden gik jeg i gang med at lære Common Lisp, det sprog, der sidenhed er blevet mit absolutte favoritsprog (og som tilmed var med til at introducere mig til rigtig datalogi). Jeg har i løbet af året arbejdet på en række mindre ting - mindre, fordi jeg ikke har været tilstrækkeligt familiær med sproget og dets redskaber til at overkomme større opgaver, men i løbet af de seneste måneder har jeg alligevel været involveret i et større projekt: Climacs - en moderne implementation af en Emacs-editor i Common Lisp. Kombineret med CLIM-Desktop er Climacs allerede et udemærket udviklingsmiljø til Common Lisp - og det er allerede begyndt at se ganske fancy ud (Climacs pr0n!). Tuesday, April 11. 2006Derfor Common LispFor snart et år siden begyndte jeg at lære mig selv Common Lisp. Jeg havde tidligere arbejdet med Emacs Lisp i forbindelse med udvidelse af GNU Emacs-editoren, men det var ikke det helt store. Jeg var i stand til at læse Lisp-kode, men jeg havde ikke forståelse for Common Lisp's kultur, teknologi, features og idiomer. Common Lisp blev meget hurtigt mit yndlings-programmeringssprog til næsten alle opgaver og har markant ændret hvordan jeg tænker på programmering, programmer, datalogi og computere i det hele taget. I dette indlæg vil jeg forsøge at beskrive hvorfor Lisp er vigtigt, hvorfor Lisp er sejt, hvorfor netop du bør lære Lisp og hvor man skal starte. Overordnet set findes der ikke noget sprog ved navn Lisp. Lisp er en betegnelse for en familie af højniveausprog, der alle arver features og designidéer fra det oprindelige Lisp-sprog, LISP, der blev skabt omkring 1960. I dag er der tre større Lisp-sprog (dialekter):
Jeg foretrækker selv Common Lisp, men har også respekt for Scheme, og bruger det nu og da til mindre programmer, hvor simplicitet er i højsædet. Common Lisp er måske ikke lige så elegant som Scheme, men der er langt mere support og, i mine øjne, nogle mere kraftfulde standard-features, der gør Common Lisp stort set ubegrænset kraftfuldt - det kan praktisk taget emulere ethvert andet sprog uden større problemer og uden at blive til en Turing tar-pit (mere om det senere). Hvad gør Lisp-det-almene-koncept så fantastisk? En del af det er at Lisp er gearet imod funktionsorienteret programmering - en programmeringsstilart, hvor man, i modsætning til imperativ programmering, ikke siger hvordan noget skal udregnes, men hvad der skal udregnes. En del af dette er at undgå side-effekter, altså at variable skifter værdi. En af grundstenene indenfor funktionsorienteret programmering er at en funktion altid skal returnere samme værdi hver gang den kaldes med et unikt sæt elementer. Det betyder at udregningen af funktionens returværdi ikke må afhænge af "globale variabler" eller lignende, da disse kan ændre sig fra kald til kald. Det er naturligvis langt fra alle funktioner der kan skrives således, og det kræver tid at vænne sig til det, men når først man begynder at tænke funktionsorienteret bliver ens kode ofte lettere at forstå og med en markant nedsat mængde bugs. Jo mindre data ændrer sig, desto mere forudsigelig er programkørslen, desto mindre potentiale er der for fejl. Funktionsorienteret programmering er ikke unikt for Lisp, og der findes andre sprog der tager konceptet langt længere end Lisp. En af Lisp's andre features er dog mere eller mindre unik for sproget - at kode og data er så tæt forbundet. Det første indtryk ikke-Lisp-programmøren får, når vedkommende ser et Lisp-program, er den massive mængde parenteser - programmet ser fuldstændigt ulæseligt ud, og læseren får den idé at Lisp-programmører bruger halvdelen af deres tid på at tælle parenteser (dette passer ikke). Parenteserne er ofte blevet fremført som en brist i Lisp's design, men de er måske den absolut vigtigste features - parenteserne beskriver nemlig enkelt-lænkede lister, Lisp's grundlæggende datastruktur. Det betyder at kode kan indlæses som enhver liste, at kode kan ændres og analyseres som enhver liste, af enhver Lisp-programmør. Ethvert gyldigt Lisp-program kan indlæses som en datastruktur eller et objekt af en af Lisp's almindelige I/O-funktioner. I andre sprog ville dette involvere konstruktionen af en kompliceret parser, til at fortolke den måske tvetydige og ofte meget komplekse syntaks, og derefter uhyre forsigtighed fra programmørens side, for at bevare den syntaktiske korrekthed. Betyder dette noget i praksis, giver det nogle reelle fordele? Ja, det betyder at Lisp-programmører har lige så meget kontrol over sproget, som kun compiler-konstruktører har over andre sprog. Og det gør det naturligvis også afsindigt let at skrive værktøjer der foretager analyse af Lisp-kode. Med et enkelt kald til funktionen read kan man læse et komplet parse-træ af et Lisp-program ind i hukommelsen - det vil jeg gerne se gjort for C i under 5000 linjer. I princippet kan man sige, at hvor andre sprog har en kompliceret syntaks, der så af compileren konverteres til et abstrakt syntaks-træ, så skriver man Lisp-kode direkte i det abstrakte syntakstræk, og kan derfor lettere se, og ændre, hvad compileren bliver fodret med. Lisp's simple syntaks betyder naturligvis også at det er let at parse i andre sprog, f.eks. vist i Lisp500, en implementation af Lisp i 500 linjer ANSI C. Den nemhed, hvormed man kan manipulere Lisp-kode, udnyttes primært til at lave runtime-generering af kode ("programmer, der skriver programmer" eller "programmer, der omskriver sig selv, mens de kører") eller til at implementere makroer. Den simple syntaks muliggør også teksteditorer, som f.eks. Emacs, der er i stand til at manipulere med programmets logiske struktur, i stedet for dets tegn. En tredje af Lisp's fordele er at det er garbage-collected, men det er nok et mindre vindende argument i dag, end det var for 30 år siden.
Når Common Lisp skal promoveres er en af de oftest nævnte
fordele makroer, så hvorfor bryde traditionen? Desuden er
det suverænt en af de sejeste features i noget programmeringssprog
jeg endnu har set. Nogle kender måske makroer fra C, og forstår ikke
hvorfor Common Lisp-programmører er så begejstrede for dem. Det er
forståeligt - C's preprocessor-baserede makroer er primitive og
forårsager let fejl. Common Lisp's er i en helt anden klasse, og
drager nytte af at Lisp-kode bare er lister. Et Lisp-makro kan
forstås som en funktion, der kaldes ved compile-time, i stedet for
ved runtime. Når Lisp-compileren gennemgår et stykke
kode, ekspanderer den først alle makroer - det sker ved at
makrofunktionen kaldes med de makro-parametre programmøren har
angivet i sin kode, hvorefter makrofunktionens returværdi indsættes
som kode i stedet for makroen. Makroer giver derved mulighed for at
manipulere og ændre koden efter den er blevet læst af parseren og
før den bliver omdannet til maskinkode af
compileren. Makrofunktionen får sine parametre som Lisp-objekter
(f.eks. lister) og kan ligeså returnere kode som en ganske
almindelig liste, så at skrive en makro er næsten lige så let som at
skrive en funktion - der er nogle få detaljer vedrørende det faktum
at makroekspansion foregår ved compile-time, som man bør være
opmærksom på, men grundlæggende er det at skrive en makro og en
funktion to ret ens processer i Lisp. Makroer giver enhver
programmør mulighed for at ændre sproget præcist som vedkommende
lyster, næsten uden grænse. Det er muligt at skrive en makro, der
som parameter tager {C++, C#, VB, Haskell, SML, Brainfuck,
FORTRAN}-kode og genererer Lisp-kode til compileren, hvis man skulle
have lyster i den retning. Common Lisp-programmører bruger ofte
makroer til at udvide sproget, lave syntaktisk sukker og
implementere domæne-specifikke sprog. Nu og da kan en del af et
Lisp-program udtrykkes klarere i et sprog designet til opgaven, og
via makroer kan man implementere et sådan under-sprog direkte i
Lisp. Makroer bruges ofte til mindre syntaktisk detaljer, der
isoleret set virker lidt banale, men som sammenlagt kan øge
fornøjelsen ved at skrive programmer. Tag f.eks. den klassiske
design pattern singleton - hvis man ønsker at lave en sådan
i C++ involverer det som regel enten at skrive en pæn bid kode og
nedarve fra en anden klasse. Hvis C++ havde makroer som Lisp, ville
man blot kunne udvide sproget til at tillade en syntaks
som
(loop for odd in (list 1 3 5 7 9)
for even in (list 2 4 6 7 8)
collecting odd
collecting even
maximizing odd into max
maximizing even into max)
Dette simple loop itererer over to lister (samtidigt), samler den
til en fælles liste og indsætter den største værdi i
variablen max. Alt hvad makroer kræver er at deres
parametre opfylder grundlæggende Lisp-syntaks-regler, f.eks. et
balanceret antal parenteser, pga. parseren. Man kan dog godt omgå
dette, hvis man er virkelig ivrig, ved at definere
en reader-makro, der ændrer på parserens opførsel, og
tillader en at tage total kontrol over den stream som parsingen
foregår fra. Dette bruges typisk til at implementere nye literal
types, f.eks. kan man få parseren til at omdanne enhver instans af
Lisp-miljøer/runtimes er programmer ganske ulig hvad man normalt ser i dag, og vil være fremmede for de fleste nye Lisp-programmører, ganske som de var det for mig engang (Emacs-brugere vil dog kunne genkende en del af dem). På sin vis minder Lisp-runtimes meget om .NET-runtimes og JRE's - et runtime-miljø kører forskellige programmer og man kan via diverse protokoller snakke med miljøet. I Lisp tager dette som regel form af en REPL - et Read-Eval-Print-Loop. Det er en simpel kommandoprompt, hvor man kan indtaste et Lisp-udtryk, få det evalueret og få udskrevet returværdierne. Ved denne prompt har man som bruger fuld adgang til Lisp-miljøet, inklusive compileren, på samme måde som alle programmer har det ved runtime. På ethvert tidspunkt kan Lisp-miljøet blive bedt om at kompilere et udtryk, en funktion eller ændre værdien af data forskellige steder i systemet. Det er en interaktivitet man hurtigt lærer at værdsætte, hvis man, f.eks. som C-programmør, er vant til at ens miljø sætter store grænser op mellem write-time, compile-time og run-time for ens program. I Lisp er miljøet altid til rådighed og kan altid evaluere ethvert Lisp-udtryk man måtte ønske. Miljøet compiler alle funktionsdefinitioner, man som programmør eller program indlæser, til maskinkode, og der er således ikke et større performancetab end i .NET og Java, og da Lisp er let at optimere, og compilerne desuden har mere end 20 års optimeringsteknologi bag sig, er Lisp nu og da overraskende hurtigt. Dette super-interaktive miljø har været en stor fordel for mig i de seneste par dage, hvor jeg har arbejdet på tekst-editoren Climacs. Mit pt. foretrukne udviklingsmiljø til Common Lisp er GNU Emacs med SLIME, der holder en åben socket-baseret forbindelse til Lisp-miljøet, og derved, med et enkelt tastetryk, kan sende en opdateret funktionsdefinition til Lisp-miljøet, der så vil kompilere og installere funktionen i systemet. Dette udnyttede jeg, da jeg debuggede en række Lisp-funktioner i Climacs - jeg ændrede funktionen i Emacs, skiftede over til Climacs-vinduet, testede den, og vendte tilbage til Emacs for at arbejde videre. At skifte over til Climacs og teste min funktion tog mig vel i gennemsnit under 3 sekunder, da jeg jo ikke behøvede at genstarte programmet - Lisp har ingen problemer med at ændre programmer, der allerede er indlæst - og jeg behøvede heller ikke genindtaste den test-data jeg brugte til at afprøve min kode. I andre sprog ville jeg have været nødt til at genstarte det program jeg testede hver gang jeg ændrede en smule på en funktion, men ikke i Lisp - der kan jeg omskrive mine programmer mens de kører. Den eneste begrænsning er at en ændring af en eksisterende funktion kun vil have effekt på nye funktionskald, hvis programmets control-flow allerede befinder sig i funktionen, vil det ikke blive påvirket før næste gang det træder ind i funktionen. Dette påvirker naturligvis ens udviklingsrytme i massiv grad, og man bliver langt mere villig til at eksperimentere, fordi det går så hurtigt med at få feedback på ens forsøg. Der er naturligvis ingen rose uden torne, og Common Lisp har også sine fejl, hvor den primære er manglen på libraries. Hvis man er vant til den massive mængde libraries der findes i forbindelse med Java, .NET, C eller C++, så kan man let blive skuffet over det noget mere magre udvalg indenfor Common Lisp. De libraries der findes er dog ofte ret gode, og da der igennem årtier konsistent er blevet produceret store programmer i Common Lisp, må det jo være muligt at klare sig. Common Lisp er et sprog i vækst, og selv i det lille år hvor jeg har observeret sproget, er der sket store landvindinger i library-udbuddet og implementations-kvaliteten, så det er så absolut ikke et døende sprog man i givet fald tilslutter sig. Selvom Common Lisp nok aldrig bliver rigtigt mainstream, så har det altid været, og vil nok altid være, et særdeles velegnet sprog til at løse svære opgaver og give ansvarlige programmører ekstremt kraftfulde værktøjer til det. Alt i alt kan jeg kun anbefale nysgerrige programmører at lære Common Lisp - det er en for det første en øjenåbner og for det andet et praktisk værktøj. I disse tider er det rimeligt nemt at installere et frit Common Lisp-miljø og finde den nødvendige dokumentation til at komme i gang, men det skal dog siges med det samme at den optimale oplevelse får man på et GNU/Linux-system (muligvis også OS X). Den letteste måde at komme i gang på, og den jeg også selv fulgte for omkring et år siden, er at læse den gratis tilgængelige bog Practical Common Lisp, der også indeholder anvisninger på at opsætte et udviklingsmiljø (også under Windows). Derefter kan man gå i gang med at læse Planet Lisp og besøge #lisp på Freenode i al den fritid man naturligvis opnår grundet produktivitetsforøgelsen ved at skifte til Lisp. Læs evt. også min Common Lisp-side. Tuesday, April 4. 2006Interaktiv programmeringJeg har efterhånden gjort det i et godt stykke tid, men nu og da stopper jeg alligevel op og tænker - hvor er interaktiv programmering bare fedt! Konceptet med at kunne omskrive et program mens det kører, og evaluere ethvert programudtryk interaktivt, ved en prompt, uden rekompilering eller andet skidt, er en helt fantastisk oplevelse sammenlignet med traditionelle edit-compile-run-loops. Hvor man normalt ændrer nogle ting, rekompilerer, tester og fikser det, der ikke virker, føler man sig når man udfører interaktiv programmering mere som en kunstner, der former en klat ler eller en klump marmor. Man får utroligt hurtigt svar på sine formodninger og man kan på ekstremt kort tid nå at gennemgå mange forskellige iterationer af en given implementation af noget funktionalitet. Det lynhurtige feedback-loop inviterer en til at udføre eksperimenter, og min erfaring er derfor at man har lettere ved at opdage både optimale løsninger på problemer, samt uforudsete konsekvenser af ens ændringer. Interaktiv programmering fjerner mange af de kedelige elementer, og lader en koncentrere sig om det essentielle og det underholdende - at få skrevet noget kode. Og da jeg samtidigt arbejder på en editor/IDE er fornøjelsen naturligvis endnu større - at hacke på et program i selve programmet er så sejt at det næsten er surrealistisk. Wednesday, January 11. 2006CLIM-desktopJeg har tidligere skrevet om CLIM, Common Lisp Interface Manager. Her i dag har jeg undersøgt CLIM-desktop, en pakke af programmer, der danner et mere eller mindre integreret CLIM-baseret udviklingsmiljø til Common Lisp. Lad mig sige det med det samme: CLIM-desktop og dets komponenter er som hovedregel ekstremt buggy (især Climacs, teksteditoren er slem), ikke udpræget finpudsede og ganske besværlige at sætte op og bruge. Det er dog ret tydeligt at der er stort potentiale bag et miljø, der er helt og holdent implementeret i CLIM. At stort set alle grafiske elementer er kontekst-sensitive i den rigtige sammenhæng er en fantastisk feature, som virkelig gør interfacet dynamisk og effektivt at arbejde med. CLIM-desktops ideal er hvad Emacs er en primitiv efterligning af. CLIM-desktop gør det meget let at udnytte Lisp's indbyggede introspection-faciliteter, da det pludselig er ganske trivielt at klikke sig rundt i slots, generiske funktioner, subklasser og superklasser i et klassehieraki, samt konstant, selv når man er nok så langt nede i debuggeren, at kunne pille ethvert objekt fra hinanden. Denne gang har jeg screenshots. På dette screenshot vises Climacs, CLIM-listeneren og Beirc (en IRC-klient). I CLIM-listeneren vises resultatet af lidt klikkeri gennem et objekthieraki - derudover er listeren en fuldgyldig REPL, hvor man kan indtaste vilkårlig Lisp-kode. Hvis man, eller programmerne, dummer sig, ender man i debuggeren (nederst til højre), hvor man kan gennemgå en stack trace, se på funktionsparametre, etc. CLIM-desktop har potentiale - nok ikke som desktop til den almene bruger, men som udviklingsmiljø til Common Lisp vil det snildt kunne overgå SLIME, hvis der bliver udviklet en smule mere på det. Friday, December 23. 2005Comment (1) Trackbacks (0) Common Lisp Interface ManagerDe sidste par dage er, for mit vedkommende, gået med at eksperimentere med CLIM - Common Lisp Interface Manager - kort sagt, et widget-framework til Common Lisp. Det er bemærkelsesværdigt at CLIM, der er næsten 20 år gammelt, har features, der endnu ikke er set i nutidens udbredte, grafiske miljøer. Ja, jeg vil nu skrive om grafiske interfaces, ja, jeg vil rose dem. Ja, det er muligvis en gris, der flyver rundt, uden for dit vindue. En af disse features, og måske den mest centrale grundpille i hele CLIM's design, er konceptet omkring presentations. Ethvert grafisk interface præsenterer information til brugeren. Dette sker ved at printe tekst, billeder eller andre grafiske repræsentationer, men i selve udprintningen mister de deres oprindelige datatype - de bliver bare til tekst, et billede, en knap, eller et andet grafisk element. Hvis interface-designeren ønsker at de skal have en speciel betydning, er han nødt til selv at sætte signaler op til at opfange musetryk, drag'n'drop, etc. Sådan er det ikke med CLIM - her præsenteres information til brugeren ved at present'e det, og en CLIM presentation husker sit oprindelige objekt, så man fra ethvert grafisk element kan sige præcist hvilket Lisp-objekt, der står bag. Naturligvis kan et stykke data stadigvæk repræsenteres via tekst eller et grafisk element, men CLIM vil huske forbindelsen mellem brugerinterface-elementet og Lisp-objektet. Dette giver for det første brugerinterfacet en massiv grad af reflection, noget der skulle få OOP-tilhængere til at klappe i deres små hænder. For det andet skaber det også en alternativ inputmetode til diverse programmer. Hvis et program f.eks. beder om et billede som input, så vil enhver repræsentation, der er present'et som typen "billede", kunne bruges som input til programmet, blot ved at trykke på det - også repræsentationer fra andre programmer end det, der beder om input. Et Lisp-objekts interne type, og hvordan det repræsenteres i brugerinterfacet, behøver naturligvis ikke være identisk - et billede kan godt vises for brugeren som en tekststreng, eller et tal - det er ligegyldigt for CLIM, det vil stadigvæk være gyldigt input, når et program beder om et billede. Programmøren kan naturligvis altid oprette nye præsentationstyper og metoder til at indlæse, og præsentere, objekter som den relevante type, og selve systemet, der sørger for at denne mekanisme virker, er næsten uendeligt fleksibelt, og kan tweakes på stort set enhver mulig måde. Men ak, ingen applikation uden en null pointer. Lige så elegant og avanceret som CLIM er, rent specifikationsmæssigt, lige så dårlig er den eneste frie implementation, McCLIM, der ofte er plaget af besynderlige bugs. Ydermere stammer CLIM fra en anden tid, hvor man ikke havde de samme principper omkring GUI-design som man har i dag, derfor kan CLIM-applikationer virke ekstremt arkaiske og gammeldags. Det rører dog ikke ved deres effektivitet - CLIM blev designet som en højere enhed mellem de muligheder hhv. en kommandolinje og en GUI giver, og grundprincipperne er stadigvæk ganske solide. Tuesday, December 20. 2005MetaprogrammeringJeg har netop læst en rant, der disser Java (hvilket altid er godt), og meget velartikuleret beskriver hvorfor metaprogrammering er en god ting. Da jeg for tiden læser om metaprogrammering, og desuden er enig i hans antagelser, vil jeg anbefale alle at læse hans artikel, og prøve et sprog der understøtter metaprogrammering. Hvorfor skal man kun kunne manipulere data? Hvad er der galt i at manipulere kode? Edit: Wow, den rant er skrevet af en meget klog mand. En af hans andre artikler er også obligatorisk læsning. Sunday, November 27. 2005Comments (0) Trackbacks (0) SDBL, Darcs, Portable AllegroServeJeg har fået kodet en smule Lisp her i weekenden, helt præcist har jeg arbejdet videre på min database-engine, SDBL, så de fleste grimme bugs burde være fjernet. Der er stadigvæk ikke alt for meget funktionalitet, og visse af de interne datastrukturer (f.eks. brugen af flade arrays til at gemme data) har vist sig at være ret upraktiske. Jeg overvejer at skifte over til at bruge cirkulære, linkede arrays (hvor arrays bestående af måske 10 til 20 celler er forbundet i en linked liste), og opbygge indices ind i dem, for at sikre hurtig lookup. Det hjalp gevaldigt på mængden af rettede fejl, da jeg begyndte at bruge SDBL som backend til nogle trivielle test-programmer - en CD-database, og, hvad der nok er mere interessant, en kollaborativ tagwall (min WikiWikiWall). Tagwallen er et webprogram, der bruger Portable AllegroServe. Når min computer er tændt, kan tagwallen ses her. Hverken CD-databasen eller tagwallen udviser udpræget god kodeskik, men det var heller ikke formålet med dem. Portable AllegroServe er en rigtig rar webserver at arbejde med. Det er en portabel udgave af open-source webserveren AllegroServe, som Franz Inc. udviklede som eksempel på multithreaded udvikling med Allegro Common Lisp. I modsætning til f.eks. Apache er AllegroServe ikke filorienteret - man sætter ikke en eller anden mappe som rod, hvorefter serveren sever ud fra den. I stedet benytter man en "publish"-funktion, så man kan tilknytte HTTP-queries til specifikke filer, til enten reelle filer, eller Lisp-funktioner, hvis output bliver sendt som svar til klienten. På den server der driver min tagwall har jeg således tilknyttet queries til filen "/tagwall", til funktionen sdbl-tagwall::tagwall, hvis returværdi bliver brugt som svar til klienten. Det er ret smart. Et andet stykke fabelagtig softwareteknologi, som jeg virkelig er blevet betaget af i denne weekend, er versionskontrol-systemet Darcs. Et versioneringssystem er, groft sagt, et program der holder styr på kildekoden til et program, logger alle ændringer til koden, og giver mulighed for at gå tilbage i historien, hvis nye ændringer er problematiske. Wikipedia kan forklare det bedre end jeg kan. Det der virkelig fængede mig ved Darcs, er at et er utrolig simpelt at bruge - i modsætning til CVS, som jeg tidligere har arbejdet med. For det første er Darcs' kommandolinjeprogram simpelt at arbejde med, ret selvdokumenterende, og med interaktive dialoger til stort set alt. For det andet kræver Darcs ikke at man kører et serverprogram - et Darcs-repository er bare en mappe, og man må selv om hvorvidt man tilgår den via SSH, FTP, SMB, NFS eller lokalt. I forhold til andre versionskontrol-systemer er Darcs simpelt at komme i gang med, har en masse nyttige features (såsom at ændringer ikke er filspecifikke), og i mine øjne ideelt til enkelte programmører, der blot ønsker noget simpelt til at holde styr på deres kode. Muligvis kan Darcs også bruges af større hold, med mere komplekse behov, men det har jeg ikke selv haft mulighed for at afprøve - udviklingen af selve Darcs foregår dog via Darcs. Jeg kan kun anbefale at læse tutorialen og komme i gang. Friday, October 28. 2005Hurra for SLIME og Lisp!Jeg sidder her til aften og prikker lidt til Climacs, i håb om at jeg kan få det til at udføre nogle nye kunster. Det er en relativt stor kodebase, og der er også en større mængde mere eller mindre buggy libraries, som Climacs gør brug af. At forstå et kompliceret system er svært nok i forvejen, og det bliver ikke lettere af at bruge et primitivt udviklingsmiljø, der ikke understøtter så mange forskellige måder at prikke til koden på, men kun tillader at man ændrer i nogle filer, recompiler, og ser hvad der sker. Derfor er jeg glad for at Climacs er skrevet i Common Lisp, for med SLIME i GNU Emacs har jeg en editor der er rimeligt tæt integreret med Common Lisp-runtime-programmet, der delvist forstår Common Lisp, og har en række features, der gør det lettere at forstå en relativt udokumenteret, fremmed kodebase. Især kan jeg godt lide slime-edit-definition (M-.), der gør det muligt at springe direkte til definitionen for en funktion, en variabel, en klasse, eller noget som helst andet. At dette overhovedet kan lade sig gøre, er naturligvis også en bieffekt af fri/åben kode. For mit vedkommende er det en stor hjælp at jeg kan læse kildekoden for de værktøjer jeg bruger, og så er jeg sådan set ligeglad med anti-FOSS horderne, der påstår at det alligevel ikke er andre end de primære udviklere, der nogensinde ser på et stykke kildekode, selv hvis det er åbent. En anden rar funktion er slime-describe-symbol (C-c C-d C-d), der viser stort set enhver tilgængelig information om et givent symbol - hvis det er en funktion, hvilke parametre den tager, eventuel inline dokumentation, information om hvornår, og hvorfra, den blev compiled, osv. Hvis det er en klasse vises alle relaterede klasser, det være sig superclasses eller subclasses. Og sidst, men måske vigtigst, er muligheden for at have en hel runtime kørende som en kommandoprompt, hvor man blot kan skrive arbitrære Lisp-udtryk, og få dem evalueret med det samme, også en virkelig fed feature, der gør det meget hurtigere at teste funktioner, end andre sprogs edit-compile-test cyklusser. Tuesday, October 4. 2005Simple DataBase in LispFor tiden arbejder jeg på et program kaldet SDBL, en forkortelse for Simple DataBase in Lisp. Det er et simpelt relationelt databasesystem, der kan bruges som et library af Common Lisp-programmer. Jeg har ikke arbejdet på det i mere end et par dage (og meget af det har jeg omskrevet en del gange), så det er i sandhed simpelt: Det er ikke threadsafe, det kan kun bruges fra Common Lisp, og ikke på tværs af processer, det understøtter kun meget simple queries (stub-JOIN, INSERT, SELECT, DROP, UPDATE, men med andre navne og syntaks), der er ingen SQL-support, og hele programmet er på omkring 500 linjer. Hvad der derimod er, er en, i mine øjne, ret sej metode til at gemme en database til disk. Normalt gøres den slags ved at definere et binært, XML-baseret, eller på anden måde programspecifikt format. Under alle omstændigheder, noget der baseret på de fleste informationer kan kaldes data. Dette er ikke den måde SDBL gør tingene på, i stedet bliver der skrevet en Lisp-expression til disk, et lille Lisp-program, der, når det køres, returnerer en database med samme indhold, som den database der blev brugt som parameter, da koden blev genereret. Oprindeligt gjorde jeg det på denne måde fordi det virkede som det mest enkle (under 30 linjer kode), men nu er jeg efterhånden blevet ret begejstret for den fremgangsmåde. At en sådan ting overhovedet kan lade sig gøre viser hvor stor en fordel Lisp's opfattelse af "data som kode - kode som data" kan være - den fremgangsmåde jeg har brugt i SDBL kan ganske enkelt ikke lade sig gøre i de fleste andre sprog. Og i de sprog hvor den er en mulighed (f.eks. PHP), gør syntaksen det så besværligt at generere gyldig og korrekt kode, at metoden mister en del af sine fordele. Lisp's syntaks, der ellers er foragtet af så mange der ikke er vant til den, gør at det ikke er sværere at generere gyldig kode, end det er at skabe en hvilken som helst anden liste. Et Lisp-program er vitterligt ikke andet end én eller flere lister. Hvis folk har lyst til at se på SDBL smider jeg nu og da kildekoden op her (kun én fil, indtil jeg mener at systemet er så stort, at der er behov for at dele det op). Friday, August 12. 2005Procedural closure <3Det er frygteligt lang tid siden jeg har skrevet noget om Lisp her på siden. Det må jeg råde bod på. Til en forandring vil dette indlæg ikke omhandle Common Lisp, men derimod Scheme, en mere elegant men også mindre featurefuld dialekt af Lisp. I dette indlæg vil jeg forfejle et forsøg på at give udtryk for hvor seje first-order procedures er, og hvor mange interessante ting man kan bruge dem til. Eksemplet i dette indlæg er taget fra den herlige bog Structure and Interpretation of Computer Programs. Nuvel, først lidt trivia: Normalt siges det at den grundlæggende datastruktur i Lisp er den enkelt-linkede liste (Lisp står som bekendt(?) for LISt Processing). Dette er så ikke helt sandt, den grundlæggende datastruktur er derimod cons-cellen, der har fået sig navn fra den funktion man bruger til at oprette den (cons). En cons-celle er grundlæggende bare et par - det er en datastruktur, med to "pladser", hvor man kan gemme værdier. De to pladser kaldes hhv. for car og cdr, igen navngivet efter de funktioner man bruger til at tilgå dem (car og cdr, hvis I ikke selv skulle have udledt det - navnene er et levn fra den IBM 704-maskine som Lisp oprindeligt kørte på, og står for hhv. contents of address register og contents of decrement register - disse navne har ikke længere nogen relevans). Hvis man vil oprette en Lisp-liste, gemme man en vilkårlig værdi i en cons-celles car, og endnu en cons-celle i cdr. I den næste cons-celle gemmer man så endnu en vilkårlig værdi i car, og endnu en cons-celle i cdr, og sådan fortsætter det, til man afslutter listen ved at placere værdien nil i en cons-celles cdr (nil er også kendt som () - den tomme liste - og svarer lidt til en null pointer). Resultatet er at man har en liste, der indeholder de værdier som man har gemt i de forskellige cons-cellers car. Dette system er noget af det mest grundlæggende i Lisp, men det kan alligevel meget simpelt re-implementeres af en programmør, ved at bruge first-order funktioner. Således defineres funktionerne cons, car og cdr:
(define (cons x y)
(lambda (z)
(cond ((= z 0) x)
((= z 1) y)
(else (error "Error in cons cell")))))
(define (car cell)
(cell 0))
(define (cdr cell)
(cell 1))
Idéen her er at funktionen cons returnerer en funktion, der når den bliver kaldt med argumentet 0 returnerer car, og når den bliver kaldt med argumentet 1, returnerer cdr. car og cdr er så implementeret ved at kalde en funktion af den type som cons returnerer med det passende parameter. Ved at neste kald af cons ((cons 1 (cons 2 (cons 3 nil)))) kan man så oprette lister, der kan tilgås via car og cdr. At implementere en så low-level datastruktur som en cons-celle via funktioner er naturligvis ikke nyttigt, for ydelsen vil blive ret ringe. Men det viser hvor fleksible first-order funktioner er, og hvilken styrke closure (her defineret som datastrukturer der kan indeholde datastrukturer af samme type, for at lave mere komplekse strukturer) er for et sprog. Monday, June 6. 2005StumpwmJeg har i snart et år brugt den fænomenale window manager ratpoison - en keyboardcentrisk window manager, til folk der ikke kan lide gnaveren. For et par dage siden faldt jeg over Stumpwm, en window manager bygget på samme principper som ratpoison, men skrevet i Common Lisp. Jeg ledte i forvejen efter et interessant Lisp-projekt, som jeg kunne få noget erfaring med sproget, så jeg hentede Stumpwm fra CVS, og prøvede at få det til at køre. Det var... ikke trivielt. Der var også en del bugs i programmet, som jeg så efterhånden har fået fikset. Jeg har uploadet min kildekode her. Med mindre man kender en del til Lisp, er chancen for at man kan få det til at køre på egen hånd yderst minimal. Jeg præsenterer derfor: Athas' ultimative guide til opsætning af Stumpwm under GNU/Linux:
Jeg har naturligvis kun checket disse trin på et Gentoo-system, så der er ingen garanti for at det virker på Debian. Friday, May 13. 2005Emacs styrerI går blev jeg mindet om hvorfor jeg bruger Emacs. For tiden bruger jeg min fritid på at studere Common Lisp, et ret interessant programmeringssprog, og jeg skriver naturligvis koden i det program jeg også bruger til alt andet: GNU Emacs. GNU Emacs er også selv skrevet i Lisp (dog Emacs Lisp, en noget mere primitiv Lisp-dialekt), og har en række funktioner der gør det hurtigt og let at slå op i dokumentationen for en given Emacs Lisp-funktion. Jeg kan naturligvis forsøge at slå Common Lisp-funktionerne op i Emacs Lisp-dokumentationen, nu og da vil jeg få korrekt information (især for grundlæggende funktioner som list, append eller cons), andre gange vil jeg få næsten-korrekt information (hvis funktionen findes i Emacs' Common Lisp-kompatibilitetssystem), men i langt de fleste tilfælde vil jeg ikke få noget ud af det. Common Lisp har et opslagsværk kaldet HyperSpec, en slags Lisp-MSDN, og jeg opdagede at Emacs havde en funktion til at slå Common Lisp-symboler op på HyperSpec-siden: hyperspec-lookup. Jeg rebindede så C-h f og C-h v til, når jeg redigerer Common Lisp-kode, at køre funktionen hyperspec-lookup i stedet for hhv. describe-function og describe-variable. Derefter ændrede jeg værdien af variablen browse-url-browser-function til w3m-browse-url, så hyperspec-lookup bruger Emacs' tekstbaserede w3m-browser til at foretage lookups. Så nu har jeg også let tilgængelig dokumentation for Common Lisp-symboler. Senere vil jeg nok erstatte det rene kald til hyperspec-lookup med en funktion der også checker dokumentationen for de funktioner jeg selv har skrevet, ligesom det virker med Emacs Lisp. Det er rart at bruge en editor med et indbygget programmeringssprog. Jeg går aldrig tilbage til almindelige statiske programmer. Min .emacs kan hentes her. Thursday, May 5. 2005Common Lisp og standarderDet siges at man først værdsætter noget, når man har mistet det. Det har jeg måttet sande i de seneste par dage, hvor jeg har forsøgt at lære mig Common Lisp. I sprog som C og C++ er jeg vant til at jeg kan tage den kode jeg har skrevet til GNU GCC, og kompilere den med andre compilere, som f.eks. Borlands eller Microsofts. Hvis der er problemer, så er det oftest fordi jeg har sjusket, eller fordi jeg udnytter implementations-specifikke detaljer. Jeg kan f.eks. regne med at scanf gør det samme under alle compilerne. Alene tanken om at en implementation skulle kunne implementere scanf forkert virker grotesk. Det siges ofte at C og C++'s standarder er svage, og ikke efterfulgt særligt godt, men graden af inkonsistens i det miljø er det rene vand i forhold til hvordan det er med Common Lisp. Her er et eksempel: Common Lisp har en funktion ved navn yes-or-no-p (ja, Common Lisp tillader bindestreger i identifiers, det er ret sejt). Det er en funktion der tager ét parameter, en tekststreng, og som returnerer enten t eller nil - Lisp's udgaver af true og false. Funktionen kunne ikke være meget mere simpel, den skriver paremetret på terminalen, tager som input enten "yes" eller "no," og returnerer en passende værdi. Men så simpelt skal det naturligvis ikke være. For at gøre det klart for brugeren at vedkommende skal skrive enten "yes" eller "no" kan det være hensigtsmæssigt at skrive noget i stil med "(yes/no)" i prompten. Altså: (yes-or-no-p "Is this correct: [yes/no] ") Under CLISP (min nuværende Common Lisp-implementation) giver det følgende output: Is this correct: [yes/no] (yes/no) What the hell? Okay, så funktionen sætter altså selv "(yes/no)" på. Det er da meget fint... hvis det ikke var fordi der ingen regel er for hvad der helt præcist skal sættes på - det varierer fra implementation til implementation. Man kan ikke være sikker på hvad der helt præcist bliver udskrevet, eller om der bliver udskrevet noget overhovedet. Hvad ville der ske hvis MSVC++ pludselig begyndte at skrive "input integer" i scanf-kald? Den slags generer mig grusomt. Dette er dog kun en mindre detalje (okay, flere detaljer, der er også nogle andre problemer jeg er i gang med at undersøge). Jeg er allerede blevet meget fascineret af Common Lisp, og det har så absolut en chance for at slå C++ som mit favoritsprog (muligvis fordi det er det eneste der er mere kryptisk...). Jeg råder alle til at prøve kræfter med Common Lisp, eventuelt gennem SLIME, der gør GNU Emacs til et top-notch Common Lisp udviklingsmiljø. Saturday, April 30. 2005Practical Common LispHåhå, Lisp er hverken almindeligt eller praktisk, håhå. Alligevel har Peter Seibel skrevet bogen Practical Common Lisp, og ikke bare det, han har også gjort den frit tilgængelig online! Derfor er jeg nu gået i gang med at lære Common Lisp... jeg har haft lyst til at lære det i et stykke tid, men jeg har ikke rigtigt haft tid. Bogen er også blevet anmeldt på Slashdot, og der burde være nok Lisp-jokes for alle. |
Language selectionFoobar (EN)
My name is Troels Henriksen, known as Athas in some parts of the Internet. This is my weblog, where I write about everything and nothing in particular. I am, at the time of this writing, a student of datalogy at the University of Copenhagen. I spend my free time programming, mostly in C++ and Common Lisp. Calendar
ArchivesQuicksearchCategoriesCreative CommonsSyndicate This Blog |
|||||||||||||||||||||||||||||||||||||||||||||||||
All original material on this website is copyright Troels Henriksen, and may only be used in accordance with the license listed in the sidebar.
