Vorlesung: Praxis der Funktionalen Programmierung | Index

Externe Bibliotheken mit greencard

Haskell-System-Entwickler können sich nicht auf den Standpunkt stellen, die Sprache ist eine Perle der reinen Theorie, und nochdazu Turing-vollständig, also sollen alle Leute alles nur noch in Haskell programmieren.

Das wäre höchst ineffektiv, denn für viele Dinge gibt es ja bereits Lösungen, und man möchte Haskell eigentlich gerade deswegen verwenden, um diese elegant zu kombinieren, und die Steuerprogramme eben auf möglichst hohem Niveau verfassen.

Dann muß man es aber auch schaffen, die vorhandenen Bibliotheken zu benutzen. Es gibt dafür eine mittlerweile halbwegs standardisierte Schnittstelle, und ein Werkzeug greencard (benannt nach der US-amerikanische Arbeitserlaubnis), das die nötigen Klebestellen (für hugs und ghc) aus einer Interfacbeschreibung generieren kann.

(Offizielle Beschreibung hier)

Worum geht es dabei? Wir haben eine Bibliothek von (C-)Funktionen, und möchten die aus einem Haskell-Programm aufrufen. Das klingt einfach, ist es aber nicht: es heißt zwar C-Funktion, ist aber im mathematischen Sinne keine, also auch für Haskell nicht. Die Dinge leben deswegen meist in der IO-Monade. Aber das ist nur ein Aspekt.

Die externen Funktionen erwarten Parameter, und liefern Resultate. Diese müssen konvertiert werden, denn ein String in C (also ein zusammenhängender Speicherbereich) ist zum Beispiel etwas anderes als ein String in Haskell (eine lazy Liste von Zeichen). Am einfachsten ist es noch, wenn nur Ints hin- und herwandern. Aber vielleicht möchten wir Records benutzen? Übergeben wir diese als Wert oder per Referenz? Darf die externe Prozedur ihre Parameter ändern? Was ist, wenn wir eine Zeiger in den Haskell-Heap herausgeben, das externe Programm merkt sich den, aber wenn es ihn beim nächsten Aufruf benutzen will, steht dort längst etwas anderes, weil inzwischen das Haskell-Runtime-System eine kompaktierende Garbage-Collection gemacht hat?

All das (sowohl was wir wollen als auch was das externe Programm denkt) geht aus C-Headern nicht genau hervor, deswegen kann der Interface-Generator nicht so gehen, daß er einfach über das C-Headerfile drüberläuft und dann eine passende Schnittstelle erzeugt. Stattdessen müssen wir selbst hinschreiben, wie wir uns die Aufrufe und Wertübergaben denken. Aus dieser Spezifikation (ein File Foo.gc) erzeugt dann greencard ein passendes Haskell-Modul Foo.hs, das wir kompilieren (oder in hugs laden) können. Natürlich muß die externe Bibliothek selbst auch noch dazugelinkt werden: wenn wir kompilieren, dann eben beim abschließenden Linken, wenn wir mit hugs interpretieren, dann als DLL (bzw. Foo.so)

Ein "offizielles" Beispiel dafür haben wir bereits benutzt: die Hugs-Graphics-Library. Das ist jedoch eine recht umfangreiche XLib-Bindung. Ein noch größerer Brocken ist die Haskell-OpenGL-Bindung (TODO: link), die ich aber noch nicht benutzt habe (für den Jongliersimulator würde man das aber sicher als Backend nehmen) (wenn man nicht von den existierenden Programmen (jongl, joe-pass) das komplette Backend klaut (geht aber eigentlich nicht, da beide keine freie Software sind) (aber die Autoren sind kooperativ).

Überschaubarer als XLlib und HOpenGL ist die Anbindung eines MIDI-Treibers (der also das timing in der Ausgabe erledigt). Siehe dazu diese Quelltexte (TODO: links) und das Makefile. Für diesen Treiber gibt es eine Bastelaufgabe (mehr C als Haskell, fürchte ich): er soll bei Pufferüberfüllung nicht blocken, sondern sofort zurückkehren. Damit sollten wir die Timing-Probleme beim Sequencer leicht beheben können.

An einer weiteren Stelle habe ich mir durch eine externe Bibliothek viel Arbeit gespart: für Go spielende Programme gibt es das GMP (Go Modem Protokoll), und mein irgendwann auch mal GO spielendes Programm soll die auch verstehen. Das GMP ist ursprünglich für Menschen gedacht, die über langsame Modems gegeneinander spielen, also sind einerseits furchtbar platzsparende Optimierungen drin, andererseits einige Tricks mit Paritäten und gegenseitigen Acknowledgments. Das ist nicht so absolut exakt spezifiziert, und auch dann nicht so angenehm from scratch zu implementieren. Brauch ja auch keiner - es gibt Referenz-Implementierungen, die seit langen Jahren ihren Dienst tun. Ich habe mir den GMP-Treiber aus gnugo genommen und einfach mit greencard ein Haskell-Interface gebastelt - geht.

(Quelltexte hier)


best viewed with any browser


http://www.informatik.uni-leipzig.de/~joe/ mailto:joe@informatik.uni-leipzig.de