Scheme è un dialetto minimalista del linguaggio di programmazione Lisp, progettato per facilitare uno stile di programmazione funzionale promuovendo una sintassi semplice e pulita. È caratterizzato dall'uso delle parentesi, da un potente sistema di macro e da un forte accento sulla ricorsione e sulle procedure di prima classe. Il linguaggio incoraggia un paradigma di programmazione funzionale e supporta molteplici tecniche di programmazione, tra cui programmazione funzionale, imperativa e logica.
Scheme è stato creato negli anni '70 da Gerald Jay Sussman e Guy L. Steele Jr. al Massachusetts Institute of Technology (MIT). È stato sviluppato come un tentativo di semplificare e migliorare il linguaggio Lisp originale, che era diventato complesso nel tempo. La motivazione dietro Scheme era quella di creare un linguaggio che fosse più facile da implementare e insegnare, pur essendo abbastanza potente da esprimere idee complesse.
Nel corso degli anni, Scheme ha subito varie revisioni e sforzi di standardizzazione. I più noti di questi standard sono i RnRS (Revisedn Reports on the Algorithmic Language Scheme), che hanno formalizzato il linguaggio e le sue caratteristiche. La comunità di Scheme ha continuato a sviluppare il linguaggio, portando a nuovi standard come R6RS e R7RS, che hanno introdotto nuove funzionalità e miglioramenti.
Attualmente, Scheme rimane popolare negli ambienti accademici, in particolare nell'istruzione informatica, grazie alla sua eleganza e sintassi pulita. La sua influenza può essere vista in molti linguaggi di programmazione e paradigmi moderni. Scheme è spesso associato alla comunità della programmazione funzionale e ha ispirato linguaggi come Clojure e Racket, che si sono basati sui suoi principi e hanno esteso le sue capacità.
Scheme utilizza una sintassi completamente parentetica in cui il codice è scritto in notazione prefissa; ad esempio, un'operazione di addizione è scritta come (+ 1 2)
.
Le funzioni in Scheme sono cittadini di prima classe, il che significa che possono essere passate come argomenti o restituite da altre funzioni. Esempio:
(define (make-adder x)
(lambda (y) (+ x y)))
(define add5 (make-adder 5))
(add5 10) ; restituisce 15
Scheme supporta l'ottimizzazione delle chiamate di coda, consentendo alle funzioni di riutilizzare il frame di stack corrente per le chiamate ricorsive che si verificano in posizione di coda, prevenendo il sovraccarico dello stack.
Scheme consente l'uso delle continuazioni, permettendo ai programmatori di catturare lo stato attuale del calcolo e manipolare il flusso di controllo:
(call-with-current-continuation
(lambda (exit)
(exit 'done)))
Scheme ha un potente sistema di macro che consente ai programmatori di creare estensioni sintattiche. Una semplice macro può essere definita come segue:
(define-syntax my-if
(syntax-rules ()
((_ test then else)
(if test then else))))
Scheme supporta l'astrazione dei dati attraverso strutture come liste, coppie e funzioni di ordine superiore, consentendo una manipolazione efficiente dei dati.
Scheme impiega lo scoping lessicale, dove l'ambito di una variabile è determinato dalla sua posizione fisica nel codice sorgente. Questo porta a un comportamento prevedibile riguardo ai binding delle variabili.
Scheme è tipizzato dinamicamente, consentendo alle variabili di contenere valori di qualsiasi tipo senza la necessità di dichiarazioni di tipo esplicite.
Essendo un dialetto di Lisp, Scheme è ottimizzato per l'elaborazione delle liste, rendendo facile eseguire operazioni su di esse:
(define my-list (list 1 2 3 4))
(car my-list) ; restituisce 1
(cdr my-list) ; restituisce (2 3 4)
Le funzioni Scheme possono essere definite ricorsivamente in posizione di coda per ottimizzare l'uso della memoria e le prestazioni. Ad esempio:
(define (factorial n)
(define (fact-helper n acc)
(if (zero? n)
acc
(fact-helper (- n 1) (* n acc))))
(fact-helper n 1))
Le applicazioni Scheme possono generalmente essere costruite utilizzando l'ambiente di runtime dell'implementazione scelta. Ad esempio, in Racket, puoi utilizzare lo strumento da riga di comando racket
per eseguire file .rkt
o compilarli in eseguibili.
Sono disponibili diversi compilatori e interpreti per Scheme, tra cui:
Scheme è ampiamente utilizzato in ambito accademico per insegnare i principi di programmazione e come strumento per la ricerca in informatica. Ha applicazioni nell'intelligenza artificiale, nella progettazione di linguaggi e nello scripting, così come in vari domini che richiedono calcolo simbolico o manipolazione complessa dei dati.
Entrambi i linguaggi supportano uno stile di programmazione funzionale, ma Python enfatizza la leggibilità e la semplicità, mentre Scheme si concentra su espressioni potenti e concise. La sintassi ricca di parentesi di Scheme può essere vista come un ostacolo per i principianti, mentre la sintassi basata sull'indentazione di Python è generalmente più accessibile.
JavaScript supporta anch'esso la programmazione funzionale e ha funzioni di prima classe. Tuttavia, JavaScript ha un sistema di oggetti basato su prototipi, mentre Scheme si basa su paradigmi funzionali. Il sistema di macro di Scheme offre capacità non presenti in JavaScript.
C è un linguaggio di basso livello, tipizzato staticamente, progettato per la programmazione di sistema, mentre Scheme è di alto livello e tipizzato dinamicamente. C si concentra sulle prestazioni e sulla manipolazione a livello hardware, mentre Scheme enfatizza l'astrazione e gli aspetti teorici del calcolo.
Entrambi i linguaggi sono radicati nella programmazione funzionale. Haskell utilizza un sistema di tipi più rigido e supporta l'evaluazione pigra, mentre la tipizzazione dinamica di Scheme e l'evaluazione eager consentono una programmazione più flessibile a scapito di alcune prestazioni.
Ruby è un linguaggio di programmazione orientato agli oggetti che mescola caratteristiche di programmazione funzionale, mentre Scheme è puramente funzionale. La sintassi di Ruby è più verbosa, mentre la semplicità di Scheme deriva dal suo design minimalista.
La traduzione del codice Scheme in altri linguaggi può essere facilitata da strumenti che comprendono sia la sintassi di Scheme che il linguaggio di destinazione. Alcuni strumenti utili per la traduzione di codice da sorgente a sorgente includono:
Chicken Scheme fornisce un compilatore che può tradurre il codice Scheme in C, rendendo possibile sfruttare le librerie C e creare eseguibili efficienti.
Racket, un derivato di Scheme, ha capacità integrate per trasformare e compilare codice in JavaScript, rendendo più facile il deployment di applicazioni web.
Gambit Scheme può compilare codice Scheme in C e in eseguibili nativi, fornendo opzioni di traduzione efficienti per applicazioni critiche per le prestazioni.