Black Week in der Golem Karrierewelt

Spare bis zu 70% auf unsere Weiterbildungsangebote. Aktionszeitraum vom 22.11. - 30.11.2024.

Zu den Black Week Angeboten

Julia: Eine Programmiersprache nicht nur für die Wissenschaft

Julia: Eine Programmiersprache nicht nur für die Wissenschaft - Golem Karrierewelt

(Bild: Martin Wolf/Golem.de)

Von Tim Schürmann

Die noch relativ junge Programmiersprache Julia erzeugt flotte Binärprogramme und kommt vor allem bei der Verarbeitung von großen Datenmengen zum Einsatz. Sie glänzt mit einer einfachen Syntax und lässt sich auch außerhalb der Wissenschaft sehr gut einsetzen.

Die Programmiersprache Julia wird seit sieben Jahren Open Source entwickelt. Sie eignet sich durch ihre speziellen Eigenschaften hervorragend für die Verarbeitung großer Datenmengen und für wissenschaftliche Anwendungen. Ähnlich wie C, C++ und Python ist Julia aber auch als General Purpose Language ausgelegt. Mit ihr lassen sich also Anwendungen für fast alle Zwecke schreiben, so dass die Sprache auch außerhalb der Nische Wissenschaft gut aufgehoben ist.

In Julia geschriebene Programme laufen ähnlich schnell wie übersetzte C-Programme. Im Hintergrund nutzt Julia dafür das LLVM-Framework, das den eigentlichen nativen Programmcode erst zur Laufzeit erstellt (Just-in-Time, JIT). Die Referenzimplementierung aller Werkzeuge und Bibliotheken steht unter der freizügigen MIT-Lizenz.

Leicht verständliche Syntax

Die Syntax erinnert leicht an eine Mischung aus Lisp und Python. Julia unterstützt neben einer funktionalen auch eine rudimentäre objektorientierte Programmierung. Dabei setzt die Sprache stark auf das sogenannte Multiple-Dispatch-Konzept: Entwickler dürfen mehrere Funktionen mit dem gleichen Namen, aber unterschiedlichen Argumenten definieren. Julia wählt dann bei einem Aufruf automatisch die am besten passende Funktion.

Die Standardbibliothek umfasst unter anderem Funktionen zur Parallelverarbeitung, Netzwerkkommunikation und Profiling. Abschließend unterstützt Julia ähnlich wie Lisp die Metaprogrammierung, über die sich der Quellcode vor der Ausführung selbst modifizieren kann.

Analog zu vielen Skriptsprachen besitzt auch Julia einen interaktiven Interpreter, der den eingegebenen Quellcode direkt ausführt. Der Interpreter lässt sich mit Code aus Dateien und über die Standardeingabe füttern.

Einfache mathematische Notation

Julia bietet eine dynamische Typisierung, dank derer eine Variable beliebige Inhalte aufnehmen kann:

Die Variablennamen dürfen aus Unicode-Zeichen bestehen. Julia macht sich die Unicode-Unterstützung ebenfalls zunutze. So kann man einfach √4 schreiben, um die Wurzel aus 4 zu ziehen. Julia ermittelt den Typ einer Variablen automatisch im Hintergrund. Bei Bedarf kann man ihn aber auch mit dem Operator :: an Ausdrücken und Variablen explizit vorgeben.

Im Fall von x::UInt64 interpretiert Julia die Variable x als 64 Bit große, vorzeichenlose Integerzahl. Hängt der Operator an einem Ausdruck, prüft Julia, ob der zurückgelieferte Wert dem Typ entspricht. Im folgenden Beispiel muss das Ergebnis von 2+3 eine Integerzahl sein, andernfalls erzeugt Julia einen Fehler:

Einer Variablen darf man direkt eine Zahl voranstellen, wie etwa im Ausdruck 2x+3. Julia führt dann automatisch eine Multiplikation mit dem Variableninhalt durch. Des Weiteren lassen sich Vergleichsoperatoren wie < oder > verketten:

Dieser Ausdruck ist in den Augen von Julia true. Dank dieser Konventionen lassen sich recht elegant mathematische Formeln notieren.

Analog zu UInt64 und Int kennt Julia viele weitere Integer- und Gleitkommatypen, komplexe und rationale Zahlen und natürlich boolesche Werte. Zudem bindet Julia die GNU Multiple Precision Arithmetic Library (GMP) und die GNU MPFR Library ein, welche über die Typen BigInt und BigFloat eine effiziente Langzahlarithmetik ermöglichen.

Strings lassen sich per *-Operator schnell verketten. Umgekehrt kann man über entsprechende Indexangaben ein Zeichen oder einen Teil ausschneiden. Im folgenden Beispiel würde y den Text lo W enthalten:

Dank der eingebundenen PCRE-Bibliothek wertet Julia sogar Perl-kompatible reguläre Ausdrücke aus. Wie in Perl kann man zudem Variableninhalte in Strings einsetzen:

Funktionen und gute Argumente

Wie in vielen Skriptsprachen lassen sich Funktionen recht kompakt in Julia notieren:

Dazu gibt es obendrein die Kurzschreibweise f(x,y) = x + y. Julia liefert standardmäßig den Wert des letzten Ausdrucks in der Funktion zurück. Im Zweifelsfall kann man mit return einen bestimmten Wert zurückgeben. Mehrere Rückgabewerte sind ebenfalls möglich, die Julia in ein entsprechendes Tupel packt und dieses dann zurückgibt.

Tupel ergänzen die schon aus anderen Sprachen bekannten Arrays und können beliebige Werte speichern: x = (2.3, Hallo, 5+4). Der Zugriff erfolgt mit der Klammernotation, x[2]liefert beispielsweise Hallo.

Funktionen behandelt Julia als First-Class-Objekte. Sie lassen sich folglich Variablen zuweisen, als Argumente übergeben und als Rückgabewerte zurückliefern. Möglich sind auch anonyme Funktionen:

Für sie gibt es ebenfalls eine Kurzschreibweise, die für das Beispiel x -> 3x+2 lautet. Wie auch in anderen Sprachen werden anonyme Funktionen hauptsächlich dafür genutzt, um sie direkt als Argumente an eine andere Funktion zu übergeben.

Gute Argumente

Die Argumente von Funktionen lassen sich mit Standard-Werten vorbelegen: function f(a, b=2) . Damit würde es genügen, f(1) aufzurufen. Ergänzend darf man den Argumenten Namen geben. Dies hebt die Bedeutung hervor, zudem darf man so ihre Reihenfolge vertauschen. Diese sogenannten Keywords Arguments stehen hinter einem Semikolon:

Auf Wunsch kann eine Funktion auch beliebig viele Argumente entgegennehmen, die Julia dann innerhalb der Funktion in einem Tupel verfügbar macht (Varargs Functions). Auch die Operatoren wie +, * oder - sind für Julia allesamt Funktionen - 1+2 ist nur eine alternative Schreibweise für +(1,2).

Immer im Fluss

Bedingte Anweisungen erfolgen mit dem aus vielen anderen Sprachen bekannten if-else-Konstrukt, wobei die Bedingung immer true oder false sein muss:

Julia bietet nur eine while- und eine for-Schleife. Letztgenannte iteriert über eine Zahlenfolge oder die Elemente in einem Container. In folgendem Beispiel gibt sie die Zahlen von 1 bis 5 aus:

Schleifen lassen sich mit break abbrechen, continue startet umgehend den nächsten Schleifendurchlauf. Zwei verschachtelte Schleifen können Entwickler zu einer zusammenziehen:

Julia kennt Exceptions, die Funktionen bei einem Fehler mit throw werfen können. Ähnlich wie in Java fängt die Umgebung die Fehler über einen try- und catch-Block ab.

Typisch

Julia kennt unter anderem die Integertypen Int8, Int16 und Int32. Hin und wieder möchte man jedoch Code schreiben, der mit allen Integertypen umgehen kann. Dabei helfen die Abstract Types, die zusammen eine Hierarchie, den Type Graph, bilden. Ein Abstract Type ist beispielsweise der Typ Number, der wiederum für alle Zahlen steht. Die Funktion foo(a::Numbers) würde folglich alle Zahlen als Argument akzeptieren.

Am Ende der Hierarchie stehen die Primitive Types, die einfach eine vorgegebene Anzahl Bits im Speicher belegen. Bei Bedarf kann man Abstract Types und Primitive Types selbst definieren. In folgendem Beispiel ist der abstrakte Datentyp MeineZahl ein Subtyp von Number

Ergänzend hält Julia noch weitere interessante Datentyp-Konzepte bereit. So lassen sich mit Union{} mehrere Typen in einem vereinen und so etwa der Datentyp StringoderZahlumsetzen. Den Typ können Entwickler zudem im Programmcode abfragen und vergleichen. Beispielsweise prüft isa(), ob ein Objekt einem ganz bestimmten Typ entspricht - isa(1, Int) wäre beispielsweise true.

Multiple Dispatch

In Julia dürfen mehrere Funktionen mit dem gleichen Namen, aber unterschiedlichen Argumenten existieren. Bei einem Aufruf der Funktion wählt dann Julia automatisch die passende aus. Im folgenden Beispiel gibt es gleich zwei Mal eine Funktion foo():

Beide führen eine Berechnung mit einer Zahl adurch. Ruft man später foo() mit einer Zahl vom Typ Float16 auf, würde Julia selbstständig zur zweiten Funktion greifen, in allen anderen Fällen die erste wählen. Auf diesem Weg realisiert die Programmiersprache Polymorphie.

Die einzelnen Varianten von foo() bezeichnet Julia als Methoden. Der Begriff ist somit etwas anders ausgelegt als in objektorientierten Sprachen, wo Methoden in der Regel Teil einer Klasse sind. Die Auswahl der passenden Methode bezeichnet man allgemein als Dispatch.

Julia orientiert sich dabei immer an allen übergebenen Argumenten und deren Typen sowie dem Type Graph. Bei objektorientierten Sprachen fällt die Wahl hingegen meist anhand der Klasse, zu der das Objekt gehört. Julias Ansatz bezeichnet man daher auch als Multiple Dispatch. Dieser ist vor allem bei mathematischen Aufgaben hilfreich. Das bekannteste Beispiel ist die Funktion +, die auf allen möglichen Datentypen eine Addition umsetzen muss. In Julia dürfen Entwickler + einfach um passende Varianten für ihre eigenen Datentypen ergänzen. Es lassen sich sogar Regeln vorgeben, nach denen Julia gemischte Typen (wie die Addition einer Ganzzahl mit einer Gleitkommazahl) in einen eigenen Typ überführen soll (Promotion).

Anders als objektorientierte Sprachen kennt Julia keine Klassen. Ähnlich wie C lassen sich jedoch mit dem Schlüsselwort struct mehrere Variablen zu einem neuen Datentyp zusammenfassen:

Julia nennt diese zusammengesetzten Datentypen Composite Types. Von der im Beispiel entstandenen Person lässt sich mit einem Funktionsaufruf ein Objekt erzeugen:

hans ist damit vom Typ Person. Die aufgerufene Funktion bezeichnet Julia als Constructor. Ähnlich wie das gleichnamige Pendant in objektorientierten Sprachen kann man auch hier den von Julia automatisch bereitgestellten Contructor überladen, indem man einfach weitere passende Methoden definiert.

Standardmäßig lassen sich die Elemente von hans über den Punktoperator zwar auslesen (a=hans.alter), aber nicht verändern. Das gelingt erst, wenn man der struct noch explizit das Schlüsselwort mutable voranstellt. Die entsprechenden Objekte verwaltet Julia grundsätzlich auf dem Heap.

Generisch

Composite Types lassen sich parametrisieren. Andere Sprachen kennen das Konzept als Generics. Die wiederum sind beispielsweise nützlich, wenn der eigentlich zu nutzende Datentyp noch unbekannt ist:

Erst bei der Erstellung eines Objekts entscheidet sich, welche Daten "Point" speichert. Im Beispiel ist der Typ von a anschließend Point{UInt8}. Analog lassen sich auch Funktionen parametrisieren.

Parallelverarbeitung 

Julia kann mehrere Funktionen parallel ausführen. Diese sogenannten Tasks lassen sich pausieren und später fortsetzen. Daten tauschen die Tasks über sogenannte Channel aus. Dabei handelt es sich um First-In-First-Out-(FIFO-)Queues, in die mehrere Tasks gleichzeitig Daten schreiben und lesen können:

count ist der Task, der via put!() den entsprechenden Wert in den Channel c schiebt. Nach jedem put!()hält Julia die Funktion count() an und übergibt die Kontrolle wieder dem aufrufenden Code. Der Channel-Construktor Channel(count)erstellt einen Task, der an den Channel gebunden ist. take!() liest ein Datum aus dem Channel und sorgt dafür, dass der producer()weiterläuft.

Intern realisiert Julia die Tasks als Coroutinen. Ergänzend bietet die Version 1.1 der Programmiersprache auch experimentelle Unterstützung für Multithreading. Stabil ist hingegen schon die Unterstützung für verteiltes Rechnen. Dabei läuft der Julia-Interpreter mehrfach lokal oder auf entfernten Rechnern. Der Programmierer kann dann gezielt auf den entfernten Rechnern einzelne Julia-Funktionen aufrufen.

Metaprogrammierung

 

Ähnlich wie in Lisp kann sich der Julia-Programmcode selbst verändern. Dazu stellt die Programmiersprache passende Funktionen bereit. Hinzu kommen die an Lisp angelehnten Makros. Diese Helfer nehmen mehrere Argumente entgegen, die sie in einen Ausdruck einbauen:

@hallo ersetzt Julia gegen println(Hallo , Paul). Den von Makro vorgegebenen Code baut Julia zusammen, noch bevor das komplette Programm startet. Auch bei Makros greift wieder das Multiple-Dispatch-Prinzip, es kann folglich mehrere Methodendefinitionen geben.

Viele weitere Kleinigkeiten

 

Neben den vorgestellten Konstrukten bietet Julia noch viele weitere interessante Konzepte. So lassen sich etwa Arrays extrem flexibel über sogenannte Comprehensions erstellen. Wer die Mengendefinition aus der Mathematik kennt, dürfte mit der Syntax schnell warm werden. Das folgende Beispiel liefert das Array [4,5]:

Diese Notation kann man auch in einer sogenannten Generator Expression verwenden. sum(n for n=1:10) berechnet etwa die Summe aller Zahlen von 1 bis 10.

Besonders für Matrixrechnungen interessant ist die Funktion broadcast(), die eine ihr übergebene Funktion auf alle Elemente eines Arrays anwendet. Sofern notwendig, passt die Funktion dabei die Dimensionen der beteiligten Arrays an, ohne dabei zusätzlichen Speicher zu belegen.

 

Des Weiteren bietet Julia ein eingebautes Dokumentationssystem: Platziert man einen String alleine vor eine Funktionsdefinition oder anderen entsprechenden Codestellen, interpretiert sie Julia als Dokumentation (sogenannte Docstrings). Diese Hilfetexte können unter anderem die Kommandozeilenfassung von Julia sowie die Juno-IDE auswerten und durchsuchen.

 

Abschließend lässt sich Julia-Code über entsprechende Schnittstellen aus C-Programmen aufrufen. Umgekehrt können Programmierer aus dem Julia-Code heraus externe Programme starten sowie C- und Fortran-Code aufrufen. Auf diese Weise lassen sich zahlreiche C-Bibliotheken einbinden und weiternutzen.

 

Julia kombiniert die Vorteile einer Skriptsprache mit effizient und flott laufenden Binärdateien. Insbesondere bei mathematischen Berechnungen, der Verarbeitung von Daten und in der Wissenschaft bietet sie eine interessante Alternative zu Python, R und Matlab. Wer in Julia einsteigen möchte, sollte die offizielle Dokumentation konsultieren. Sie bespricht äußerst ausführlich alle Aspekte der Sprache und hält zahlreiche Tipps für die tägliche Arbeit und den Aufbau von Julia-Programmen bereit.

veröffentlicht am 25. Juni 2019

Newsletter abonnieren

Melde dich für unseren Newsletter an und erhalte die neuesten Informationen zu Themen rund um deine IT-Karriere.

Alles für deine IT-Karriere

Entdecke unser vielfältiges Angebot für deine persönliche und fachliche Weiterentwicklung in der IT. Fachtrainings, E-Learningkurse oder Coachings zu den wichtigsten IT-Themen und Trends in der Golem Karrierewelt.

Jetzt entdecken!

IT-Weiterbildungen für Unternehmen

Seit Jahren unterstützen wir erfolgreich kleine, mittlere und große Unternehmen bei der Konzeption und Umsetzung von Weiterbildungen für IT-Professionelle. Jetzt über die Angebote für Unternehmen informieren.

Weitere Informationen

IT-Karrieretipps und Services

Ob Jobsuche, Sprachen lernen, IT-Fernstudium oder Gehaltsvergleich: Bei uns findest du alles für deine IT-Karriere. Erkunde unseren Karriere-Ratgeber oder nutze das Karriere-Lexikon zu allen relevanten Themen und Begriffen.

Zum IT-Karriere-Ratgeber