Scheme is een minimalistische dialect van de Lisp-programmeertaal, ontworpen om een functionele programmeerstijl te vergemakkelijken terwijl het een eenvoudige en schone syntaxis bevordert. Het kenmerkt zich door het gebruik van haakjes, een krachtig macrosysteem en een sterke nadruk op recursie en first-class procedures. De taal moedigt een functioneel programmeerparadigma aan en ondersteunt meerdere programmeertechnieken, waaronder functioneel, imperatief en logisch programmeren.
Scheme werd in de jaren 1970 gecreëerd door Gerald Jay Sussman en Guy L. Steele Jr. aan het Massachusetts Institute of Technology (MIT). Het werd ontwikkeld als een poging om de oorspronkelijke Lisp-taal te vereenvoudigen en te verbeteren, die in de loop der tijd complex was geworden. De motivatie achter Scheme was om een taal te creëren die gemakkelijker te implementeren en te onderwijzen zou zijn, terwijl deze nog steeds krachtig genoeg was om complexe ideeën uit te drukken.
In de loop der jaren heeft Scheme verschillende herzieningen en standaardisatie-inspanningen ondergaan. De meest opmerkelijke van deze standaarden zijn de RnRS (Revisedn Reports on the Algorithmic Language Scheme), die de taal en zijn functies formaliseerden. De Scheme-gemeenschap heeft sindsdien de taal verder ontwikkeld, wat heeft geleid tot nieuwere standaarden zoals R6RS en R7RS, die nieuwe functies en verbeteringen introduceerden.
Op dit moment blijft Scheme populair in academische omgevingen, vooral in het computerwetenschappenonderwijs, vanwege zijn elegantie en schone syntaxis. De invloed ervan is te zien in veel moderne programmeertalen en -paradigma's. Scheme wordt vaak geassocieerd met de functionele programmeergemeenschap en heeft talen zoals Clojure en Racket geïnspireerd, die voortbouwen op zijn principes en zijn mogelijkheden uitbreiden.
Scheme gebruikt een volledig haakjes-syntaxis waarbij code in prefixnotatie wordt geschreven; bijvoorbeeld, een optelling wordt geschreven als (+ 1 2)
.
Functies in Scheme zijn first-class citizens, wat betekent dat ze als argumenten kunnen worden doorgegeven of uit andere functies kunnen worden geretourneerd. Voorbeeld:
(define (make-adder x)
(lambda (y) (+ x y)))
(define add5 (make-adder 5))
(add5 10) ; retourneert 15
Scheme ondersteunt tail call optimalisatie, waardoor functies het huidige stackframe kunnen hergebruiken voor recursieve aanroepen die in tail-positie plaatsvinden, waardoor stack overflow wordt voorkomen.
Scheme staat het gebruik van continuaties toe, waardoor programmeurs de huidige staat van de berekening kunnen vastleggen en de controleflow kunnen manipuleren:
(call-with-current-continuation
(lambda (exit)
(exit 'done)))
Scheme heeft een krachtig macrosysteem waarmee programmeurs syntactische extensies kunnen creëren. Een eenvoudige macro kan als volgt worden gedefinieerd:
(define-syntax my-if
(syntax-rules ()
((_ test then else)
(if test then else))))
Scheme ondersteunt gegevensabstractie via structuren zoals lijsten, paren en hogere-orde functies, waardoor efficiënte manipulatie van gegevens mogelijk is.
Scheme maakt gebruik van lexicale scoping, waarbij de scope van een variabele wordt bepaald door de fysieke locatie in de broncode. Dit leidt tot voorspelbaar gedrag met betrekking tot variabele bindingen.
Scheme is dynamisch getypeerd, waardoor variabelen waarden van elk type kunnen bevatten zonder dat expliciete typeverklaringen nodig zijn.
Als een Lisp-dialect is Scheme geoptimaliseerd voor lijstverwerking, waardoor het eenvoudig is om bewerkingen op lijsten uit te voeren:
(define my-list (list 1 2 3 4))
(car my-list) ; retourneert 1
(cdr my-list) ; retourneert (2 3 4)
Scheme-functies kunnen recursief worden gedefinieerd met tail-positie om het geheugenverbruik en de prestaties te optimaliseren. Bijvoorbeeld:
(define (factorial n)
(define (fact-helper n acc)
(if (zero? n)
acc
(fact-helper (- n 1) (* n acc))))
(fact-helper n 1))
Scheme-toepassingen kunnen over het algemeen worden gebouwd met behulp van de runtime-omgeving van de gekozen implementatie. In Racket kun je bijvoorbeeld de opdrachtregeltool racket
gebruiken om .rkt
-bestanden uit te voeren of ze te compileren naar uitvoerbare bestanden.
Er zijn verschillende compilers en interpreters beschikbaar voor Scheme, waaronder:
Scheme wordt veel gebruikt in de academische wereld voor het onderwijzen van programmeerprincipes en als hulpmiddel voor onderzoek in de computerwetenschappen. Het heeft toepassingen in kunstmatige intelligentie, taalontwerp en scripting, evenals in verschillende domeinen die symbolische berekeningen of complexe gegevensmanipulatie vereisen.
Beide talen ondersteunen een functionele programmeerstijl, maar Python legt de nadruk op leesbaarheid en eenvoud, terwijl Scheme zich richt op krachtige en beknopte expressies. De haakjesrijke syntaxis van Scheme kan als een obstakel voor beginners worden gezien, terwijl de inspringing-gebaseerde syntaxis van Python over het algemeen toegankelijker is.
JavaScript ondersteunt ook functioneel programmeren en heeft first-class functies. JavaScript heeft echter een prototype-gebaseerd objectensysteem, terwijl Scheme vertrouwt op functionele paradigma's. Het macrosysteem van Scheme biedt mogelijkheden die niet aanwezig zijn in JavaScript.
C is een low-level, statisch getypeerde taal die is ontworpen voor systeemprogrammering, terwijl Scheme high-level en dynamisch getypeerd is. C richt zich op prestaties en hardware-niveau manipulatie, terwijl Scheme de nadruk legt op abstractie en theoretische aspecten van berekening.
Beide talen zijn geworteld in functioneel programmeren. Haskell gebruikt een strikter type-systeem en ondersteunt lazy evaluation, terwijl Scheme's dynamische typing en eager evaluation meer flexibele programmering mogelijk maken ten koste van enige prestaties.
Ruby is een objectgeoriënteerde programmeertaal die functionele programmeerfuncties mengt, terwijl Scheme puur functioneel is. De syntaxis van Ruby is uitgebreider, terwijl de eenvoud van Scheme voortkomt uit zijn minimalistische ontwerp.
De vertaling van Scheme-code naar andere talen kan worden vergemakkelijkt door tools die zowel de syntaxis van Scheme als de doeltaal begrijpen. Enkele nuttige tools voor bron-naar-bron codevertaling zijn:
Chicken Scheme biedt een compiler die Scheme-code naar C kan vertalen, waardoor het mogelijk is om C-bibliotheken te benutten en efficiënte uitvoerbare bestanden te maken.
Racket, een afgeleide van Scheme, heeft ingebouwde mogelijkheden voor het transformeren en compileren van code naar JavaScript, waardoor het gemakkelijker wordt om webtoepassingen te implementeren.
Gambit Scheme kan Scheme-code compileren naar C en native uitvoerbare bestanden, wat efficiënte vertaalopties biedt voor prestatiekritische toepassingen.