Programmierung mit Ontologien kombinieren
by Viktor Garske on Oct. 10, 2020, 10 p.m.→ Schnellzugriffe für diesen Artikel: Arbeitspapier (engl., PDF), GitHub-Repository
Programmierung ist heutzutage ein extrem wichtiges Werkzeug geworden. Dabei hat sich in den vergangenen Jahren der Fokus geändert: war der „portable Assembler“ namens C in den 1970ern noch dafür da, Abstraktion für verschiedene Plattformen zu ermöglichen, geht es heutzutage eher darum, Funktionalität so zusammenzustecken, dass das resultierende Programm den Anforderungen genügt. Bis heute ist allerdings der imperative Ansatz derjenige, der unangefochten die Programmierlandschaft beherrscht. Dabei spezifiziert der Programmierer Schritt für Schritt den Lösungsweg, den der Computer dann ausführt.
Ein Blick in die Geschichte
Es ergeben sich allerdings einige Probleme, die früher keine größere Aufmerksamkeit benötigten, heute allerdings die Arbeit erheblich erschweren: so konnte man es sich in den 1980ern leisten, die komplette Codebase selber zu entwickeln. Ken Thompson formulierte dabei das Credo „You can't trust code that you did not totally create yourself.“ Dieser Satz gilt heute eigentlich nach wie vor, da importierter Code auf dem gleichen Level wie die eigene Software agiert.
Nun gibt es verschiedene Programmiersprachen, die auch verschiedene Zwecke erfüllen: während das zuvor erwähnte C auf der Low-Level-Ebene unterwegs ist, gibt es Sprachen wie Python, die immer weiter abstrahieren.
Diese Abstraktion nimmt allerdings keineswegs ein Ende und das macht die Sache besonders interessant. Denn eigentlich befinden wir uns bei den imperativen bzw. prozedularen Sprachen auf Stufe 3 von insgesamt 5. Auf den tieferen Stufen haben wir Maschinencode oder Assemblersprachen, auf deren darüberliegenden Stufen liegen SQL, die Unix Shell und R (4GL) sowie Mercury und OPS5 (5GL). Während die Sprachen der 4. Generation noch den meisten geläufig sind, werden sicherlich die wenigsten von Mercury o.ä. gehört haben.
Ziel dieser Abstraktion ist es übrigens, dem Computer nur noch das Problem zu beschreiben und diesen dann den Lösungsweg ermitteln zu lassen.
Als ich davon vor einigen Jahren gehört habe, hat es mich nicht mehr losgelassen. Und so überlege ich seit einigen Jahren, wie man neben C, C++, Rust und Python eine Möglichkeit nutzen kann, abstrakt Programme zusammenzustellen, die dann vom Computer zusammengeschweißt werden (und zwar ohne dynamische Typisierung). Eben wie die klassische Unix-Philosophie, die sich in den Code hineinzieht.
Vor einigen Monaten hat sich abgezeichnet, dass sich die Idee am besten in einer (vorerst experimentellen) Programmiersprache umsetzen lässt.
Was aber ist nun die Besonderheit der Sprache? Die oben eingeführten Sprachen der fünften Generation sind sehr funktional, deklarativ – und vielen unhandlich. Man ist zu sehr an die imperative Arbeitsweise gewöhnt, um sie komplett auszutauschen. Trotzdem blieb bei mir der Aspekt, die imperative Arbeitsweise durch logische Programmierung zu erweitern, irgendwie hängen. Ausschlaggebend hierfür war das Semantic Web, das in den 2000ern besonders Konjunktur hatte. Aber auch Wikidata fasziniert mich mit der Art und Weise, wie Informationen formalisiert werden können, um anschließend Wissen daraus ableiten zu können. So ist es heute schon möglich, die Mondphase am Geburtstag von John von Neumann abzufragen, ohne einen genauen Algorithmus hierfür spezifzieren zu müssen. Ehrlich gesagt finde ich das extrem spannend und überlege, wie sich das sinnvoll in der Programmierung einsetzen lässt. Denn für obiges Szenario müssen wir nicht einmal auf maschinelles Lernen zurückgreifen, sondern können uns an einem regelbasierten System bedienen, das in formalen Umgebungen besonders hilfreich ist.
Um was es geht
Dafür habe ich nebenbei begonnen, die Ontology-assisted Experimal Programming Language, kurz OXPL, in eine Grammatik zu gießen.
Die OXPL geht dabei auf einen Ausflug in die Welt der Ontologien: bei diesen geht es grob gesagt darum, Wissen dem Computer zugänglich zu machen, damit dieser dies Verarbeiten kann. Teilweise wird dies schon heute umgesetzt, wenn wir uns Klassenhierarchien in z.B. Java anschauen.
Ich möchte nur einmal kurz die Schlüsselaspekte umreißen: beginnen wir mit dem „Hello world“.
fn main() {
println("hello, world");
}
So weit, so unspektualär. Wie wir sehen, sind wir allerdings auf dem Gebiet der imperativen Programmierung unterwegs - und das ist ein entscheidender Aspekt: man kann sie jederzeit als Fallback nutzen. Wagen wir aber nun den Sprung ins kalte Wasser: der folgende Codeblock implementiert einmal eine Addition: zwei Summanden werden abgefragt, das Ergebnis wird ausgegeben.
instance Summand1: integer {
std::fetchesFrom <std::io::stdin>.
<std::io::stdin> std::io::prompts "Summand 1: ".
}
instance Summand2: integer {
std::fetchesFrom <std::io::stdin>.
<std::io::stdin> std::io::prompts "Summand 2: ".
}
instance Sum: std::math::sum {
std::math::sum::hasSummand <Summand1>.
std::math::sum::hasSummand <Summand2>.
}
fn main() {
println("The sum is " + string::from(Sum));
}
Hier sehen wir das charakteristische Merkmal der Sprache: das Programm ist bis auf die main-Funktion lediglich eine Beschreibung zweier Summanden sowie einer Summe. Die Tripel (Subjekt - Prädikat - Objekt - Punkt) formulieren explizit die Beziehungen und Anforderungen untereinander. Nun soll der Interpreter bzw. Compiler im nächsten Schritt über Regeln sowie viele Informationen ableiten, dass eine imperative Ausführung möglich und eine Executable entsteht. Auch wenn man mit Python dieses Vorhaben in einer Zeile lösen könnte, würde das obige Beispiel rein konstruktiv bereits die Summanden als Ganzzahlen erkennen, sodass Bugs wie eine Summe "11" bei den Summanden 1 und 1 vermieden werden.
Ein weiterer Aspekt: Sicherheit. Der Super-Gau unter importierten Bibliotheken sind unerwartete, bösartige Seiteneffekte. Wie wäre es, wenn jegliche Kommunikation nach außen hin erlaubt werden muss? Dann fällt besonders auf, wenn eine Calculator-Bibliothek auf einmal Festplatten- und Netzwerkzugriff möchte. Für das Ausgabebeispiel würde das Ganze so aussehen:
allows(this, std::io::print, main).
fn main() {
println("hello, world!");
}
OXPL wird dieses Permissionsystem von Anfang an implementieren, sodass spätere Nachbesserungen wie die libseccomp im Normalfall by-design nicht mehr notwendig ist.
Was ich bereits habe
Ich habe bereits angefangen, als Sideproject an der ontology toolchain (ontc) zu entwickeln. Das wird die Referenzimplementierung sein. Sie ist in C geschrieben und steht bereits auf GitHub zur Verfügung.
Mit
sudo apt install build-essential git bison flex
git clone https://github.com/v-gar/ontc.git
make
kann die Anwendung gebaut werden. Die Executable ontc liegt dann im Buildverzeichnis. Nun kann eine Datei helloworld
(eine Dateiendung habe ich noch nicht festgelegt) erstellt werden, die das obige Hello world enthält. Das Ausführen ist mitteils ./build/ontc run helloworld
möglich. Um die Ontologie zu testen, habe ich folgendes Beispiel:
fn hey() {
println("Hey!");
}
fn main() {
println("Hello world!");
}
<main> isPreceededBy <hey>.
Dieses Beispiel zeigt, dass über diese Tripel auch Einfluss auf den Programmablauf genommen werden kann, sofern (wie im Beispiel) der Interpreter bestimmte Prädikate berücksichtigt.
Ein Anfang
Diese beiden Beispiele sind Teil des Proof-of-Concepts dieses Projekts. Großartig mehr ist noch nicht möglich. Zwei Dinge sind für die Sprache entscheidend: gute Entwicklungswerkzeuge und eine vielfältige, hochqualitative Standardbibliothek. Die Standardbibliothek gibt, ähnlich wie bei schema.org, den Rahmen und definiert, wie üblicherweise Prädikate aufgebaut sind, wie mit dem Compiler interagiert werden kann, etc. Die ontc soll dabei mit gemeinsamer Codebase als Toolchain dienen und einen Interpreter, Debugger und Analyzer beherbergen, um schnell gute Programme zu schreiben.
Wer gerne mehr zu meinem Projekt lesen möchte, kann sich mein Arbeitspapier (PDF) hierzu anschauen.
Da dieses Vorhaben unmöglich in absehbarer Zeit alleine umsetzbar ist, suche ich nach Mitstreitern, die sich an diesem Projekt beteiligen wollen. Ihr könnt euch hierzu unter meiner Kontaktadresse melden!
Viktor Garske
Viktor Garske ist der Hauptautor des Blogs und schreibt gerne über Technologie, Panorama sowie Tipps & Tricks.