Aus Linux-Magazin 11/2006

Die Skriptsprache Boo

© photocase.com

Lange Zeit mussten Mono- und Dotnet-Entwickler auf eine maßgeschneiderte Skriptsprache warten. Seit kurzem füllt Boo diese Lücke. Als Mischung aus Python, C# und einer Prise funktionaler Programmierung eignet sie sich für Prototyping ebenso wie für größere Projekte.

Rodrigo Barreto de Oliveira war frustriert. Keine der vorhandenen Programmiersprachen eignete sich für sein anstehendes Projekt. In Python vermisste er eine strenge statische Typprüfung und eine gute Anbindung an das Dotnet-Framework. Die Alternative C# hat zwar eine nette Syntax, verursacht aber eine Menge Tipparbeit.

Also entwarf er eine eigene Skriptsprache (Abbildung 1) mit einem kleinen Pacman-Gespenst als Logo. Sie sollte auf Python basierten, sich aber auf die Common Language Infrastructure (CLI) und das Dotnet-Framework stützen. Das Ergebnis würzte er noch mit ein paar Konzepten aus C# und Ruby.

Heraus kam die objektorientierte Sprache Boo [1], die Dank der Mono-Umgebung [2] auch unter Linux zeigt, was in ihr steckt. Weil das obligatorische Hello World mit der Zeile »print(“Hallo Welt”)« etwas zu trivial ist, zeigt Listing 1 eine GTK#-Variante (Abbildung 2).

Boo steckt einen zusammenhängenden Codeblock nicht wie vielfach üblich in geschweifte Klammern, sondern arbeitet wie Python mit Einrückungen. Nicht einmal ein »end« als Abschluss ist erlaubt. Ebenso sind Semikolons am Ende einer jeden Zeile verpönt und nur in Zweifelsfällen angebracht. Im Gegenzug erlaubt Boo nicht nur die Python-Kommentare mit einer Raute #, sondern auch die von C und C++ bekannten Varianten mit »/*« und »//«.

Abbildung 1: Auf der übersichtlichen Homepage des Boo-Projekts findet sich neben umfangreichen Codebeispielen auch eine sehr gute Einführung in die Sprache.

Abbildung 1: Auf der übersichtlichen Homepage des Boo-Projekts findet sich neben umfangreichen Codebeispielen auch eine sehr gute Einführung in die Sprache.

Abbildung 2: Das Hallo-Welt-Programm in der GTK#-Fassung: Kein Button, dafür nur sieben Zeilen Code.

Abbildung 2: Das Hallo-Welt-Programm in der GTK#-Fassung: Kein Button, dafür nur sieben Zeilen Code.

Listing 1: Hello
GTK#

01 import Gtk
02 
03 Application.Init()
04 window = Window("Hallo Welt", DefaultWidth: 300, DefaultHeight: 200)
05 
06 window.DeleteEvent += def():
07     Application.Quit ()
08 
09 window.ShowAll()
10 Application.Run()

Typisierte Variablen

Im Unterschied zu Python verwendet Boo normalerweise statische, typisierte Variablen, man muss also ihren Typ vor dem ersten Einsatz angeben. Das hat den Vorteil, dass der Compiler schon zur Übersetzungszeit fehlerhafte Zuweisungen erkennt und direkt anmahnt. Damit der Programmierer nicht wie in anderen Sprachen mit den Typen jonglieren muss, denkt der Compiler von Boo aber mit und leitet den korrekten Typ automatisch nicht vollständig qualifizierter Variablen ab (Type Inference). Beispiel:

a as int // a ist ein Integer
a=3
b=a
print b

Damit ist klar, dass »b« nur eine Zahl sein kann. Sogar die erste Zeile wäre im Beispiel überflüssig, da der Compiler aufgrund der zugewiesenen »3« genügend Informationen besitzt. Nur wenn diese direkte Ableitung nicht funktioniert, ist die Angabe des Typs zwingend.

Recht einfach gestaltet sich auch der Umgang mit Listen, Arrays und Hashtables. Um beispielsweise eine Liste »a« mit den Zahlen 1 bis 5 zu füllen, könnte man entweder mit »a = List(range(1,5))« direkt ein Listenobjekt erstellen oder die Kurznotation verwenden:

a= [1,2,3,4, 'fuenf']
print(a[:3])

Die eingebaute Funktion »range()« liefert Abschnitte aus dem angegebenen Bereich. Wie das Beispiel zeigt, darf man sogar gemischte Typen in die Liste einfügen. Die Kurznotation beim »print«-Befehl bezeichnet die Boo-Terminologie als Slicing-Operation, die in diesem Fall die ersten drei Werte aus »a« zurückliefert. Als zusätzliches Schmankerl unterstützt Boo reguläre Ausdrücke, darunter sogar den »Match«-Operator »=~« aus Perl:

beispieltext = "Dies ist ein Test"
if beispieltext =~ @/Te.t/:
    print("ist enthalten")

Dieses Beispiel fahndet im »beispieltext« nach der Zeichenkette »Test«, wobei zwischen »e« und »t« auch ein beliebiger anderer Buchstabe auftauchen darf. Der reguläre Ausdruck steckt zwischen den Zeichen »@/« und »/«.

Funktionen im Python-Stil

Bei der Definition von Funktionen fühlen sich Python-Kenner sofort zu Hause:

def Hallo(name as string):
    return "Ihr Name: ${name}"
print Hallo("Klaus")

Boo behandelt Funktionen als Objekte erster Klasse. Das Konzept der so entstehenden First Class Functions stammt ursprünglich aus den funktionalen Programmiersprachen. Mit derartigen Funktionen darf der Programmierer alles anstellen, was die Programmiersprache hergibt. So kann er sie ruhigen Gewissens in eine Variable stecken oder als Argumente an andere Funktionen übergeben. Selbstverständlich darf er sie auch als Ergebnis zurückliefern:

def funktion1(item as int):
     return item // mache nichts
def funktion2():
     return funktion1

Boo erlaubt es ihm sogar, eine Funktion innerhalb einer anderen zu definieren:

def Aussen():
     Innen = def():
          print("innen wurden aufgerufen")
     Innen()
Aussen()

Damit ist es nur noch ein kleiner Schritt zu den so genannten Closures. Dieses Konzept hat Boo ebenfalls aus der funktionalen Programmierung geerbt. Unter einem Closure, Block oder Funktionsabschluss ist ein Stück Programmcode beziehungsweise ein Funktionsrumpf zu verstehen, der die lokalen Variablen seiner umgebenden Funktion sehen und verwenden kann.

Closures mal zwei

Boo kennt bei Closures zwei Syntaxformen: Entweder die oben gezeigte Variante als Block (Block Based Synatx) oder durch geschweifte Klammern eingerahmt (Brace Based Syntax), wie in Listing 2 zu sehen ist. Hier stecken die Closures in den geschweiften Klammern und benutzen in ihrem Rumpf jeweils das »a« aus der sie umschließenden Funktion (Zeilen 3 und 4). Der Aufruf der Closures zählt auch dann noch hoch, wenn die Umgebung – und damit auch die Variable – eigentlich gar nicht mehr existiert.

Listing 2:
Block-Syntax

01 def funktion():
02      a = 0 # neue Variable
03      inc = { a+=1 }
04      show = { return a }
05      return inc, show
06 
07 i,s = funktion()
08 print(s())
09 i()
10 print(s())

Wenn in Boo schon das Zurückgeben einer Funktion erlaubt ist, dann sind auch so Hirn-zermarternde Ausdrücke wie der folgende möglich:

potenz = { x as int | return { y as int | return x ** y }} // ** steht für die Potenz

Dabei nimmt »potenz« eine Closure-Funktion auf, die wiederum selbst eine neue Funktion zurückliefert (die Parameterdeklaration ist vom Funktionsrumpf durch »|« getrennt). Die ausgespuckte Funktion erwartet aber ihrerseits wieder eine Eingabe. Der Aufruf dieses Gespanns erfolgt dann beispielsweise per »potenz(5)(2)«. Hier wird also zunächst die 5 an die Funktion von »potenz« übergeben, die ihrerseits eine Funktion zurückliefert (genauer gesagt die Funktion »{ y as int | return 5 ** y }«). Ihr wird dann letztlich die 2 übergeben, dann das endgültige Ergebnis ausgeliefert.

Unter dem Strich wurde also eine einzige Funktion mit zwei Argumenten durch zwei Funktionen mit nur einem Argument ersetzt. Dieses Konzept heißt nach seinen Erfindern Currying oder Schönfinkeln.

Generatoren

Neben Closures unterstützt Boo so genannte Generatoren. Solche Funktionen produzieren eine Folge von Ergebnissen. Dabei erzeugen sie jedoch nicht einfach eine fertige Liste, sondern berechnen das nächste Element der Folge erst dann, wenn es tatsächlich gebraucht wird. In Boo heißen derartige Funktionen Generator-Methoden (Listing 3).

Listing 3:
Generatoren

01 def MyGenerator():
02     i = 1
03     yield i
04     for x in range(10):
05         i += 2
06         yield i
07 
08 print(List(MyGenerator()))

Die Generator-Methode »MyGenerator« läuft bis zum nächsten »yield«, wirft den Inhalt der dort angegebenen Variablen raus und stoppt. Sobald der Aufrufer (im Beispiel »List«) den nächsten Wert benötigt, setzt die Funktion ihre Arbeit genau an dieser Stelle fort. Da der Generator den nächsten Wert nur bei Bedarf erzeugt, ist im Idealfall weniger Speicherplatz nötig – schließlich kostet eine Zahl weniger als eine komplette Liste. Das macht sich besonders bei der Steuerung von Iterationen bemerkbar, zum Beispiel in For-Schleifen.

Neben den Generator-Methoden kennt Boo noch die Generator Expressions. Sie funktionieren genau wie ihre Funktions-Kollegen, bestehen aber nur noch aus einer For-Schleife. Das erspart die explizite Definition einer Funktion. Dies wiederum erlaubt vor allem die kompakte Beschreibung von Mengen, wie zum Beispiel “Alle ungeraden Zahlen von 1 bis 10 die anschließend noch mit 2 multipliziert wurden”:

ungerademalzwei = i*2 for i in range(10) if i%2
for x in ungerademalzwei:
        print x

Auch hier zieht Boo die nächste Zahl erst dann aus der Menge, wenn es in der zweiten For-Schleife die Abbruchbedingung prüft. Wer die Generator Expression noch in einer Liste verpackt, erhält die List Generators, die schnell eine Liste mit ausgewählten Objekten füllen:

ungerademalzweiliste = [i*2 for i in range(10) if i%2]

Im Gegensatz zu C# kommt Boo bei Bedarf auch vollständig ohne Klassen aus. Wer mag, darf imperativ oder wie in C nur mit Funktionen programmieren. Hinter den Kulissen arbeitet Boo jedoch objektorientiert. Sogar jede Funktion darf man als Objekt betrachten. Die Anweisung: »print(“Hello World”)« ist gleichbedeutend mit »print.Invoke(“Hello World”)«. Wird ».Invoke« durch ».BeginInvoke« ersetzt, startet der Funktionsrumpf gleich in einem eigenen Thread:

def Berechnung():
     for x in range(10):
          Langeberechnung(x)
Berechnung.BeginInvoke()

Da bekommt sicherlich so mancher C-Programmierer feuchte Augen. Selten war Multithreading so einfach zu programmieren.

Attribute und Felder

Bei der Objektorientierung verfügt jedes Objekt über Methoden und Attribute. Um auf Letztere zuzugreifen, schreibt der Programmierer normalerweise für jedes von ihnen eine Methode zum Setzen (Set) und Auslesen (Get). Diese häufig wiederkehrende Aufgabe ist recht lästig, weshalb Boo einfach das Property-Konzept von C# übernahm. Boo unterscheidet zunächst zwischen Feldern (Fields) und Eigenschaften (Properties). Die Felder sind herkömmliche Variablen, die die Attribute des Objekts realisieren. Zusätzlich existieren noch die besagten Properties. Sie bilden eine interessante syntaktische Alternative zu den Get- und Set-Methoden (Listing 4).

Listing 4:
Properties

01 class Stuhl:
02     Farbe as string:
03         get:
04             return _farbe
05         set:
06             _farbe = value
07 
08     _farbe as string
09 
10 holzstuhl = Stuhl()
11 holzstuhl.Farbe = 'Braun'

Der Zugriff auf das Attribut »Farbe« erfolgt dabei intuitiv über eine einfache Zuweisung. Hinter den Kulissen wird im Beispiel die Get-Methode aufgerufen und der übergebene Wert im Feld »_farbe« abgelegt (der Unterstrich steht für eine private Member-Variable). Dem Benutzer des Objekts bleiben diese Interna verborgen. Um weitere Tipparbeit einzusparen, erlaubt Boo noch eine Kurznotation:

class Stuhl:
    [Property(Farbe)]
    _farbe as string

Die zugehörigen Get- und Set-Methoden erzeugt Boo im Hintergrund.

Von Ruby hat sich Boo das so genannte Duck Typing abgeschaut. Dabei schaltet der Programmierer in seinem Code die statische Typprüfung für eine Variable ab und darf ihr somit ab diesem Zeitpunkt beliebige Objekte zuweisen:

d as duck // d darf ab sofort alles aufnehmen
d = 5 // ab hier ist d ein Integer...
d = "Hallo" // ... und ab hier ein String

Kurz gesagt: Wenn es also quakt wie eine Ente, aussieht wie eine Ente und sich bewegt wie eine Ente, dann behandle es so wie eine Ente – auch wenn etwas anderes draufsteht.

Übersetzung

Skripte kann Boo auf drei Arten ausführen. In jedem Fall muss der Programmierer jedoch zuvor die Mono-Umgebung einspielen und das Boo-Paket von [1] entpacken. Zunächst lässt sich ein Skript mit dem Compiler »booc.exe« kompilieren:

mono bin/booc.exe -out:hallo.exe hallo.boo

Das übersetzte Ergebnis des Skripts »hallo.boo« ist dann mit dem Kommando »mono hallo.exe« zu starten. Zusätzlich benötigte DLLs bindet der Parameter »-r« ein:

mono bin/booc.exe -r:gtk-sharp -out:hallogtk.exe hallogtk.boo

Bei der Weitergabe solcher Programme ist lediglich die Bibliothek »Boo.Lang.dll« aus dem »bin«-Verzeichnis mitzuliefern.

Neben dem Boo-Compiler liegt dem Paket der Interpreter »booi.exe« bei, der ein Boo-Skript direkt ausführt: »mono binbooi.exe hallo.boo«. Ergänzend gibt es »booish.exe«, eine interaktive Shell, in der sich analog zu dem Python-Pendant direkt Befehle eintippen lassen.

Schlussendlich lässt sich Boo-Code auch mit Hilfe des Boo-API nutzen. Mit ihren Dotnet-Objekten ist sogar ein Boo-Skript aus einem anderen heraus (Listing 5) zu starten.

Listing 5: Boo-Code mit dem API
ausführen

01 import Boo.Lang.Compiler
02 import Boo.Lang.Compiler.IO
03 import Boo.Lang.Compiler.Pipelines
04 
05 compiler = BooCompiler()
06 compiler.Parameters.Input.Add(StringInput("<script>", "print('Hello!')"))
07 compiler.Parameters.Pipeline = Run()
08 compiler.Run()

Schöne Sprache für die CLI

Rodrigo Barreto de Oliveira hat Boo von Anfang an als erweiterbare Sprache ausgelegt, daher dürfen Programmierer beispielsweise auch selbst definierte Makros ergänzen. Eine Hand voll dieser nützlichen Helfer bringt Boo überdies bereits mit. Dazu gehört das Makro »assert«, das eine Bedingung überprüft und im negativen Fall eine Exception mit einer Meldung wirft. Im folgenden Beispiel muss die Variable »a« einen Wert größer 5 besitzen:

assert a>5, "Fehler: a nicht größer als 5"

Hinter jedem Makro steckt nichts anderes als ein schnödes Dotnet-Objekt, das eine bestimmte Signatur aufweist. Damit ist es Boo sogar egal, in welcher Sprache ein Makro geschrieben wurde. Die Erweiterbarkeit geht sogar noch einen Schritt weiter: Der Compiler von Boo ist als modulare Pipeline ausgelegt. Wer mag, darf sich in den Übersetzungsprozess einhängen und darin eigene Aktionen ausführen.

Mit Boo erhält die CLI endlich eine moderne Skriptsprache, deren Kompilate erfreulich schnell zu Werke gehen. Damit eignet sie sich nicht nur für die Entwicklung von Prototypen, sondern Dank ihres modularen Aufbaus und schlanken Interpreters auch zur Einbettung in Dotnet- und Mono-Programme anderer Sprachen. Die syntaktische Verwandtschaft zu Python erleichtert zudem den Umstieg. Derzeit in Vorbereitung ist eine Unterstützung für Dotnet 2.0, deren Generics gerade im Fokus der Boo-Entwicklung stehen. (ofr)

Infos

[1] Boo: [http://boo.codehaus.org]

[2] Mono: [http://mono-project.com]

DIESEN ARTIKEL ALS PDF KAUFEN
EXPRESS-KAUF ALS PDFUmfang: 4 HeftseitenPreis €0,99
(inkl. 19% MwSt.)
LINUX-MAGAZIN KAUFEN
EINZELNE AUSGABE Print-Ausgaben Digitale Ausgaben
ABONNEMENTS Print-Abos Digitales Abo
TABLET & SMARTPHONE APPS Readly Logo
E-Mail Benachrichtigung
Benachrichtige mich zu:
0 Kommentare
Älteste
Neuste Beste Bewertung
Inline Feedbacks
Alle Kommentare anzeigen
Nach oben