Bitte Beachten: Dieser Blog wird hier nicht weiter geführt! Kommentare und neue Blogposts gibt es unter http://feitel.indeedgeek.de. Dort bitte auch neue Kommentare zu diesen Beiträge erstellen.

Posts mit dem Label Programmierung werden angezeigt. Alle Posts anzeigen
Posts mit dem Label Programmierung werden angezeigt. Alle Posts anzeigen

Sonntag, 25. Mai 2008

Wissenswertes über Erlang(en)

Viele haben es ja schon mitbekommen, aber nun noch mal für den Rest der Welt: Ich habe ein neues Spielzeug gefunden! Ich bin vor kurzem durch eine Folge im Chaosradio auf die Programmiersprache Erlang gestoßen. Erlang lässt sich mit nur einem Wort beschreiben: anders (alternativ auch genial ;-).

Erlang bezeichnet sich selbst als COPL (Concurrency Oriented Programming Language). Diese Bezeichnung halte ich für sehr treffend. In Objektorientierten Sprachen dreht sich alles um Objekte und in Erlang sind es statt dessen die Prozesse. Ich habe vor kurzem einen Artikel gelesen, in dem es darum ging, dass Prozesse in Erlang doch im Grunde nichts anderes als Objekte in anderen Sprachen sind. Ich halte diesen Vergleich etwas unglücklich, da Erlang eine Funktionale Sprache ist. Am ehesten erinnerte mich Erlang an Prolog, da ich hier auch eine Art Regeln spezifizieren kann und es möglich ist darauf durch Abfragen über Matching zu zugreifen. Allerdings ist Erlang weitaus mächtiger.

Erlang wurde ursprünglich 1987 von Ericsson für Telefonanlagen entwickelt. Und ich muss sagen sie haben sehr gute Arbeit geleistet. Nichts passt besser zu Telefonanlagen als Erlang: In Erlang läuft so gut wie alles parallel. Die Prozesse sind so leichtgewichtig das auch mehrere tausende gleichzeitig von der Performance kaum Auswirkungen haben. Erlang ist hochgradig Fehlertolerant: Sollte ein Prozess abstürzen kann ein anderer diesen einfach neu starten. Und zu guter Letzt: Es ist in Erlang einfach möglich während der Laufzeit Codeteile durch neue zu ersetzen; ohne Unterbrechung der Ausführung.

Durch die Entwicklung in der Praxis ist Erlang keine akademische Sprache sondern (relativ) oft in freier Wildbahn anzutreffen. Spätestens nach der Open Source Variante, die 1998 veröffentlicht wurde, stieg die Beliebtheit rasch an. So gibt es heute eine Vielzahl von Projekten die auf Erlang aufbauen: Das berühmteste Beispiel ist wohl ejabberd ein Jabberserver. Außerdem setzt Amazon mit ihrer SimpleDB ebenso wie Facebook auf Erlang. Es gibt einen hochperformanten Webserver namens yaws und einem gigantischen Benchmark-Tool (Tsung) für z.B. Webserver. Außerdem gibt es eine Datenbank namens Mnesia die sich perfekt in Erlang integriert. Man hat so keine Tabellen mehr wie in Relationale Datenbanken sondern nutzt die Erlang eigenen Datentypen um mit der Datenbank zu kommunizieren. Außerdem kann Mnesia wie eigentlich alles in Erlang verteilt laufen so das es möglich wäre, auf einem Computer die DB im RAM (für Performance) zu halten und auf einem anderen die Datenbank auf Festplatte als Backup. Eine weitere Datenbank die auf Erlang setzt ist couchdb die Dokumenten-orientiert arbeitet.

Doch was macht Erlang so besonders? Programmiert man in einer der üblichen Sprachen macht man immer ein neuen Thread auf wenn man muss. In Erlang macht man immer einen neuen Prozess auf sobald man kann. So ist es theoretisch möglich das ein Erlang Programm auf einer Maschine mit 16 Prozessoren 16 mal schneller als auf einem Prozessor läuft. Die Prozesse in Erlang sind extrem leicht zu erstellen (sowohl von der Syntax als auch von dem Ressourcenverbrauch). Es ist möglich in Millisekunden einfach so mal 30000 Prozesse zu erstellen und wieder zu beenden ohne das die Prozessorauslastung sonderlich steigt.

Threads? Prozesse? Was den nun? So gut wie alle Sprachen bieten Threads für Nebenläufigkeiten an. Erlang hingegen nur Prozesse. Der Hauptunterschied ist: Threads haben gemeinsam genutzten Speicher. Dadurch kommt es immer wieder zu Verklemmungen und Konflikte. Ein Hoher Aufwand muss betrieben werden um dies zu vermeiden. In Erlang geht man einen anderen Weg: Dort hat jeder Prozess exklusiv einen festen Speicher. Kein anderer Prozess kann ihm dazwischen funken. Die Prozesse kommunizieren untereinander durch einfache Nachrichten. Durch dieses Prinzip ist es zum Beispiel möglich ein Programm (fast) ohne umschreiben von einer Einplatzlösung auf mehrere Computer zu verteilen so dass die Programmteile miteinander kommunizieren.

Der andere Vorteil von Erlang ist seine Eigenschaft als funktionale Sprache. So gut wie alle Probleme werden Rekursiv gelöst weshalb es auch keine Schleifen gibt. Außerdem kann man Funktionen einfach in Variablen speichern wodurch man gigantische Möglichkeiten bekommt. Am Anfang muss man sich doch sehr daran gewöhnen das Variablen, die einmal gesetzt wurden nicht wieder überschrieben werden können. Erlang arbeitet nur mit simplen Datentypen wie Tupeln und Listen, allerdings ist die Sprache so gebaut das diese einfachen Konstrukte vollkommen ausreichen. Und das Beste von allem: Der Index dieser Datentypen fängt bei 1 an! (und nicht wie üblich bei 0)

Die Sprache eignet sich perfekt für Serveranwendungen auf die gleichzeitig viele Benutzer zugreifen, allerdings lässt sich auch das tk-GUI Toolkit verwenden wodurch GUI Anwendungen ebenso möglich sind. Sogar ein 3D-Modellierer namens Wings3D ist in Erlang geschrieben.

Wer mehr über Erlang wissen will, dem kann ich nur die oben schon verlinkte Chaosradio-Sendung empfehlen. Die Dokumentation findet Großteils in man-Pages statt, was für Viele etwas ungewohnt ist. Allerdings findet man auch ein sehr gutes Tutorial welches einem die Konzepte näher bringt. Zu der Entstehung der Sprache existiert ein großartiges Video welches ihr unbedingt anschauen müsst! Und zu Letzt muss ich noch unbedingt das Outro von Chaosradio verlinken welches auch ein Grund war die Sprache zu lernen (Foyer des Arts - Wissenswertes über Erlangen).

Ich dachte mir, evtl. besteht ja Interesse an einem kleinen Tutorial hier auf diesem Blog? Wenn ihr sagt euch würde die Sprache interessieren starte ich mal den Versuch ein kleines Tutorial über Erlang zu schreiben.

Montag, 7. April 2008

Ich kann nicht Programmieren...

... zumindest kam es mir letzte Woche so vor. Für eine Vorlesung musste ich einen (bis jetzt) vereinfachten Single Sign On - Server schreiben. Da wie gesagt dies bisher nur ein Prototyp ist, war die Implementierung relativ simpel und die Anwendung lief recht schnell. Die nächste Aufgabe bestand daraus den Prototyp mit vorgegebenen Unittests zu testen.

GRAUSAM! Ich dachte naja, zwei drei Tests werden wohl rot sein, aber das gut 60% fehlschlägt hat mich dann doch sehr betroffen gemacht. Die meisten Fehler waren einfach eine fehlende Überprüfung auf Null oder eine vergessene Negation in einer If Abfrage. Ich habe mich darauf hin gleich gefragt warum die Tests so schlecht ausgefallen sind bzw. was man besser machen könnte um solche Fehler zu vermeiden.

Natürlich Methoden wie Unittests, Design by Contract, TDD, etc. sind in aller Munde aber mal ehrlich, nutzt ihr die bei jedem Projekt so ausführlich wie man sollte? Ich bestreite nicht den Nutzen dieser Techniken, auch bin ich mir durchaus benutzt das diese bzw. vergleichbare Techniken in ernsthaften Projekten unabdingbar sind aber ich finde es sind immer noch zu viele Barrieren vorhanden so das die Techniken wirklich Sinn geben. Ich weiß nicht genau wie ich sagen soll, deshalb gibt es mal paar Beispiele:

Ein Unittest ist schnell geschrieben, aber man muss ja prinzipiell jede Mögliche Kombination von Input und Output Werten bzw. auch an Zuständen im System Abdecken. Ein enges System aus Unittests halte ich für sehr sinnvoll, aber wenn sich beispielsweise ein Nutzer mit Name und Passwort an einem Server anmelden will. Was muss ich alles Testen? Was ist wenn Nutzername und/oder Passwort Null sind? Was ist wenn Nutzer und/oder Passwort falsch sind? Was ist wenn der Server nicht erreichbar ist? Was ist wenn der Nutzer bereits angemeldet ist? Was ist wenn der Nutzer überhaupt nicht im System vorhanden ist? Was ist wenn Nutzername und/oder Passwort ein Leerer String sind? Und so weiter. Mir fällt bestimmt noch mehr ein. Und das nur für einen Vorgang! Man stelle sich das mal in einem Komplexen System vor! Außerdem ergibt sich früher oder später die otwendigkeit von Mock Objekten und GUI Tests. Diese Spirale dreht sich so lange hoch bis ich irgendwann mehr Testcode als produktiven Code habe. Und genau hier tritt für mich ein Widerspruch zwischen Theorie und Praxis auf. Ich weiß das es gut ist, ich weiß das man es braucht, aber ich habe keine Ahnung wie man sinnvoll (sowohl logistisch als auch zeitlich) alle Fälle abdecken soll.

Andere Möglichkeit sind Pre- Postconditons. Hier tritt, außer dem Umfangproblem der Unittests, auch noch das Problem der Mangelnden Verbreitung auf. Die Sprachen, die ich mehr oder weniger kenne (Java, Ruby, C(++), Python, Bash, Prolog(:D), ...) bringen Conditions von Haus aus nicht mit. Deshalb bleiben mir zwei Möglichkeiten: Entweder ich lade mir eine zusätzliche Erweiterung der Programmiersprache dazu (was leider wieder andere Probleme wie Kompatibilität und Overhead mit sich bringt) oder ich arbeite mit Asserts. Bringt meine Sprache der Wahl schon Asserts mit habe ich es etwas einfacher. Ansonsten muss man sich erst eine Assert Funktion schreiben (wofür es gerade in Ruby einige sehr schöne Ansätze gibt!). Das Problem was ich hier wieder sehe ist einfach das es keine gezielte Notation der Conditions gibt. Ich habe einfach am Anfang (oder auch am Ende) meiner Funktion 10,20 oder auch mehr Zeilen mit Assert Statements die alle Fälle die mir einfallen abdecken sollten. Blöd nur wenn die Methode selbst nur 5 Zeilen hat. Besser wäre ein Ansatz mit dem man durch die Erweiterung der Sprache eine gesonderte Syntax innerhalb des Methodenkopfes einführt. Und selbst da macht einem die Vielzahl der Möglichen Input- und Output-Werte Kopfzerbrechen.

Ich habe jetzt mal die beiden Methoden herausgegriffen und meine Bedenken dazu geäußert. Ich komme aber irgendwie aus diesem Widerspruch nicht heraus. Um robusten Code zu schreiben muss ich alle Fälle abdecken, wenn ich alle Fälle abdecke, komme ich nicht dazu Code zu schreiben. Klar der Mittelweg ist der beste, aber das dachte ich bei meinem Projekt auch und dann waren doch 60% rot. Es muss doch eine Möglichkeit geben schnell und kurz während ich die Methode schreibe alle Fälle auszuschließen die nicht auftreten dürfen. So eine Technik kann nur funktionieren wenn sei nebenbei, schon fast automatisch, abläuft (sei es nun wirklich automatisiert oder einfach nur durch den Autor). Aber irgendwie kämpfen die Methoden die mir spontan einfallen alle mit massig Overhead.

Wie handhabt ihr das? Schon mal TDD versucht? Geht es euch Ähnlich? Ich bin für Vorschläge dankbar!

Montag, 31. März 2008

Kurzes Statement an alle Programmierer

Aus aktuellem Anlass 3 kurze Statements an alle Programmierer da draußen:

Wenn ihr glaubt man kann durch das Einsetzten toller Tools, Editoren oder Libraries bessere Programme schreiben, dann könnt ihr nicht programmieren!
Ich sehe es an Eclipse: IntelliSense, Projektverwaltung, Wizards alles schön und gut, ich arbeite trotzdem ungern damit! Eclipse hat tausend Einstellungsmöglichkeiten aber versucht mal die Einrückung auf zwei Leerzeichen einzustellen. Warum zum Teufel mach ich tausend Menüpunkte statt einer Configdatei? Warum muss ich eine halbe Minute warten bis ich Eclipse gestartet hab nur um zu schauen ob Änderungen im SVN sind? VIM ist für mich mit Abstand der beste Editor obwohl ich ihn bei weitem noch nicht so bedienen kann wie ich es gerne hätte. Dort ist einfach alles einstellbar, so wie es für jeden richtig ist. Für was brauche ich IntelliSense wenn ich eine Doku hab? Für was brauche ich ein SVN-Plugin wenn ich svn als Programm habe? Eclipse steht nur im Weg wenn man programmiert. Sicher Programmiert es sich so schneller weil man einfach nur seinen Methodennamen vervollständigen lassen muss aber genau in dem Moment gibt man das Denken an die IDE ab. Bevor ich in Eclipse ein Projekt erstellt habe bin ich mit VIM fertig.
Wenn ihr glaubt man kann aus einem UML-Diagramm, einem GUI-Designer oder Anderweitig auch nur Programmteile zusammenklicken, dann konntet ihr noch nie programmieren!
Programmieren hat zu einem kleinen Teil etwas mit einer planbaren Engineeringtätigkeit zu tun. Der Rest besteht aus Erfahrung und einem künstlerischem Prozess. Mit Kunst meine ich keine schöne GUI sondern viel mehr ist es eine Art von künstlerischem Ausdruck. Etwas, das durchaus mit dem spielen eines Instrumentes gleich kommt. KEIN Editor schafft es dies durch ein paar Code-Templates die man im Hintergrund platziert zu komprimieren. Sie gaukeln nur ein schnelles Ergebnis vor bis man ein gewissen Detailgrad erreicht hat und dann kommt der GAU weil der Editor nur noch im Weg steht und komplexe Dinge nicht mehr möglich sind.
Wenn ihr glaub, man muss nicht stetig weiterlernen, dann werdet ihr auch nie programmieren können!
Je nach dem was man als Programmiersprache definiert sollten zwei neue Sprachen im Jahr locker drinnen sein. Jeden Monat ein Buch zu einem neuen Thema sollte auch möglich sein. Bevor man sich mit einem neuen Thema beschäftigt => Buch lesen. Bevor man versucht etwas durch probieren zum laufen zu bekommen => Buch lesen. Ziel ist es zu wissen was man tut und nicht Beispiele aus einem Online Tutorial herauszukopieren und zu hoffen das es Funktioniert!

Schlechte Programme sind das Übel der Neuzeit. Früher gab es die Pest, heute reichen dazu ein paar Programmierer. Programmieren hat nichts mit Syntax zu tun. Programmieren ist einfach ein Gefühl welches sich durch Code ausdrückt!

Anmerkung: Das ganze ist natürlich (mal wieder) übertrieben und nicht ganz ernst gemeint. Deswegen ist es leider nicht weniger wahr.

Samstag, 15. März 2008

ssh-agent Startscript

Nachdem ich zufällig auf einige interessante Möglichkeiten von SSH gestoßen bin entschloss ich mich vor meinen Semesterferien das Buch "SSH, The Secure Shell" auszuleihen. Ich muss sagen wenn dieses Buch ist schon fast die SSH Spezifikation. Dort steht wirklich alles drinnen. Zum Lesen völlig ungeeignet allerdings für Ideen und zum Nachblättern gar nicht mal so schlecht.

So bin ich auch auf Authentifikation über SSH-Keys gestoßen. Normalerweise gibt man sein Passwort ein, wenn man sich auf einem Server anmeldet. SSH-Keys funktionieren allerdings eher wie GPG Verschlüsselung. Man generiert einen Public Key, den man auf allen Servern, bei denen man sich anmelden möchte, hinterlegt. Außerdem noch einen Privat Key mit dem sich jeder, bei allen Server auf denen der Public Key hinterlegt ist, anmelden kann. Das funktioniert einfach, in dem der Server mit dem Public-Key eine Challenge verschlüsselt und diese dem Client zusendet. Dieser entschlüsselt die Nachricht wieder und sendet es zurück. Stimmen die Nachrichten beim Server überein wird der Nutzer rein gelassen. Allerdings kann dann wie schon gesagt jeder der den Private Key kennt auch auf den Server zugreifen. Um dies etwas zu erschweren kann man ein Passwort definieren das für den Privaten Key benötigt wird. Man sieht schon, im Grunde nichts anderes als GPG. In Wirklichkeit kann man sogar (mit den nötigen Einstellungen) seinen GPG Key dafür nutzen.

Nun hat man zwar SSH-Key, allerdings darf man nun wieder jedes mal sein Passwort eingeben. Man hat also nichts gewonnen. Dafür gibt es nun (ebenfalls aus der GPG-Welt bekannt) einen Agent, der die Key einmal lädt, dabei das Passwort abfragt und anschließend auf Anfragen reagiert. So muss man nicht jedes mal neu sein Passwort eingeben. Sehr praktische Geschichte! Leider ist die Syntax etwas umständlich und auch muss jeder Key einzeln geladen werden. Deshalb habe ich mir etwas den Kopf zerbrochen wie man dies automatisieren kann. Dabei ist das folgende Script entstanden. Es schaut bei jedem Aufruf (in der Regel sobald sich eine neue Shell öffnet) ob der SSH-Agent läuft. Außerdem lädt es die Keys Automatisch so das man nur noch die Passwörter eingeben muss. Außerdem ermöglicht es über die beiden Funktionen start-ssh-agent und load-ssh-indentities dies auch Manuell durchzuführen.

Um zu verstehen wie das Script funktioniert muss man sich erst einmal anschauen wie der ssh-agent bekannt gibt es es ihn gibt. Ruft man ihn einfach mit dem ssh-agent Kommando auf (was der falsche Weg ist), erscheinen einige Variablen. Diese Variablen sind wichtig damit der ssh-Client auf den Agent zugreifen kann. Der Agent selbst läuft vom Terminal losgelöst. Er wird also nicht beendet wenn die Session beendet wird sondern läuft im Hintergrund weiter. Einer der richtigen Wege (es gibt mehrere Möglichkeiten) wäre eval `ssh-agent`. Hier wird zuerst der ssh-agent aufgerufen und die Ausgaben ausgeführt. So werden die Variablen im aktuellen Terminal bekannt gegeben. Möchte man nun auch in einer anderen Session auf den Agent zugreifen muss man dort auch einfach nur die Variablen bekannt machen. Da der Agent wie gesagt unabhängig von der aktuellen Session läuft interessiert es nicht aus welcher Shell er gestartet wurde.

$ ssh-agent
SSH_AUTH_SOCK=/tmp/ssh-Yvpvk13029/agent.13029; export SSH_AUTH_SOCK;
SSH_AGENT_PID=13045; export SSH_AGENT_PID;
echo Agent pid 13045;

Mein kleines Script macht im Grunde nichts anderes als diese Variablen in einer Datei zu speichern und bei jedem anmelden diese einzulesen. Außerdem lädt es alle Identitäten die in in dem Verzeichnis ~/.ssh/keys/ liegen. Wichtig ist, dass dort nur Private-Keys liegen dürfen; keine Public-Keys!

# Lädt alle Indentitäten im Verzeichnis ~/.ssh/keys/
function load-ssh-indentities() {
  # Prüft erst ob ein tty verfügbar ist
  if tty -s ; then
    for FILE in ~/.ssh/keys/* ; do
      ssh-add $FILE
    done
  fi
}

# Startet den SSH-Agent mit einer Indentitäten-Lifetime von einer Stunde
# und schreibt die Variablen in die ~/.ssh_agent_vars Datei
function start-ssh-agent() {
  eval `ssh-agent -s -t 3600`
  echo -e "export SSH_AGENT_PID=$SSH_AGENT_PID\nexport SSH_AUTH_SOCK=$SSH_AUTH_SOCK" > ~/.ssh_agent_vars \
  && echo "SSH-Agent gestartet"
}

# Fragt erst nach ob Indentitäten geladern werden sollen.
function _ask_load_ssh_indentities() {
  echo "Indentitäten laden? (y/N)"
  read ANSWER
  ( [ "$ANSWER" = "y" ] || [ "$ANSWER" = "Y" ] ) && load-ssh-Indentitäten
}

# Die Variable SSH_AUTH_SOCK wird für den SSH-Agent benötigt und sollte gesetzt sein wenn er läuft.
if [ "$SSH_AUTH_SOCK" = "" ]; then
  # Es wird geprüft ob die Datei ~/.ssh_agent_vars vorhanden ist.
  if [ -f ~/.ssh_agent_vars ]; then
    # Wenn ja wird diese Includiert und geprüft ob die Variablen darin noch gültig sind.
    source ~/.ssh_agent_vars

    # Prüft ob es ein ssh-agent Prozess mit der PID gibt und das Socket existiert.
    if ( ps -p $SSH_AGENT_PID  | grep -q ssh-agent ) && [ -e $SSH_AUTH_SOCK ]; then
      # SSH-Agent ist gestartet Vorhandene Indentitäten werden angezeigt.
      echo "SSH-Agent ist bereits gestartet Geladene Indentitäten:"
      ssh-add -l
    else
      # Die Variablen sind nicht mehr gültig. Datei wird gelöscht und SSH-Agent wird neu gestartet.
      echo "~/.ssh_agent_vars Datei existiert aber es ist kein Agent gestartet. Starte Agent."
      rm ~/.ssh_agent_vars
      start-ssh-agent && _ask_load_ssh_indentities
    fi
  else
    # Datei ist nicht vorhanden. Es wird geprüft ob SSH-Agent läuft.
    if ps u -C ssh-agent | grep -q ssh-agent ; then 
      echo "SSH-Agent läuft aber es existiert keine ~/.ssh_agent_vars Datei!"
      echo "SSH-Agent muss gestoppt werden und mit 'start-ssh-agent' neu gestartet"
      # Weiteres Vorgehen dem User überlassen
    else
      # Wenn nicht wird er gestartet.
      start-ssh-agent && _ask_load_ssh_indentities
    fi
  fi
else
  # Variable ist schon gesetzt. Es wird geprüft ob SSH-Agent auch wirklich läuft
  if ps u -C ssh-agent | grep -q ssh-agent ; then 
    echo "SSH-Agent ist bereits gestartet. Geladen Indentitäten:"
    ssh-add -l
  else
    # Tritt nur bei Subshells auf, wenn die Variable in die Subshell
    # übergeben wurde obwohl der Agent schon beendet wurde.
    echo "SSH_AUTH_SOCK Variable ist gesetzt aber SSH-Agent läuft nicht!"
    # Weiteres Vorgehen dem User überlassen
  fi
fi

Das Script muss am besten in die Datei ~/.bashrc eingefügt werden. Dabei ist wichtig, dass dies nicht in einer Subshell ausgeführt werden darf. Deshalb direkt in die bashrc kopieren oder mittels source einbinden. Da es sonst Namespace-Probleme mit Variablen und Funktionen gibt darf es nicht über exec eingebunden werden (deshalb auch kein #!/bin/bash am Anfang).

Für Kritik und Verbesserungsvorschläge bin ich immer offen. Getestet habe ich das Script bis jetzt mit Bash 3.2.17 und zsh 4.3.4. Einziger Kritikpunkt den ich schon jetzt von meiner Seite habe: Das Script merkt natürlich nicht wenn der User nicht mehr angemeldet ist. Da der Agent unabhängig von der Session ist läuft er einfach weiter auch wenn der User schon längst über alle Berge ist. Das ist in so weit ein Problem da man theoretisch einen Speicherdump des ssh-agent Prozesses machen kann und dann alle Keys vorfindet. Um dies zu verhindern habe ich die 60 Minuten Sperre eingebaut. So scheißt der Agent nach 60 Minuten alle Keys raus und man muss sie neu laden. Leider ist das etwas unschön. Ich habe allerdings (noch) keine bessere Idee.

Mittwoch, 13. Februar 2008

Mono Funktional

Obwohl ich absolut kein Plan von C# und .Net habe und auch sonst Microsoft ... hasse, konnte ich doch der Werbung zu dem F#-Tutorial von Andreas nicht widerstehen. Da es für mich auf keinen Fall in Frage kommt Windows zu installieren habe ich die freie, .Net kompatible Laufzeitumgebung Mono installiert. Da dies nicht so einfach ging hier ein kurzes HowTo. Anmerkung: Ich weiß nicht ob alle diese Schritte genau so notwendig sind, da ich wie schon erwähnt mich auf dem Gebiet nicht auskenne, aber so läuft alles.

Zuerst einmal die Laufzeitumgebung inklusive Libraries und C#-Compiler installieren. Da ich (noch) Kubuntu nutze hier erläutere ich hier mal nur den Ubuntu-Weg. In allen anderen Distributionen sollte es allerdings ähnlich ablaufen. Anmerkung: Installiert braucht Mono gut 100MB, also mal kurz installieren und testen lohnt fast nicht.

sudo apt-get install mono-devel

Da dies soweit ich das beurteilen kann allerdings nur den C# 1.0 Compiler installiert installieren wir noch den 2.0 Compiler inklusive Libraries nach. Außerdem werden noch für C# zwei zusätzliche Libraries benötigt:

sudo apt-get install mono-gmcs libmono-winforms1.0-cil libmono-system-runtime2.0-cil

Jetzt sollte Mono so weit laufen. Testen kann man dies indem man ein Beispiel von der Student Homepage herunter lädt (Wenn das mal kein Werbebonus gibt) und mit gmcs Program.cs kompiliert. Ausführen lässt sich das Ergebnis einfach mittels mono Program.exe. Das einzige was noch zu klären bleibt: Warum zum Henker machen Windowsanhänger bei Konsolenprogrammen immer ein Console.ReadLine(); am Ende? ;-)

Soweit zu Mono. Jetzt kommt der F# Teil. Erst einmal F# downloaden. Am besten wählt man hier Download F# 1.9.3.14 als Zip aus. Das Paket entpacken und als Administrator installieren. (Gefällt mir ja überhaupt nicht ein Microsoft Produkt als Administrator auf meinem Rechner auszuführen.) Hierzu einfach das mitgelieferte Bashscript ausführen:

sudo bash install-mono.sh

Dieses Script installiert F# und kompiliert das ganze in das bin-Verzeichnis. Hat alles so weit geklappt ist man nun (mehr oder weniger) stolzer Besitzer von F# und kann sich gleich an der ersten Aufgabe von Andreas versuchen.

Als Beispiel gleich mal meine Lösung:

#light
open System
let rec (!) (a : int) =
  if (a = 1) then
    a
  else
    let a2 = a - 1
    a * !a2

printfn "Zahl eingeben:"
let a = read_line ()
let endresult = !Convert.ToInt32(a)
printfn "Die fakultaet von %s ist %i" a endresult

Das wird nun mit folgendem Befehl erst kompiliert und anschließend ausgeführt:

downloads/FSharp-1.9.3.14/bin/fsc.exe fakultaet.fs
mono fakultaet.exe

Wenn man dem Binary Ausführungsrechte gibt kann man es auch einfach mittels ./fakultaet.exe ausführen. Das einzige was jetzt noch schön wäre, ist ein vim Syntax-Style für F#...

Bleibt abzuwarten wann Tutorial Teil 3 folgt. Ich bin gespannt!

Donnerstag, 6. Dezember 2007

Was macht eine Programmiersprache gut?

Ich brauche euch glaube ich nicht mehr zu erklären was eine Programmiersprache ist. Jeder von euch hatte wahrscheinlich schon mit mehreren Sprachen zu tun gehabt. Jeder von euch mag die eine Sprache mehr und eine andere weniger. Aber im Grunde sind die meisten Sprachen alle gleich. Man hat seine Standardbefehle wie z.B. if oder for, und in den meisten Sprachen lassen sich Funktionen oder auch Klassen definieren.

Gut, zugegeben jede Sprache hat einige besondere Features. In C++ kann man z.B. vieles elegant über Pointer lösen hat aber an anderer Stelle dafür wieder andere Probleme damit. Java ist durch seine Virtuelle Maschine mehr oder weniger plattformunabhänig. Python zeichnet sich als Script Sprache aus und wird dynamisch typisiert und in Ruby ist Codeblöcke und Callback Möglichkeiten markant. Aber sind diese Features wirklich entscheidend welcher Sprache wir den Vorzug geben? Lassen sich nicht alle diese Features mehr oder weniger einfach auf alle anderen Sprachen übertragen?

Man kann mit allen Sprachen alles Programmieren. Man braucht nur ein einfachen Interpreter und ich kann mit C++ Webseiten programmieren. Ein extra Compiler und mit PHP lassen sich Desktop GUI Programme erstellen. Alles nur eine Frage des Aufwands den man rein stecken muss. Bestes Beispiel ist hier wohl Ruby on Rails (RoR) und PHP. Viele loben RoR schon als neues PHP. Etwas unfair wenn man bedenkt das RoR ein komplettes Framework ist und PHP nur eine Sprache. Aber ich kann doch ohne Probleme dieses Framework mit PHP nach programmieren (Wurde auch schon gemacht). Auch die Vorteile das RoR von der Sprache selbst besser ist trifft nicht unbedingt zu. Einfach nur ein Paar Librarys neu schreiben und schon hat man in PHP die Objektorientierung wie man sie aus RoR kennt. Warum soll also RoR eine bessere "Sprache" sein als PHP?

Worauf ich eigentlich hinaus will, es ist auf Sprachebene relativ egal welche Sprache ich benutze. Manche sind für den Verwendungszweck besser geeignet manche Schlechter aber vom Prinzip macht es kein Unterschied. Aber warum mag man nun eine Sprache mehr oder eine andere weniger? Meine Theorie ist einfach das es sehr viel auf das Umfeld ankommt. Wenn ich mir zum Beispiel so manche API Dokumentation von Ruby anschaue bleiben oft Fragen offen. Bei Java hingegen bekomme ich zu jeder popeligen API eine super Dokumentation. Wenn ich in Ruby eine Frage habe wartet man in Supportforen oft vergebens auf eine Antwort. Wo hingegen bei C++ so gut wie jede Frage schon einmal geklärt wurde. Oder um wieder auf Rails zurück zu kommen: Wenn ich in PHP ein Programm kurz testen will muss ich erst Apache einrichten und starten. RoR bringt sein Webserver bereits mit. Das ganze lässt sich noch beliebig fortsetzten aber meine Message ist denke ich klar: Die Sprache selbst macht wenig aus. Auf das Environment kommt es an!

Wobei ich nicht abstreiten mag das bei Vielen sich eine "Das kenne ich, das nehme ich" - Mentalität bei Programmiersprachen abzeichnet. Mir ist das vor kurzem wieder aufgefallen. Ich habe einige Beispielprogramme mit Java und aufwendiger Swing-GUI gesehen. In Ruby wäre in Kombination mit dem Kommandozeileninterpreter irb das Programm wahrscheinlich nur ein Zehntel so lange.

Aber gut, muss jedem selbst überlassen sein in welcher Sprache er gerne Programmiert. Ich kann nur jedem ausdrücklich empfehlen sich möglichst viele Sprachen mal anzuschauen um einfach über seinen Horizont zu sehen. Jede Sprache hat ihre eigene Mentalität ihre eigene Community und eigene Ideen warum sie so ist wie sie ist. Und alle Sprachen sind interessant!

Dienstag, 27. November 2007

Schneckenalarm

Kennt ihr das wenn man von irgendeiner Aufgabe hört die eigentlich total trivial ist. Man weiß genau man hat keine Zeit. Aber man kann einfach nicht anders als diese Aufgabe jetzt und hier zu lösen und vergisst dabei alles andere um sich herum.

So ging es mir gestern wieder einmal. Unsere Erstsemester dürfen sich gerade mit der berühmt berüchtigten "Schnecke" herumschlagen. Ich musste das Programm auch schon im ersten Semester schreiben und muss sagen ist etwas Tricky. Ich habe das gestern so mit bekommen weil viele damit Probleme haben und nach einem Tipp gefragt haben.

Ich hab das ganze im Kopf durchgespielt und meinte: "Ach des sind nur 20 Zeilen Code...". Zuhause habe ich mir mein alten Code angeschaut und bin erschrocken. ich habe fast 200 Zeilen gebraucht und so beim darüber schauen erschien mir alles total kompliziert. Also was mach ich? Genau, ich hab mein Texteditor aufgemacht (noch nicht mal eclipse) und drauf los geschrieben.

Im ersten Semester weiß ich noch das ich mir Seitenweise Schnecken aufgemalt habe um zu verstehen wie sie funktionieren. Gestern waren es genau zwei Schnecken. Bei einer habe ich getestet wie es geht und bei der anderen überprüft. Man könnte ja sagen ich wusste ja schon wie es geht, allerdings habe ich nach einem anderen Ansatz gesucht als den vom ersten Semester.

Die Lösung soll übrigens so aussehen.

Das ganze hat einige erstaunliche Erfahrungen an den Tag gebracht und mir gezeigt das mein Studium vielleicht doch nicht ganz umsonst ist.

Vor allem aus der Vorlesung SEKS (Software Engineering komplexer Systeme) ist sehr viel in das Programm eingeflossen. Normalerweise würde man für das Problem ein zwei dimensionales Array nutzen und das immer über zwei Koordinaten ansprechen. Allerdings habe ich die Idee mit dem eindimensionalen Array aus SEKS geklaut und hier erstmalig angewendet. Prinzip ist: Wenn ich ein Feld nach rechts will addiere ich zur aktuellen Position eins hinzu. Für links wird eins subtrahiert. Für rauf wird die Größe des Arrays abgezogen und für runter das selbe darauf addiert. Also habe ich eine Matrix von 8x8 Feldern komme ich mit pos-8 nach oben und eben mit pos+8 nach unten. Das vereinfacht schon einmal viel da ich nicht immer mit zwei Koordinaten arbeiten muss sondern nur noch eine Position habe.

Auch von meiner Algo (Algorithmen und Datenstrukturen) floss viel in meine Schnecke ein. In Algo war immer eine sehr kurze und performante Lösung gefragt. Auch war es nicht ungewöhnlich das man einfach mal 30 Minuten vor 10 Zeilen sitzt und überlegt wie man das noch anders / einfacher gestalten kann.

Ich habe unbewusst meine Schnecke in mehrere Abschnitte unterteilt. Also zuerst Parameter von Außen geprüft und übernommen, dann Konstanten und anschließend Variablen definiert. Dann kam die eigentliche Ausführung gefolgt von der Ausgabe. Jeder dieser Teile habe ich glaub ich mehrmals neu geschrieben um jeden Teil für sich zu optimieren.

Hier erstmal der Code:

public class Schnecke {
  public static int aktuellerBuchstabe = 0;
  public static String[] output;
  public static String word;

  public static void main (String[] args) {
    if (args.length < 2) { System.err.println("Falsche Parameter"); System.exit(1); }
    int size = Integer.parseInt(args[0]);
    word = args[1];

    final int runter = size;
    final int rauf   = -size;
    final int links  = -1;
    final int rechts = 1;

    int height = size - 1;
    int width  = size - 1;
    int[] richtungen = {runter, links, rauf, rechts};
    output = new String[size * size];
    int pos = fill(size-1, 0, rechts);  // Da sich erster Schritt anders Verhällt

    while (height >= 1 && width >= 1) {
      for (int r : richtungen) {
        if (r == runter || r == rauf) { pos = fill(height, pos, r); height -=2; }
        else                          { pos = fill(width,  pos, r); width  -=2; }
        if (height < 1 || width < 1)  break;
      }
    }

    for(int i = 0; i < output.length; i++) {
      System.out.print((output[i] != null)? output[i]: " ");
      if (i % size == size-1 ) System.out.print("\n");
    }
  }

  public static int fill(int count, int pos, int richtung) {
    output[pos] = getNextChar();
    return (count == 0)? pos: fill(--count, (pos+richtung), richtung);
  }

  public static String getNextChar() {
    int pos = aktuellerBuchstabe++ % word.length();  // ++ sehr unschön
    return word.substring(pos, pos+1);
  }
}

Während der Implementierung sind mir wieder einmal die Grenzen von Java aufgefallen. Dadurch das es z.B. primitive Datentypen gibt stößt man schnell an die Grenzen des Machbaren. Deshalb habe ich mir schon vorgenommen das selbe Programm demnächst mit Ruby zu schreiben und mich überraschen zu lassen in wie weit sich das unterscheiden wird.

Mir ist durchaus bewusst das dieses Programm nichts tolles ist. Aber ich fand es gestern sehr interessant mir selbst beim Programmieren zu zuschauen. Viele interessante Dinge sind mir dadurch bewusst geworden. Bevor sich noch jemand beschwert, ich würde so komprimiert und mit so vielen kleinen Tricks nie etwas schreiben was ich öfters verwenden will, da es einfach zu undurchsichtig ist. Dann würde ich eher eine 200 Zeilen Variante bevorzugen. Diese Schnecke war einfach nur zum Testen gedacht.