Wie benutze ich Haskell in unseren Computerpools?
Um eines der Haskell-Systeme im Computerpool zur Verfüng zuq haben, führt man zu Beginn der Sitzung setup lang/hugs bzw. setup lang/gcc und setup lang/ghc aus (am besten automatisch mit Hilfe der Datei .login).
Als Editor verwenden wir den XEmacs, da für diesen ein Editor ein Haskell-Plugin zur Verfügung steht.
Die Verwendung des Haskell-Plugins (aka Haskell-Mode) erleichtert das Entwickeln von Haskell-Programmen ungemein. Es stehen u.a. folgende Funktionen zur Verfügung:
Um das Haskell-Plugin zu verwenden, muss die Datei .emacs mit Initialisierungscode ergänzt werden. Für Hugs ist der Code hier hier zu finden, für den GHC hier, und für den GHC mit richtigen Optionen für WASH hier.
Wie kann ich eine Eingabe (bzw. ein String) in einen Int konvertieren?
Hierzu gibt es die eingebaute Funktion
read :: Read a => String -> a
die einen String in Objekt eines anderen Typs a konvertiert. (Der Constraint Read a zu Beginn des Typs besagt, dass dies nicht für beliebige Typen a funktioniert, sondern nur für solle, deren Objekte man wirklich aus einem String "einlesen" kann).
Wendest Du diese Funktion an, muss Du dem Haskell-System auf die Sprüge helfen, damit es herausbekommen kann, was für ein Objekt es aus dem String erzeugen soll (das kann es ja erstmal nicht wissen ...) Das geht so, in dem Du bei der Verwendung von read auf jeden Fall explizit einen Typ mit angibst. Damit kann das Haskell-System entscheiden, wie es konvertieren muss.
Hier ist beispielsweise eine I/O-Aktion, die einen Int einliest:
getInt :: IO Int getInt = do line <- getLine return (read line)
Diese I/O-Aktion liest erst eine Zeile als String ein, wandelt diese mit Hilfe von read in einen Int um, und gibt diesen dann als Ergebnis zurück. Die Typannotation IO Int genügt, damit das System weiss, hier mit read in einen Int zu konvertieren.
Im Haskell-Prelude ist die Funktion
show :: Show a => a -> String
vordefiniert, mit deren Hilfe Werte vieler verschiedener Typen in Strings umgewandelt werden können. Eine allgemeine Druckfunktion ist hiermit auch schon vordefiniert:
print :: Show a => a -> IO () print = putStrLn . show
Damit wird es sehr einfach beliebige Objekte auzusgegeben; so gibt etwa das Programm
main :: IO ()
main = do print 42
print True
erst die Zahl 42, und anschliessend den bool'schen Wert True aus.
Ich bräuchte Zufallszahlen. Wie bekomme ich die denn in Haskell?
Das Modul Random stellt hierzu passende Funktionen zur Verfügung. Die dort definierte Funktion
randomRIO :: Random a => (a, a) -> IO agibt Dir wahrscheinlich genau die I/O-Aktion zurück, die Du gesucht hast: Aufgerufen mit einem Paar (lo, hi) bekommst Du eine I/O-Aktion zurück, die eine Zufallszahl im geschlossen Intervall [lo, hi] erzeugt und zurückliefert.
Folglich gibt das Programm
import Random
main :: IO ()
main = do r <- rollTheDice
print r
rollTheDice :: IO Int
rollTheDice = randomRIO (1, 6)
eine Zufallszahl zwischen 1 und 6 aus. Wie Du das Ganze innerhalb eines CGI-Programmes machst, siehe unten.
Auf welchen Rechnern kann ich WASH-Programme schreiben?
WASH ist auf den den Solaris-Maschinen in den Computerpools insatlliert. Solltest Du nicht direkt vor solch einem Rechner sitzen, kannst Du Dich auch per SSH auf einen dieser Rechner einloggen. Hier sind einige Namen von Pool-Rechnern: leo, merkur, neptun, ...
Welchen Setup-Befehl muss ich ausführen, damit ich WASH im Computerpool zur Verfügung habe?
Für das Entwickeln mit Hugs heisst der Befehl
setup lang/hugs
und für das Arbeiten mit dem GHC
setup lang/gcc setup lang/ghc
Am besten legt man diese Befehl in der Datei .login ab, damit das Setup bei jedem Neustart automatisch ausgeführt wird.
Wie sieht ein einfaches WASH-Programm aus?
Hier ist das erste Mini-Programm aus der Vorlesung, das eine einfache Webseite ausspuckt.
import CGI -- indicate it's using WASH main = -- main program (fixed name) run $ -- starts a CGI script standardQuery "Hello" $ -- constructs a Web page text "This is my first CGI program!" -- contents of page
Das Programm als Textdatei (CGIHello.hs)
Wichtig ist, das WASH-Programme immer die CGI-Bibliothek importieren.
Wie entwickle und teste ich ein WASH-Programm?
Geschickt ist es, Programme interaktiv zu entwickeln, zumindest solange bis keine Typfehler mehr auftreten.
Mit Hugs steht WASH (in den Computerpools) sofort zur Verfüung, d.h. um interaktiv mit einem Programm zu arbeiten, sagt man einfach:
hugs CGIHello.hs
Für den GHC ist WASH in den Pools ebenfalls installiert. Um WASH zur Verfügung zu haben, bedarf es einer expliziten Package-Angabe. Um also interaktiv ein WASH-Programm zu testen, sagt man einfach:
ghci -package wash CGIHello.hs
Am komfortabelsten geht das interaktive Entwickeln aber immer noch mit Hilfe des Editors XEmacs und dem Haskell-Plugin.
Und dann! Wie bekomme ich am Ende das ausführbare CGI-Programm?
Dazu muss das WASH-Programm in eine ausführbare Binärdatei übersetzt werden. Das geht mit folgendem GHC-Aufruf:
ghc --make -package wash -o CGIHello CGIHello.hs
Erstmal ganz ruhig! Das CGI-Programm lässt Du nun den Web-Server ausführen, und die Ausgabe kannst Du dann mit Hilfe Deines Lieblingsbrowsers anschauen -- wie siehe unten.
Kann ich auch zu Hause in WASH programmieren?
Natürlich! Das gesamte Softwarepaket inklusive Installationsanleitung ist auf der WASH-Webseite zu finden.
Trotzdem wird von der Bearbeitung der Übungsaufgaben zu Hause am eigenen Rechner eher abgeraten. Um WASH-Programme zu Hause erfolgreich entwickeln und testen zu können, wird neben dem GHC und WASH ausserdem ein konfigurierter Apache-Web-Server benötigt. Zu Installationen zu Hause kann generell keine Hilfestellung geleistet werden.
Das Arbeiten auf einem Poolrechner von zu Hause aus ist hingegen per SSH problemlos möglich.
Ich würde gerne eine I/O-Aktion innerhalb eines CGI-Programms ausführen. Geht das irgendwie?
WASH stellt genau zu diesem Zweck die Funktion
io :: (Read a, Show a) => IO a -> CGI a
zur Verfügung. Diese Funktion transformiert eine I/O-Aktion in eine CGI-Aktion. Ein CGI-Programm, das etwa erst (mit Hilfe einer I/O-Aktion) eine Zahl würfelt, und dann die gewürfelte Zahl ausgibt, sieht somit wiefolgt aus:
import CGI
import Random
main = run $
do r <- io rollTheDice
standardQuery "Hello" $ text $ "Your lucky number is " ++ (show r)
rollTheDice :: IO Int
rollTheDice = randomRIO (1, 6)
Sieht der auftretende Typfehler etwa wiefolgt aus
ERROR "Blah.hs":42 - Type error in application *** expression :radioGroup empty *** Term :radioGroup *** Type :WithHTML a CGI (RadioGroup b INVALID) *** Does not match :c -> d
liegt das wahrscheinlich daran, dass Du (wie fäschlicherweise auf den Vorlesungsfolien angegeben) radioGroup mit einem zusäzlichen Argument (wie etwa empty) aufgerufen hast. Korrekt wird aber ein Handle schon durch radioGroup alleine (d.h. ohne weitere Argumente) erzeugt. Hier ist ein korrektes Codefragment dazu:
do ...
rptF <- radioGroup
p (text "Number of exercises " >>
text " 5 " ## radioButton rptF 5 empty >>
text " 10 " ## radioButton rptF 10 empty >>
text " 20 " ## radioButton rptF 20 empty >>
radioError rptF)
...
Wie füge ich in mein WASH/CGI-Programm ein Bild ein?
Auch dazu gibt es passende Funktionen. Zu allererst musst Du Dir mit Hilfe von externalImage ein Bild-Objekt erzeugen. Dieses kann dann entweder einfach in eine Seite integriert werden (mit Hilfe von makeImg) oder als aktive, klickbare Image-Map fungieren.
Ersteres funktioniert beispielsweise wie folgt:
import CGI
main = run $ standardQuery "My Picture!" $
do p $ text "Hello, here comes a picture:"
img <- externalImage "myPic.jpg" "Sorry, I don't know how to display the pic!"
makeImg img empty
Für letzteres benötigst Du die Funktion imageField. Auch hierzu ein Beispiel:
import CGI
main = run $ standardQuery "My Clickable Picture!" $
do p $ text "Helle, here comes a clickable picture. Please click on me:"
img <- externalImage "myPic.jpg" "sorry, no pic"
activate continueAction (imageField imgH) empty
continueAction (x, y) =
standardQuery "My Clickable Picture!" $
p $ text $ "You pressed on me at postition " ++ (show x) ++ "/" ++ (show y)
Damit der Webserver die Bilder findest, musst Du sie entweder in Deinem Webserver-Verzeichnis unter .../htdocs ablegen oder explizit eine (externe) URL angeben.
Wie benutze ich den wash2hs-Präprozessor, um .wash-Dateien zu transformieren?
Damit der Päprozessor zur Verfügung steht, muss zuerst folgender Setup-Befehlsetup internet
ausgeführt werden. Um nun ein .wash-Datei (etwa Calculator.wash) nach Haskell zu transformieren, wird das Tool wash2hs mit der .wash-Datei als Eingabe aufgerufen (wichtig ist, beim Aufruf den Dateinamen ohne Dateiendung anzugeben):
wash2hs Calculator
Der Päprozessor erzeugt daraus eine reguläre Haskell-Datei (im Beispiel Calculator.hs), die anschließend normal übersetzt werden kann:
ghc --make -package wash Calculator.hs
Auf welchen Rechnern kann ich den Web-Server starten?
Der Web-Server läuft nur auf den Solaris-Maschinen in den Computerpools. Solltest Du nicht direkt vor solch einem Rechner sitzen, kannst Du Dich auch per SSH auf einen dieser Rechner einloggen. Hier sind einige Namen von Pool-Rechnern: leo, merkur, neptun, ...
Welchen Setup-Befehl muss ich ausführen, damit ich den Web-Server starten kann?
Um den Web-Server unserer Vorlesung starten zu können, mußt Du den folgenden Setupbefehl ausführen:
setup internetAm besten legt man diesen Befehl in der Datei .login ab, damit das Setup bei jedem Neustart automatisch ausgeführt wird.
Wie starte ich dann den Web-Server?
Du startest den Web-Server mit folgendem Befehl:
start-apache
Das Start-Skript gibt dann Informationen darüber aus, wie der Web-Server zu erreichen ist:
Der Server ist unter http://leo.informatik.uni-freiburg.de:8000/ zu erreichen CGI-Programme beginnen mit der URL http://leo.informatik.uni-freiburg.de:8000/cgi-bin/ Das Verzeichnis für CGI-Programme: /home/hugo/internet-2003-apache/cgi-bin Das Verzeichnis für HTML-Dokumente: /home/hugo/internet-2003-apache/htdocs Das Verzeichnis für Log-Dateien: /home/hugo/internet-2003-apache/logs Der Web-Server ist gestartet. Ctrl-C beendet den Web-Server wieder.
Dieses Start-Skript legt beim ersten Start in deinem Home-Verzeichnis einige Verzeichnisse an, die zum Betrieb des Web-Servers notwendig sind.
Hilfe, das Starten des Servers schlug fehl! Warum bloß?
Das Startskript brach sofort ab und antwortete:
Das Starten des Web-Servers schlug fehl!
Zuerst sollte man herausfinden, was eigentlich schief gelaufen ist: Genauere Informationen enthalten die Log-Dateien des Web-Servers. Diese Dateien finden sich im Verzeichnis ~/internet-2003-apache/logs. Lautet der Eintrag etwa
[Thu May 8 15:41:27 2003] [crit] (125)Address already in use: make_sock: could not bind to port 8000
bedeutet das, dass jemand anderes die angeforderte Portnummer schon blockiert. Du kannst in einem solchen Fall entweder den Rechner wechseln, oder den Server mit
start-apache --port nnnnexplizit unter einer anderen Portnummer starten.
Der Server läuft nun. Wie kann ich etwas sinnvolles damit tun?
Es gibt zwei sinnvolle Sachen, die man mit diesem Server machen kann: HTML-Dokumente für die Clients dort ablegen und CGI-Programme ablaufen lassen.
HTML-Dokumente müssen in einem bestimmten Verzeichnis gespeichert sein, damit der Server diese finden kann. Dieses Verzeichnis befindet sich in deinem Home-Verzeichnis unter ~/internet-2003-apache/htdocs. Dort liegt bereits eine Datei index.html, die nur zu Testzwecken dient (kann also gelöscht werden).
CGI-Programme müssen im Verzeichnis ~/internet-2003-apache/cgi-bin liegen und die Attribute müssen auf ausführbar gesetzt sein. Ein CGI-Programm (z.B. CGITest) wird vom Web-Server ausgeführt, sobald ein Client (d.h. etwa ein Browser) die passende URL anfordert (z.B. http://leo.informatik.uni-freiburg.de:8000/cgi-bin/CGITest). Das Web-Server-Startskript gibt an, unter welcher URL CGI-Programme bei Dir zu finden sind.
Mein CGI-Programm funktioniert nicht!
Ein CGI-Programm muss als ausführbar markiert sein, sonst wird es vom Web-Server nicht ausgeführt. Eine Datei kann mit chmod +x mein-erstes-cgi als ausführbar markiert werden.
Zuerst sollte man herausfinden, was eigentlich schief gelaufen ist: Genauere Informationen enthalten die Log-Dateien des Web-Servers. Diese Dateien finden sich im Verzeichnis ~/internet-2003-apache/logs. Es empfiehlt sich den Web-Server zu beenden, bevor man in die Log-Dateien schaut: unter Umständen ist erst dann der aktuellste Eintrag zu sehen. Die neusten Einträge sind am Ende zu finden.
In der Log-Datei steht immer nur "premature end of script headers" -- was soll das heissen?
Das CGI-Programm hat keine, oder eine unverständliche Ausgabe gemacht, die der Web-Server nicht verstehen kann. In diesem Fall solltest du das CGI-Programm mal außerhalb des Web-Servers testen und den Aufruf durch den Web-Server simulieren. D.h. die benötigten Environment-Variablen mit setenv von Hand setzen.
Ich werde aus der Log-Datei nicht schlau!
Wende Dich an Matthias Neubauer.
Wie kann ich meine XPath-Ausdrücke auf ein XML-Dokument loslassen?
In den Computerpools sind zwei System, qexo und galax, installiert. Beides sind Interpreter für die XML-Anfragesprache XQuery. Da XQuery eine Obermenge von XPath ist, kannst Du damit auch Deine XPath-Ausdrücke testen.
Welchen Setup-Befehl muss ich ausführen, damit ich qexo oder galax starten kann?
Du musst den folgenden Setupbefehl ausführen:
setup internetAm besten legt man diesen Befehl in der Datei .login ab, damit das Setup bei jedem Neustart automatisch ausgeführt wird.
Wie sieht den dann beispielsweise eine XPath-Anfrage an ein XML-Dokument aus?
Du musst zuerst das Dokument mit Hilfe des document-Befehls und direkt anschliessend den XPath-Ausdruck, mit dem Du auf das Dokument zugreifen willst, angeben.
Willst Du etwa aus dem XML-Dokument hamlet.xml aus dem Stück den Titel aller Personen herausfinden, sieht die Anfrage wiefolgt aus:
document("hamlet.xml")/PLAY/PERSONAE/TITLE
Die Antwort des Systems ist die passende Knotenmenge, die hier nur aus einem TITLE-Knoten besteht.
Für Galax musst Du deine XPath-Anfrage in eine Datei, etwa in my_query.xq, schreiben. Du startest Galax mit Deiner Anfrage mit folgendem Befehl:
galax my_query.xq
Danach antwortet das System mit der passenden Knotenmenge, die hier nur aus einem TITLE-Knoten besteht.
<TITLE>Dramatis Personae</TITLE>
Wie arbeite ich mit dem Qexo-Interperter?
Du startest den Interpreter mit folgendem Befehl:
qexo
Danach siehst Du die Interpreter-Eingabezeile, von der aus Du direkt XPath-Anfrage stellen kannst.
{--1--} document("hamlet.xml")/PLAY/PERSONAE/TITLE
<TITLE>Dramatis Personae</TITLE>
Die Antwort des Systems ist die passende Knotenmenge, die hier nur aus einem TITLE-Knoten besteht.
Qexo ist ziemlich neu und implementiert (noch) nicht alle XPath-Funktionen. Beispielsweise ist die Funktion last() noch nicht vorhanden. Welche Funktion vorhanden sind, musst Du etwa auf der Qexo-Homepage nachlesen.
Wie arbeite ich in den Computerpools Pool-Rechnern mit HaXml?
HaXml funktioniert in den Pool-Raeumen nur mit dem GHC (nicht mit GHCi oder hugs)!
Hast Du ein Haskell-Programm geschrieben, das die generischen HaXml-Kombinatoren benutzt, musst Du es also mit dem GHC kompilieren. Die HaXml-Bibliothek steckt in dem Package HaXml. D.h. um ein Haskell/HaXml-Programm MyHaXmlCode.hs zu übersetzen, musst Du GHC wiefolgt aufrufen:
ghc --make -package HaXml MyHaXmlCode.hs
Eine Übersicht über die HaXml-Kombinatoren und Beispielprogramme dazu findest Du in dem dazugehörigen ICFP99-Artikel in den Abschnitten 2.2 und 2.3. Beachte, dass sich bei der installierten Version kleine Details gegenüber der Artikel-Version geändert haben. Das Import-Statement für HaXml lautet nun import Text.XML.HaXml; die aktuelle HaXml-API findest Du hier.
Willst Du hingegen eine DTD in einen speziell angepassten HaXml-Haskell-Datentyp übersetzen, musst Du das Tool DtdToHaskell benutzen (nachdem Du setup internet ausgeführt hast):
DtdToHaskell play.dtd > PlayDtd.hs
Um dann diesen Datentyp zu benutzen, musst Du erst den Modulnamen an den Dateinamen angleichen (hier etwa module PlayDtd where ... in PlayDtd.hs), um dann das generierte Modul mit dem erzeugten Datentyp (hier mit import PlayDtd) in Dein eigenes Programm zu importieren. Wie du ein XML-Dokument in ein Objekt dieses Datentyps einliest, steht hier. Mehr dazu findest Du auch im ICFP99-Artikel unter Abschnitt 3.
Was ist denn nun XSL? Warum heisst das manchmal auch XSLT? Und dann wieder auch mal XSL-FO? Komisch!
Die Extensible Stylesheet Language (XSL) besteht eigentlich aus drei verschiedenen Dingen:
Das typische (aber nicht zwingende) Anwendungsszenario für XSL ist also die Definition eines XSLT-Stylesheets, das eine bestimmte Klasse von XML-Dokumenten (wie etwa bei uns Theaterstücke der DTD play.dtd) in druckbare, formatierte (XSL-FO-)Dokumente übersetzt.
Wie transformiere ich ein XML-Dokument mit Hilfe eines XSLT-Style-Sheets?
Du hast zwei Möglichkeiten bei uns in den Computerpools ein XML-Dokument per XSLT zu tranformieren.
Zum einen ist die XSLT-Implementierung Xalan-J in unseren Computerpools installiert (zumindest nachdem man setup internet ausgeführt hat). Mit diesem Tool lassen sich XML-Dateien in andere XML-Dateien mit Hilfe eines XSLT-Style-Sheets von der Kommandozeile aus tranformieren. Der Aufruf
xalan-j -XSL my_style_sheet.xsl -IN input.xml -OUT output.fo
etwa konvertiert die Eingabe-Datei input.xml in ein FO-Dokument output.fo mit Hilfe eines Style-Sheets my_style_sheet.xsl.
Zum anderen ist in neueren Webbrowsern wie etwa in Mozilla und in X-Smiles eine XSLT-Implementierung integriert. Sieht der Browser eine XML-Datei, die zu Beginn eine passende Verarbeitungszeile wie etwa
<?xml-stylesheet type="text/xsl" href="http://www.foo.com/my_style_sheet.xsl"?>
zeigt der Browser nicht die eigentliche Datei an, sondern das Ergebnis der XSLT-Transformation mit dem angebenen Style-Sheet.
Beachte aber, dass Mozilla und der Internet Explorer bisher noch keine XSL-FO-Dokumente anzeigen können.
Du hast zwei Möglichkeiten bei uns in den Computerpools ein Dokument (wie beispielsweise das Dokument fo-example.fo), das per XSL-FO formatiert wurde, zu betrachten.
Zum einen ist der XSL-FO-Renderer Fop in unseren Computerpools installiert (zumindest nachdem man setup internet ausgeführt hat). Mit diesem Tool lassen sich FO-Dateien in andere druckbare Formate wie etwa Postscript oder PDF konvertieren. Obige Beispielsdatei wird damit etwa wie folgt ins PDF konvertiert:
Fop fo-example.fo fo-example.pdf
Zum anderen steht der Webbrowser X-Smiles zur Verfügung. Dieser Browser kann selber XSL-FO darstellen. Nachdem der Browser mit
xsmiles
gestartet wurde, kann man direkt URLs angeben, die entweder XSL-FO-Dokumnte enthalten, oder nach einer XSLT-Transformation XSL-FO ergeben (Möglicherweise versteht aber X-Smiles weniger XSL-FO als etwa Fop.)
Genau. Ist aber gar nicht so schwer! Am einfachsten ist es dazu, das Ausgangsdokument zweimal hintereinander zu durchlaufen!
Beim ersten Durchlauf wird das Inhaltsverzeichnis erstellt. Für jedes relevante Ausgangselement wird ein passendes Verweiselement generiert (in XHTML etwa durch ein <a href="#...">...</a>, in XSL-FO etwa mit Hilfe eines basic-link-Elements). Um nun einen passenden Namen für die Zielposition zu bekommen, gibt es die XSLT-Funktion generate-id, die garantiert, dass bei jeder Transformation für einen bestimmten Ausgangsknoten bei mehrmaligen Anwendungen immer identische Namen generiert werden (siehe dazu auch hier).
Beim zweiten Durchlauf wird der normale Dokumentinhalt erzeugt. Hierbei müssen die Stellen, an die ein Inhaltsverzeichnisverweis zeigen soll, wieder durch Namen markiert werden (in XHTML etwa durch ein <a name="...">...</a>, in XSL-FO etwa mit Hilfe eines id-Elements). Wieder wird der Name der Zielposition mit Hilfe von generate-id erzeugt. Die Spezifikation von generate-id garantieren nun, dass beim Erzeugen des Links und beim Erzeugen der Zielposition identische Namen generiert werden.
Wie sieht denn beispielsweise eine Webseite aus, die ein XForms-Formular enthält?
Die folgende Webseite xforms-example.xhtml enthält solch ein Formular. Die dabei verwendete Datenmodell-Definition benutzt die externe XML-Scheme-Definition payschema.xsd.
Beachte, dass Du zur vernüftigen Betrachtung dieser Webseite einen XForms-fähigen Webbrowser benötigst. In unseren Poolräumen steht dazu der Browser X-Smiles zur Verfügung. Der Browser wird bei uns mit dem Befehl
xsmiles
gestartet.