Scheme

programmeringsspråk

Scheme er eit funksjonelt programmeringsspråk i Lisp-familien. Scheme vart introdusert til den akademiske verda av Guy L. Steele og Gerald Jay Sussman gjennom fleire artiklar på 1970-talet kalla «the lambda papers». Desse artiklane skildra eigenskapane til Scheme og gav ein kompilator kalla «RABBIT» som kompilerte Scheme-kode til eit subset av MacLISP (ein tidleg Lisp dialekt). I dag er Scheme standardisert, og det finst ei rekkje kompilatorar og tolkere for språket.

Saman med Common Lisp er Scheme blant dei mest brukte Lisp-dialektane. To ting som skilde Scheme frå andre Lisp-dialektar frå si tid er at variablar har leksikal rekkjevidde («lexical scope») og at implementasjoner må garantera halekall-optimalisering («tailcall optimalization»).

Scheme følgjer ein minimalistisk filosofi. Syntaksen til programmeringsspråket er ekstremt enkel samanlikna med andre språk, og mengda innebygde prosedyrar er relativt avgrensa. Scheme vert i dag hovudsakleg brukt i akademiske miljø, og har tradisjonelt vorte brukt i forsking på kunstig intelligens.

Standardisering endre

Det er to standardar som definerer Scheme; ein offisiell IEEE-standard, og ein de facto standard kalla «Revised Report on the Algorithmic Language Scheme», forkorta til RnRS, der n kallar revisjonen. Den siste revisjonen av RnRS heiter R6RS og utvidar språket, blant anna med eit standardbibliotek.

Grunna Scheme si avgrensa mengd innebygde funksjonar, tilbyr mange implementasjoner funksjonalitet som ikkje vert omfatta av standardane nemnt ovanfor. I fellesskap har difor utviklarar bak ulike Scheme-implementasjoner laga fleire «Scheme Requests for Implementation» (SRFI). Ein SRFI definerer utvidingar av språket som brukarar kan nytta på tvers av ulike implementasjoner.

Eigenskapar endre

Scheme er ein veldig simplistisk Lisp-dialekt, med berre det mest naudsynte av datatypar og funksjonar tilgjengeleg. Schemes filosofi er å, i staden for å byggja ut språket med nye primitive datatypar og operasjonar, og dermed gjera det stort og komplekst, vera eit enkelt og effektivt språk der slike nye finessar lett kan leggjast til av programmereren. Det er likevel ei rekkje Scheme-implementasjoner som kjem med mange medfølgende bibliotek.

Syntaks endre

Det er syntaksen til Lisp som i hovudsak er kjelda til kritikk av språket. Uttrykk i Scheme vert skrive på såkalla listeform. Ei liste er eit eller fleire element etter kvarandre, omringa av parentesar. Eit element kan vera eit atom (eit primitivt «objekt» som t.d. eit tal eller eit symbol), eller ein kombinasjon, som er underlister i lista. I Scheme vert evaluert ei liste ved å evaluera det første elementet i lista, for å få ein funksjon. Denne funksjonen vert kalla med resultatet av å evaluera resten av elementa i lista. Eit enkelt uttrykk kan sjå slik ut:

 (+ 1 2 (- 9 3) 2)

Her har vi ei liste med symbolet +, to tal, kombinasjonen (- 9 3), og eit tal. Kombinasjonen (- 9 3) er i seg sjølv ein eit uttrykk med symbolet – og to tal. Her vil + evaluerast ved at symbolet vert slått opp i lista over variabelbindingar, og resultera i funksjonen for addisjon, som vert kalla med resultatet av å evaluera dei andre elementa i lista som argument. 1 og 2 vil evaluera til seg sjølv, kombinasjonen (- 9 3) vil evaluerast på same måte, og 2 vil evaluera til seg sjølv. Vi får 11 som resultat.

Numerisk tårn endre

Scheme har eit relativt godt utval av datatypar for tal. RnRS standardane legg ikkje nokon føring på korleis tal skal representerast i minne. Det er definert eit såkalla numerisk tårn som består av abstrakte datatypar for komplekse tal, reelle tal, rasjonale tal og heiltal. Medan det er frivillig å implementera heile det numeriske tårnet i R5RS, er det påkravt av R6RS.

Predikata complex?, real?, rational? og integer? kjenner att kvar einskild type, medan predikatet number? kjenner att alle. Dersom eit tal er eit heiltal, så vil det òg vera eit rasjonelt tal, eit reelt tal og eit komplekst tal. Medan eit rasjonalt tal som 5/8 vil òg vera eit reelt og komplekst tal, men ikkje eit heiltal.

Implementasjoner må òg skilja mellom presise (exact?) og upresise (inexact?) tal. Eit tal-objekt er presist dersom det anten er skrive som ein presis konstant, eller dersom det er resultatet av ein presise operasjon utført på presise tal. Eit tal-objekt kan vera upresis dersom det er skrive som eit upresist tal, dersom det er resultatet av ein upresis operasjon eller dersom det kjem frå ein presis operasjon utført på upresise tal. R5RS standarden krev at dersom to implementasjoner kjem fram til eit presist resultat frå ein beregning som ikkje involverer upresiste tal, så vil resultata vera matematisk like.

Funksjonskall endre

Sidan ein i Lisp må skriva alle funksjonskall i eigne lister, med funksjonsnamnet først, gjev det ei rekkje fordelar. Det er ingen skilnad på operatørar og funksjonar, som det er i andre språk -- det er ingen syntaktisk måte ein kan skilja mellom brukerdefinerte funksjonar og primitive operatørar, slik som det er i mange andre språk der ein skriv operatørar mellom argumenta, medan funksjonar vert kalla på andre måtar. Dermed er det naturleg nok heller ingen form for operatør-prioritet i Lisp, sidan alle funksjonskall er skrive i eigne lister. Dei negative sidene med prefiksnotasjon og lister er at det ikkje følgjer den vanlege matematiske notasjonen, og dermed krev det ein tilvenning å setja seg inn i Scheme, spesielt når ein skal skriva matematiske uttrykk.

Scheme har berre éin løkkefunksjon, kalla do. Andre formar for iterasjon og løkker vert gjort ved hjelp av rekursjon. I Lisp vert skrive data og kode med same syntaks. Det tyder at ein kan byggja opp uttrykk som deretter kan evaluerast. Eit døme på dette:

 (define (lag-addisjon a b c)
   (list '+ a b c))

Her vert det definert ein funksjon, lag-addisjon som tek tre argument, a, b og c. Denne funksjonen returnerer ei liste med symbolet + og dei tre argumenta. Merk at + vert skrive med ein apostrof føre. Dette er for å hinder at Scheme -- ved å følgja evalueringsreglene forklart ovanfor -- evaluerer symbolet og lagar ei liste av funksjonsobjektet for addisjon i staden for berre symbolet. Når ein funksjon vert kalla, er det resultatet av det siste uttrykket i funksjonskoda som er returverdien for funksjonen. Sidan lista lag-addisjon returnerer er skrive som eit uttrykk (med funksjonsnamnet først), kan det evaluerast:

 (eval (lag-addisjon 1 2 3))
 => 6

eval er Scheme-interpreteret. Det vert kalla med resultatet av å kalla lag-addisjon, som er lista (+ 1 2 3), som argument. Svaret vi får er 6.

I Scheme er funksjonar primitive datatypar, akkurat som tal, strengar, symbol, osb. Det tyder at funksjonar kan sendast til andre funksjonar som argument, og funksjonar kan returnera andre funksjonar som returverdi. Det siste tilfelet, ein funksjon som vert returnert av ein annan funksjon, vert kalla ein closure. Ein closure har enno tilgjenge til variablane som var bunde i funksjonen som returnerte henne. Dette gjer at closures kan brukast som objekt (i objektorientert samanheng), med lokale variablar. Dette kan illustrerast med følgjande døme:

 (define (lag-person navn alder)     ; definer funksjonen lag-person
   (lambda (operasjon)               ; lag et funksjonsobjekt som tar et argument kalt operasjon
     (case operasjon                 ; se om operasjon er lik symbolet navn eller alder og returner henholdsvis navn og alder
       ('navn navn)
       ('alder alder))))

Her vert det definert ein funksjon, lag-person som returnerer eit funksjonsobjekt som representerer ein person. Dette vert gjort ved å laga ein funksjon med lambda. Funksjonen tek eit argument, som er ein operasjon som objektet skal utføra. Vi kan bruka det slik (pila «=>» viser kva kvart uttrykk evaluerer til):

 (define en-person (lag-person "Lars" 23))
 (en-person 'navn)
 => "Lars"
 (en-person 'alder)
 => 23

Dette viser berre litt av kva for abstraksjonar og høve closures gir.

Kjelder endre

Bakgrunnsstoff endre

Scheme-implementasjonar