Julia

Lesezeit: 10 Minuten

Julia ist eine prozedurale Sprache mit Multimethoden, funktionalen Merkmalen und Makros zur aspektorientierten Programmierung. Julia wurde von den US-amerikanischen bzw. indischen Informatikern Jeff Bezanson, Stefan Karpinski, Viral B. Shah und Alan Edelman erfunden. Die Sprache ist seit 2009 in der Entwicklung. Das erste Release erschien 2012. Im Jahr 2018 wurden sowohl die Version 0.7 als auch 1.0 veröffentlicht. Der Name der Sprache wurde laut Karpinski einfach aus ästhetischen Gesichtspunkten gewählt und hat keine weitere Bewandtnis.

Alleinstellungsmerkmale

Tatsächlich habe ich nicht rausfinden können, warum man sich im Jahr 2009 dazu entschieden hat, die Sprache Julia zu entwickeln. Aus meiner Sicht verbindet Julia die Einfachheit und gute mathematische Eignung von Python mit der Geschwindigkeit von C und Fortran. Multiple Dispatch (und der Verzicht auf Objektorientung) sowie der Unicode-Support können sicherlich als Alleinstellungsmerkmale gewertet werden, mir fällt aber kein Anwendungsfall ein, wo ich sagen würde, dass Julia heute oder zum Zeitpunkt der Veröffentlichung der klare Gewinner im Vergleich mit anderen Sprachen war.

Einsatzbereiche

In den Case Studies auf JuliaHub (Link am Ende des Beitrags) sind viele Beispiele aufgeführt, welche Unternehmen Julia für welche Zwecke einsetzen. Julia kommt in der Finanz- und Versicherungsbranche zum Einsatz, im naturwissenschaftlichen Bereich und bei der Anwendung maschinellen Lernens. Julia wird von vielen Universitäten, unter anderem MIT, Stanford und UC Berkely eingesetzt, sowie unter anderem von Amazon, ASML, Aviva, BlackRock, CERN, der FAA, der Federal Reserve Bank of New York, IBM, dem brasilianischen INPE, JP Morgan und der NASA.

Pros

Julia hat sich in den letzten Jahren rasend schnell weiterentwickelt und hat in vier Jahren einen Versionssprung von 1.1 auf 1.8 hingelegt. Ich finde dies erwähnenswert, da ich viel Kritik in mehreren Jahren alten Artikeln zur Sprache gesehen habe, die ich selbst nicht nachvollziehen kann und die heute unter Umständen nicht mehr oder nur noch im reduzierten Maße zutrifft. Julia ist eine sehr schnelle Sprache, auf vergleichbarem Niveau wie C und Fortran. Syntaktisch ist Julia sehr ähnlich zu Python. Ich habe ein Skript in beiden Sprachen geschrieben, welches die Summe aller geraden Fibonacci-Zahlen kleiner vier Millionen berechnet und dieses läuft in Julia (1,3 Sekunden) etwa zehnmal schneller als in Python (12,2 Sekunden).

Aus Julia heraus kann Code in anderen Sprachen aufgerufen werden, zurzeit sind das mindestens C, C++, C#, Fortran, Java, JavaScript, MATLAB, Python und R. Julia ist sehr leicht zu erlernen, nach meinem persönlichen Empfinden neben Python eine der leichtesten Sprachen überhaupt. Julia ist sehr kompakt und flexibel und damit hervorragend für Prototyping und Skripting geeignet.

Julia bietet viele native Datentypen, die im mathematisch-naturwissenschaftlichen Bereich interessant sind. Neben Listen (Arrays) und Maps (Dictionaries) gibt es auch Tupels, Ranges und Matrizen. Bei Letzterem können Leerzeichen und Zeilenumbrüche als Trennzeichen fungieren, so dass sich entsprechend formatierte zweidimensionale Inhalte wie etwa Sudokus per copy/paste ohne weitere Anpassung in ein Julia-Skript übertragen lassen. Das folgende Beispiel zeigt eine Matrix mit drei Zeilen und jeweils den Werten 1-3, 4-6 und 7-9:

matrix = [
    1 2 3
    4 5 6
    7 8 9
]

Julias Typensystem basiert auf Multiple Dispatch (Multimethoden). Je nach Argumententyp kann beim Aufruf einer Funktion add(value1, value2) eine unterschiedliche Implementierung verwendet werden. Dadurch werden auch Namenskonflikte vermieden, wenn eine gleichnamige Funktion in mehreren verwendeten Modulen vorhanden ist, solange die sich in der Typisierung ihrer Parameter unterscheiden. Julia bietet viel syntaktischen Zucker, der die Produktivität und Lesbarkeit erhöhen kann. Neben nativen regulären Ausdrücken gibt es Raw / Multi Line Strings, String Interpolation, mehrere Rückgabewerte und eine besonders kompakte Notation für einzeilige Funktionen:

add(a,b) = a + b

Julia unterstützt varargs (variable Anzahl an Argumenten), Named und Optional Arguments in Funktionen und Named Tuples. Unicode-Zeichen werden in Julia umfangreich unterstützt. Der Ausdruck 3 ≠ 4 liefert wie 3 != 4 den Wert true zurück, analog können und ähnliche Zeichen eingesetzt werden. Die folgende Funktion verwendet das Summenzeichen als Name:

∑(x,y) = x + y
∑(1,2) # Rückgabewert ist 1 + 2 = 3

Ein besonders nettes Detail ist der Einsatz des Ausrufezeichens am Ende von Funktionsnamen zur Kennzeichnung von Seiteneffekten. So erkennt man auf einen Blick, ob eine Funktion Eingabewerte verändert. Diese Guideline wird allerdings nicht erzwungen, so dass man auch dagegen verstoßen (und sich fälschlicherweise darauf verlassen) kann. Folgender Aufruf fügt dem Array numbers den Wert number hinzu:

push!(numbers, number)

Julia unterstützt Koroutinen und Threads und bietet allgemein gute Unterstützung für parallele Verarbeitung. Im Lieferumfang ist auch eine interaktive REPL enthalten. Um eine sehr gute Performance zu erreichen, unterstützt Julia unterschiedlich große Zahlentypen wie Int8, Int16, Int32 und Int64. Diese können auch überlaufen. Für besonders große Zahlen gibt es die in der Größe nicht begrenzten Typen BigInt und BigFloat. Julia hat einen integrierten Package Manager, über den direkt aus dem Code Bibliotheken bei Bedarf heruntergeladen und installiert werden können.

Julia wendet Typinferenz an. Variablen ohne expliziten Typen können diesen bei Neuzuweisung ändern. Variablen mit explizitem Typ können nullable gemacht werden. In der Vergangenheit scheint es hierfür verschiedene Ansätze gegeben zu haben, aktuell scheint es Best Practice zu sein, dafür den Datentyp Union zu verwenden:

a = 3
a = "abc" # dynamische Variable
b::Union{Int,Nothing} = 3
b = nothing # nullable int
c::Int = 3
c = nothing # MethodError, da Typ nicht nullable ist

Julia ermöglicht aspektorientierte Programmierung und Code-Generierung via Makros, die durch ein vorangestelltes @ erkennbar sind. Folgender Aufruf verwendet das @time-Makro, um anzugeben, wie lange die Multiplikation von zehn Millionen minus 1 mit sich selbst dauert:

 @time 9_999_999^big(9_999_999)
# in meinem Fall 1.317931 Sekunden

Was mir persönlich sehr gefällt, sicherlich aber Geschmackssache ist und auch negativ aufgefasst werden kann, ist die Indizierung ab 1 und nicht ab 0, und in Kombination damit, dass Ranges sowohl am Anfang als auch am Ende inklusive sind. Bei verschiedenen Aufgaben, in denen Zahlenbereiche involviert waren, habe ich gemerkt, dass ich somit komplett auf ein +1 bzw. -1 in den Ausdrücken verzichten kann.

Die Range 1:999 enthält also alle Zahlen von 1 bis 999, das Äquivalent in Python wäre z.B. range(1,999+1). In einem Array kann ich das letzte Element mit dem Aufruf array[length(array)] referenzieren, während es in Python array[len(array)-1] oder in Java array[array.length-1] ist. Im täglichen Gebrauch haben diese beiden Eigenschaften von Julia für mich nur positive Effekte gehabt.

Cons

Julia ist in den letzten Jahren sehr gereift, gegenüber wesentlich älteren Sprachen wie Python ist das Ökosystem aber immer noch vergleichsweise klein. Beim Einsatz in größeren Projekten würde ich ganz genau hinschauen, ob es entsprechende Bibliotheken in Julia gibt.

Julia bietet leider keine bzw. inkonsistente Namenskonventionen. Einige Standardfunktionen liegen in squashed case vor, andere in snake case. Beispiele aus der Netzwerk-Standardbibliothek sind die Funktionen getaddrinfo() und join_multicast_group(). Problematisch ist, dass es keine eindeutige Regelung gibt, ab wann ein Name zu unleserlich für squashed case angesehen wird. Verwendet man keine IDE mit richtig guter Autovervollständigung, passiert es schnell, dass man sich verschreibt, weil man nicht mehr sicher ist welcher Case für die Funktion verwendet wird.

Julia kennt keine objektorientierte Programmierung. Stattdessen gibt es Typen, Multimethoden und Structs. Letztere gruppieren aber nur Werte, keine Funktionen. Auch wenn nicht jede Sprache Objektorientierung muss, hat dies den nachteiligen Effekt, dass es manchmal verwirrend ist, welche Funktion welche Argumente in welcher Reihenfolge entgegennimmt. Gestärkt wird dieser Nachteil dadurch, dass ziemlich viele Funktionen ziemlich viele Typen unterstützen. Ein konkretes Beispiel, wo ich selbst darüber gestolpert bin:

list = [1, 2, 3] # Liste mit 3 Elementen
value = 4 # einzelnes Element, nicht in der Liste
in(value, list) # false
in(list, value) # false
push!(list, value) # Element der Liste hinzufügen
in(value, list) # true -> Element ist in Liste
in(list, value) # false -> Liste ist nicht in Element

Datenblatt

NameJulia
Webseitehttps://julialang.org/
Erscheinungsjahr2012
Aktuellste Version (Stand 29. Januar 2023)Version 1.8.5 vom 8. Januar 2023
Typisierungdynamisch
Paradigmenimperativ
prozedural
funktional
aspektorientiert
ProsCons
sehr leicht zu erlernen(noch) vergleichsweise kleines Ökosystem
sehr schnell (wie C und Fortran)keine objektorientierte Programmierung
viele native Typen inklusive Listen, Tupels, Matrizen und RangesVerwirrungspotenzial bei Parametrisierung
kompakte Notation, native reguläre Ausdrücke, Multi Line Strings, String Interpolation, mehrere Rückgabewerte möglich, Named Arguments, Optional Arguments, Named Tuples, Multiple Dispatchkeine guten/expliziten Namenskonventionen
Ausrufezeichen als Merkmal für Funktionen mit Seiteneffekten
Indizierung ab 1 und Ranges mit inklusivem Ende
Unterstützung für große Zahlen
integrierter Package Manager, REPL
Makros
Koroutinen und Threads
explizite Deklaration von nullable / non nullable möglich

virtuallet

virtuallet ist ein kleines Programm von mir, welches ich in diversen Programmiersprachen implementiert habe. Hier geht es direkt zum Julia-Code von virtuallet auf GitLab. Hier gibt es weitere Infos zu virtuallet.

- Case Studies auf JuliaHub