Racket (Programmiersprache)
Racket ist eine multi-paradigmatische Programmiersprache aus der Lisp-Scheme-Familie. Sie ist Nachfolger des Scheme-Dialekts PLT Scheme. Racket besitzt eine integrierte Entwicklungsumgebung (IDE), DrRacket, und eine umfangreiche Bibliothek.
Racket | |
---|---|
Basisdaten | |
Paradigmen: | multi-paradigmatisch |
Erscheinungsjahr: | 2010 (PLT Scheme 1994) |
Designer: | PLT Inc. |
Entwickler: | PLT Inc. |
Aktuelle Version | 8.13[1] (16. Mai 2024) |
Typisierung: | dynamisch, statisch |
Dialekte: | Typed Racket, Lazy Racket, Scribble, FrTime |
Standardisierungen: | R5RS, R6RS |
Beeinflusst von: | Scheme |
Betriebssystem: | Linux, macOS, FreeBSD, Windows |
Lizenz: | LGPL, MIT, Apache |
racket-lang.org |
2018 erhielt Racket den Programming Languages Software Award von ACM SIGPLAN. In der Laudatio wurde Rackets singuläre Bedeutung sowohl für Forschung als auch Lehre von Programmiersprachen über rund zwanzig Jahre hervorgehoben.[2] Racket ist eine Programmiersprache zur Erzeugung und Erweiterung (Language Extensibility) von Programmiersprachen. Es spielt eine wichtige Rolle in der Forschung zu rekursiven Modulen 1. Klasse, Gradual Typing, Functional Reactive Programming und Kontrakten höherer Ordnung. DrRacket fand Verbreitung in Einführungskursen zu Programmiersprachen und bei neueren Lehrbüchern.[3][4]
Racket ist Open-Source-Software unter GNU Lesser General Public License bzw. MIT-Lizenz und Apache-Lizenz.[5]
Code-Beispiele
BearbeitenDas klassische Hallo-Welt-Programm:
#lang racket/base
"Hello, World!"
Quadrieren einer Liste in der Basissprache racket/base
(dynamische Typisierung):
#lang racket/base
(define liste '(1 2 3 4 5)) ; Typ der Liste wird zur Laufzeit ermittelt
(define (quadrieren x) ; Keine Typfestlegung der Funktion "quadrieren"
(* x x)) ; Fehler werden erst zur Laufzeit festgestellt
(displayln (map quadrieren liste)) ; (1 4 9 16 25)
Quadrieren einer Liste im Dialekt typed/racket
, der statische Typisierung unterstützt:[6]
#lang typed/racket
(define liste : (Listof Integer) '(1 2 3 4 5)) ; Typfestlegung der Liste
(: quadrieren (-> Integer Integer)) ; Typfestlegung der Funktion "quadrieren"
(define (quadrieren x)
(* x x))
(displayln (map quadrieren liste)) ; (1 4 9 16 25)
Durch Nutzung von typed/racket
zeigt DrRacket in der REPL (Read-Eval-Print-Loop, eine Art Eingabeaufforderung) Typinformationen an:
> liste
- : (Listof Integer)
'(1 2 3 4 5)
> quadrieren
- : (-> Integer Integer)
#<procedure:quadrieren>
Ausschnitt einer Markdown-Vorlagen-Datei cv.md.pp
für das Textsatzsystem Pollen
:[7]
#lang pollen
◊(define beruf "Arzt")
◊(define wohnort "Berlin")
◊(define kinder (* 2 2))
Mein Name ist _Max_ _Mustermann_,
* ich bin von Beruf ◊beruf
* wohne in ◊wohnort
* habe ◊kinder Kinder
In der entsprechenden Markdown-Datei cv.md
steht dann
Mein Name ist _Max_ _Mustermann_,
* ich bin von Beruf Arzt
* wohne in Berlin
* habe 4 Kinder
Generierung eines Sierpinski-Dreiecks:
#lang racket
(require 2htdp/image)
(let sierpinski ([n 8])
(if (zero? n)
(triangle 2 'solid 'red)
(let ([t (sierpinski (- n 1))])
(freeze (above t (beside t t))))))
Die grafische Ausgabe des Sierpinski-Dreiecks ist in der Abbildung rechts dargestellt.
Sprachorientierte Programmierung
BearbeitenIn Racket steht die Erzeugung von domänenspezifischen Sprachen oder Allzwecksprachen im Mittelpunkt.
Jede Sprache, die in Racket implementiert wird, muss zwei Bestandteile aufweisen: einen sog. Reader, der vereinfacht ausgedrückt den Quellcode einliest, und einen sog. Expander, der den Quellcode interpretiert.[8]
Für eine Sprache minilang
(für Mini-Sprache) könnte der Reader beispielsweise wie folgt aussehen:
#lang racket
(define (read-syntax path port)
(define code-zeilen (port->lines port)) ; Einlesen des Codes
#`(module module-name racket/base ; Modul als Syntax-Objekt anlegen (Expander racket/base)
(for ([code-zeile (list #,@code-zeilen)]) ; Code-Zeilen einspleißen und for-Schleife anlegen
(writeln code-zeile)))) ; Code-Zeilen auf Terminal ausgeben
(provide read-syntax) ; read-syntax zur Verfügung stellen
Dieser liest den Code zeilenweise ein und erzeugt ein Syntax-Objekt (mit #`
), in das die Zeilen als Strings in eine Liste in der for
-Schleife eingespleißt werden (#,@
), d. h. sie werden unter Eliminieren einer Listenebene eingefügt: `(list 1 2 ,@(list 3 4) 5) => (list 1 2 3 4 5)
. Danach werden diese der Reihe nach an das Terminal ausgegeben. Der Reader muss in jedem Fall die Funktion read-syntax
zur Verfügung stellen.
Nun speichert man den Code in einer Datei minilang.rkt
und legt anschließend eine Datei testminilang.rkt
im selben Verzeichnis mit dem Inhalt
#lang reader "minilang.rkt" ab cd ef
an. Diese kann man nun in DrRacket (oder mit racket testminilang.rkt
) ausführen und es werden alle Zeilen am Terminal ausgegeben.
"" "" "ab" "cd" "ef"
Hier ist zu beachten, dass der Reader nur das Syntax-Objekt zurückgibt, der Code selbst aber erst später ausgeführt wird. Wenn die Strings der Code-Zeilen modifiziert werden sollen (beispielsweise um Vorformatierungen vorzunehmen), dann kann man das Einspleißen im Syntax-Objekt verändern. Dies könnte zum Beispiel so aussehen:
#lang racket
(define (read-syntax path port)
(define code-zeilen (port->lines port))
(define veraenderte-code-zeilen
(map (lambda (s) `(veraendere ,s)) ; Strings mit Präfix "veraendere" versehen
code-zeilen))
#`(module module-name "minilang2-expander.rkt" ; Eigenen Expander verwenden
(for ([code-zeile (list #,@veraenderte-code-zeilen)])
(writeln code-zeile))))
(provide read-syntax)
Hier werden die Strings aus den Code-Zeilen mit einem Präfix „veraendere“ versehen und als Liste von Datums-Objekten gespeichert. Diese Liste wird in die Schleife innerhalb des Syntax-Objekts eingespleißt. Das bedeutet, dass jeder String mit einer Funktion veraendere
aufgerufen wird. Diese wird im Expander minilang2-expander.rkt
implementiert (der auch wieder im selben Verzeichnis gespeichert wird).
#lang racket
(define (veraendere s) ; Funktion String -> String
(string-append "veraendert " s)) ; "string" -> "veraendert string"
(provide veraendere) ; Stelle Funktion "veraendere" zur Verfügung
Führt man das testminilang.rkt
von oben mit dem veränderten Reader aus, erhält man die Ausgabe:
"veraendert " "veraendert " "veraendert ab" "veraendert cd" "veraendert ef"
Der gezeigte Reader erzeugt eine Terminalausgabe, wenn man die Quelldatei testminilang.rkt
ausführt. Es ist aber auch möglich, mit Hilfe von require
solche Module in anderen Code einzubinden. Dazu könnte man im vorliegenden Beispiel das Syntax-Objekt wie folgt modifizieren:
#`(module module-name "minilang2-expander.rkt"
(define export-daten (list #,@veraenderte-code-zeilen))
(provide export-daten))
Hier werden die erzeugten Strings in einer Liste export-daten
gespeichert und zur Verfügung gestellt. Bindet man nun das Programm testminilang.rkt
wie folgt ein,
#lang racket
(require "testminilang.rkt")
(writeln export-daten) ; ("veraendert " "veraendert " "veraendert ab" "veraendert cd" "veraendert ef")
kann man auf export-daten
zugreifen, die hier auf das Terminal ausgegeben werden.
In den vorangegangenen Beispielen ist eine Art Syntax und Semantik implementiert worden. Oft wird in Racket die Syntax nur leicht modifiziert (weiterhin sog. S-Expressions), aber neue Funktionalität mit Hilfe von Makros hinzugefügt. Diese dienen dazu, Syntax-Objekte ineinander zu transformieren. Hier spielen auch die verschiedenen Phasen (Phase 0 – Laufzeitphase, Phasen – Übersetzungsphasen) in Racket eine Rolle, in die man ebenfalls eingreifen kann.
Wenn man neue Syntax (abseits von S-Expressions) einführt, ist es oft einfacher, das Einlesen im Reader in mehrere Schritte zu unterteilen. Dies sind im Einzelnen das Zerlegen des Eingabestrings in irreduzible Elemente der Sprache (Tokenizer, Lexer) und das Prüfen und Umwandeln dieser Elemente in das Syntax-Objekt entsprechend einer Grammatik (Parser). Dies wird in der zitierten Literatur ausführlich dargestellt.
Weblinks
Bearbeiten- Offizielle Website (englisch)