Warum Haskell?

Haskell ist eine rein funktionale Sprache und beschränkt Sie von vielen herkömmlichen Programmiermethoden in einer objektorientierten Sprache. Aber die Einschränkung der Programmiermöglichkeiten bietet uns wirklich Vorteile gegenüber anderen Sprachen?

In diesem Lernprogramm werfen wir einen Blick auf Haskell und versuchen zu klären, was es ist und warum es sich für Ihre zukünftigen Projekte vielleicht lohnt.


Haskell auf einen Blick

Haskell ist eine ganz andere Sprache.

Haskell ist eine ganz andere Sprache, als Sie vielleicht gewohnt sind, in der Art, wie Sie Ihren Code in "Pure" -Funktionen anordnen. Eine reine Funktion ist eine Funktion, die keine externen Aufgaben ausführt, außer einen berechneten Wert zurückzugeben. Diese externen Aufgaben werden im Allgemeinen als "Nebeneffekte" bezeichnet.

Dazu gehören das Abrufen externer Daten vom Benutzer, das Drucken auf die Konsole, das Lesen aus einer Datei usw. In Haskell fügen Sie keine dieser Aktionen in Ihre reinen Funktionen ein.

Nun fragen Sie sich vielleicht: "Was nützt ein Programm, wenn es nicht mit der Außenwelt interagieren kann?" Nun, Haskell löst dies mit einer speziellen Funktion, der sogenannten IO-Funktion. Im Wesentlichen trennen Sie alle datenverarbeitenden Teile Ihres Codes in reine Funktionen und fügen dann die Teile, die Daten laden, in E / A-Funktionen ein. Die "main" -Funktion, die aufgerufen wird, wenn Ihr Programm zum ersten Mal ausgeführt wird, ist eine E / A-Funktion.

Sehen wir uns einen schnellen Vergleich zwischen einem Standard-Java-Programm und dem entsprechenden Haskell-Vergleich an.

Java-Version:

 import java.io. *; class Test public static void main (Zeichenfolge [] args) System.out.println ("What's Your Name:"); BufferedReader br = new BufferedReader (neuer InputStreamReader (System.in)); Stringname = null; try name = br.readLine ();  catch (IOException e) System.out.println ("Es war ein Fehler");  System.out.println ("Hallo" + Name); 

Haskell-Version:

 welcomeMessage name = "Hello" ++ name main = do putStrLn Name "Was ist Ihr Name:" <- getLine putStrLn $ welcomeMessage name

Das erste, was Sie beim Betrachten eines Haskell-Programms feststellen können, ist, dass es keine Klammern gibt. In Haskell setzen Sie nur Klammern ein, wenn Sie versuchen, die Dinge zusammenzufassen. Die erste Zeile am Anfang des Programms - die mit beginnt Willkommensnachricht - ist eigentlich eine Funktion; Es akzeptiert eine Zeichenfolge und gibt die Willkommensnachricht zurück. Die einzige andere Sache, die hier etwas seltsam erscheinen mag, ist das Dollarzeichen in der letzten Zeile.

putStrLn $ welcomeMessage name

Dieses Dollarzeichen weist Haskell einfach an, zuerst das auszuführen, was sich auf der rechten Seite des Dollarzeichens befindet, und dann nach links zu gehen. Dies ist erforderlich, da Sie in Haskell eine Funktion als Parameter an eine andere Funktion übergeben können. Haskell weiß also nicht, ob Sie versuchen, das zu bestehen Willkommensnachricht Funktion zu putStrLn, oder erst bearbeiten.

Abgesehen von der Tatsache, dass das Haskell-Programm erheblich kürzer ist als die Java-Implementierung, besteht der Hauptunterschied darin, dass wir die Datenverarbeitung in eine rein Funktion, während wir es in der Java-Version nur ausgedruckt haben. Dies ist Ihre Aufgabe in Haskell in Kürze: Sie trennen Ihren Code in seine Komponenten. Warum fragst du? Gut. Es gibt einige Gründe. Lassen Sie uns einige davon überprüfen.

1. Sicherer Code

Dieser Code kann nicht beschädigt werden.

Wenn Sie in der Vergangenheit bereits Programme abgestürzt haben, wissen Sie, dass das Problem immer mit einer dieser unsicheren Operationen zusammenhängt, z. B. ein Fehler beim Lesen einer Datei, ein Benutzer hat falsche Daten eingegeben usw. Durch die Beschränkung Ihrer Funktionen auf die Verarbeitung von Daten wird garantiert, dass diese nicht abstürzen. Der natürlichste Vergleich, den die meisten Leute kennen, ist eine Math-Funktion.

In Math berechnet eine Funktion ein Ergebnis. das ist alles. Zum Beispiel, wenn ich eine Math-Funktion schreiben würde, wie f (x) = 2x + 4, dann, wenn ich reingehe x = 2, ich werde bekommen 8. Wenn ich stattdessen vorbei komme x = 3, ich werde bekommen 10 als Ergebnis. Dieser Code kann nicht beschädigt werden. Da alles in kleine Funktionen aufgeteilt ist, wird das Testen von Einheiten trivial. Sie können jeden einzelnen Teil Ihres Programms testen und wissen, dass es zu 100% sicher ist.

2. Erhöhte Code-Modularität

Ein weiterer Vorteil bei der Trennung Ihres Codes in mehrere Funktionen ist die Wiederverwendbarkeit von Code. Stellen Sie sich vor, wenn alle Standardfunktionen wie Mindest und max, druckte auch den Wert auf den Bildschirm. Diese Funktionen wären dann nur unter ganz bestimmten Umständen relevant, und in den meisten Fällen müssten Sie eigene Funktionen schreiben, die nur einen Wert zurückgeben, ohne ihn zu drucken. Gleiches gilt für Ihren benutzerdefinierten Code. Wenn Sie ein Programm haben, das eine Messung von cm in Zoll konvertiert, können Sie den eigentlichen Konvertierungsprozess in eine reine Funktion umwandeln und sie dann überall wieder verwenden. Wenn Sie es jedoch fest in Ihr Programm kodieren, müssen Sie es jedes Mal erneut eingeben. Theoretisch scheint dies ziemlich offensichtlich zu sein, aber wenn Sie sich an den Java-Vergleich von oben erinnern, gibt es einige Dinge, an die wir uns nur noch hartcodieren.

Darüber hinaus bietet Haskell zwei Möglichkeiten, Funktionen zu kombinieren: den Punktoperator und Funktionen höherer Ordnung.

Mit dem Punktoperator können Sie Funktionen miteinander verketten, sodass die Ausgabe einer Funktion in die Eingabe der nächsten eingeht.

Hier ein kurzes Beispiel, um diese Idee zu demonstrieren:

 cmToInches cm = cm * 0.3937 formatInchesStr i = show i ++ "inches" main = do putStrLn "Länge in cm eingeben:" inp <- getLine let c = (read inp :: Float) (putStrLn . formatInchesStr . cmToInches) c

Dies ist dem letzten Haskell-Beispiel ähnlich, aber hier habe ich die Ausgabe von kombiniert cmToInches zur Eingabe von formatInchesStr, und haben diese Ausgabe an gebunden putStrLn. Funktionen höherer Ordnung sind Funktionen, die andere Funktionen als Eingabe akzeptieren oder Funktionen, die eine Funktion als Ausgabe ausgeben. Ein hilfreiches Beispiel hierfür ist das integrierte Haskell Karte Funktion. Karte übernimmt eine Funktion, die für einen einzelnen Wert gedacht war, und führt diese Funktion für ein Array von Objekten aus. Mit Funktionen höherer Ordnung können Sie Codeabschnitte abstrahieren, die mehrere Funktionen gemeinsam haben, und dann einfach eine Funktion als Parameter angeben, um den Gesamteffekt zu ändern.

3. Bessere Optimierung

In Haskell gibt es keine Unterstützung für das Ändern des Status oder von veränderlichen Daten.

In Haskell gibt es keine Unterstützung für das Ändern des Status oder von veränderlichen Daten. Wenn Sie also versuchen, eine Variable zu ändern, nachdem sie festgelegt wurde, wird beim Kompilieren ein Fehler angezeigt. Das klingt vielleicht zunächst nicht ansprechend, aber Ihr Programm wird "referenziell transparent". Dies bedeutet, dass Ihre Funktionen immer dieselben Werte zurückgeben, wenn dieselben Eingaben erfolgen. Dadurch kann Haskell Ihre Funktion vereinfachen oder ganz durch einen zwischengespeicherten Wert ersetzen, und das Programm wird wie erwartet normal ausgeführt. Eine gute Analogie dazu sind Math-Funktionen - da alle Math-Funktionen referenziell transparent sind. Wenn Sie eine Funktion hätten, wie Sünde (90), Sie könnten das durch die Nummer ersetzen 1, weil sie den gleichen Wert haben, sparen Sie Zeit bei der Berechnung dieses Werts. Ein weiterer Vorteil, den Sie mit dieser Art von Code erhalten, besteht darin, dass Sie Funktionen, die sich nicht auf einander verlassen, parallel ausführen können, was wiederum die Gesamtleistung Ihrer Anwendung erhöht.

4. Höhere Produktivität im Workflow

Ich persönlich habe festgestellt, dass dies zu einem erheblich effizienteren Workflow führt.

Indem Sie Ihre Funktionen zu einzelnen Komponenten machen, die auf nichts anderes angewiesen sind, können Sie Ihr Projekt viel fokussierter planen und ausführen. Normalerweise würden Sie eine sehr allgemeine To-Do-Liste erstellen, die viele Dinge umfasst, wie "Build Object Parser" oder ähnliches. In diesem Fall können Sie nicht wirklich wissen, um was es sich handelt und wie lange es dauert. Sie haben eine Grundidee, aber oft neigen die Dinge dazu, "aufzutauchen".

In Haskell sind die meisten Funktionen ziemlich kurz - ein paar Zeilen, maximal - und sind ziemlich fokussiert. Die meisten von ihnen führen nur eine bestimmte Aufgabe aus. Dann haben Sie jedoch andere Funktionen, die eine Kombination dieser untergeordneten Funktionen sind. Ihre To-Do-Liste besteht also aus sehr spezifischen Funktionen, bei denen Sie genau wissen, was die einzelnen Aufgaben im Voraus tun. Ich persönlich habe festgestellt, dass dies zu einem erheblich effizienteren Workflow führt.

Jetzt ist dieser Workflow nicht exklusiv für Haskell. Sie können dies leicht in jeder Sprache tun. Der einzige Unterschied besteht darin, dass dies in Haskell der bevorzugte Weg ist, wie er auch für andere Sprachen gilt, wo Sie wahrscheinlich mehrere Aufgaben miteinander kombinieren.

Aus diesem Grund empfehle ich Ihnen, Haskell zu lernen, auch wenn Sie es nicht jeden Tag verwenden wollen. Es zwingt Sie, sich in diese Gewohnheit zu begeben.

Nun, da ich Ihnen einen schnellen Überblick über einige Vorteile der Verwendung von Haskell gegeben habe, wollen wir uns ein Beispiel aus der Praxis ansehen. Da dies eine netzbezogene Website ist, dachte ich, dass eine relevante Demo darin besteht, ein Haskell-Programm zu erstellen, das Ihre MySQL-Datenbanken sichern kann.

Beginnen wir mit einer Planung.


Ein Haskell-Programm erstellen

Planung

Ich habe bereits erwähnt, dass Sie Ihr Programm in Haskell nicht wirklich in einem übersichtlichen Stil planen. Stattdessen organisieren Sie die einzelnen Funktionen, während Sie gleichzeitig daran denken, den Code zu trennen rein und IO-Funktionen. Das erste, was dieses Programm tun muss, ist eine Verbindung zu einer Datenbank herzustellen und die Liste der Tabellen abzurufen. Dies sind beide E / A-Funktionen, da sie Daten aus einer externen Datenbank abrufen.

Als Nächstes müssen wir eine Funktion schreiben, die die Liste der Tabellen durchläuft und alle Einträge zurückgibt. Dies ist auch eine E / A-Funktion. Sobald das erledigt ist, haben wir ein paar rein Funktionen, um die Daten für das Schreiben vorzubereiten, und nicht zuletzt müssen alle Einträge zusammen mit dem Datum und einer Abfrage in Sicherungsdateien geschrieben werden, um alte Einträge zu entfernen. Hier ist ein Modell unseres Programms:

Dies ist der Hauptfluss des Programms, aber wie ich bereits sagte, wird es auch ein paar Hilfsfunktionen geben, die beispielsweise das Datum und so weiter machen. Nachdem wir nun alles festgelegt haben, können wir mit dem Aufbau des Programms beginnen.

Gebäude

Ich werde die HDBC MySQL-Bibliothek in diesem Programm verwenden, die Sie durch Ausführen installieren können Kabal installieren HDBC und cabal install HDBC-mysql Wenn Sie die Haskell-Plattform installiert haben. Beginnen wir mit den ersten beiden Funktionen in der Liste, da beide in die HDBC-Bibliothek integriert sind:

 import Control.Monad import Database.HDBC import Database.HDBC.MySQL importieren System.IO import System.Directory import Data.Time importieren Data.Time.Calendar main = do conn <- connectMySQL defaultMySQLConnectInfo  mysqlHost = "127.0.0.1", mysqlUser = "root", mysqlPassword = "pass", mysqlDatabase = "test"  tables <- getTables conn

Dieser Teil ist ziemlich geradlinig; Wir erstellen die Verbindung und fügen dann die Liste der Tabellen in eine Variable ein Tische. Die nächste Funktion durchläuft die Liste der Tabellen und ruft alle Zeilen in jeder Tabelle auf. Eine schnelle Möglichkeit besteht darin, eine Funktion zu erstellen, die nur einen Wert verarbeitet, und dann die Karte Funktion, um es auf das Array anzuwenden. Da wir eine IO-Funktion abbilden, müssen wir verwenden mapM. Mit dieser Implementierung sollte Ihr Code jetzt wie folgt aussehen:

 getQueryString name = "select * from" ++ name processTable :: IConnection conn => conn -> String -> IO [[SqlValue]] processTable conn name = Lassen Sie qu = getQueryString-Namenszeilen <- quickQuery' conn qu [] return rows main = do conn <- connectMySQL defaultMySQLConnectInfo  mysqlHost = "127.0.0.1", mysqlUser = "root", mysqlPassword = "pass", mysqlDatabase = "test"  tables <- getTables conn rows <- mapM (processTable conn) tables

getQueryString ist eine reine Funktion, die a zurückgibt wählen Abfrage, und dann haben wir die tatsächliche processTable Funktion, die diese Abfragezeichenfolge verwendet, um alle Zeilen aus der angegebenen Tabelle abzurufen. Haskell ist eine stark typisierte Sprache, was im Wesentlichen bedeutet, dass Sie zum Beispiel keine int wo ein Schnur soll gehen. Aber Haskell ist auch "Typinferenz", was bedeutet, dass Sie die Typen normalerweise nicht schreiben müssen, und Haskell wird das herausfinden. Hier haben wir einen Brauch conn Typ, den ich explizit angeben musste; also das ist was die linie über der processTable Funktion macht.

Als nächstes werden in der Liste die von der vorherigen Funktion zurückgegebenen SQL-Werte in Strings konvertiert. Eine andere Möglichkeit, Listen zu behandeln Karte ist eine rekursive Funktion zu erstellen. In unserem Programm haben wir drei Ebenen von Listen: eine Liste von SQL-Werten, die sich in einer Liste von Zeilen befinden, die sich in einer Liste von Tabellen befinden. ich werde benützen Karte für die ersten beiden Listen und dann eine rekursive Funktion, um die letzte Liste zu behandeln. Dadurch kann die Funktion selbst ziemlich kurz sein. Hier ist die resultierende Funktion:

 unSql x = (fromSql x) :: Zeichenfolge sqlToArray [n] = (unSql n): [] sqlToArray (n: n2) = (unSql n): sqlToArray n2

Fügen Sie dann der Hauptfunktion die folgende Zeile hinzu:

 let stringRows = Kartenzeilen (map sqlToArrays)

Möglicherweise haben Sie bemerkt, dass Variablen manchmal als deklariert werden var und zu anderen Zeiten als lassen Sie var = Funktion. Die Regel lautet im Wesentlichen: Wenn Sie versuchen, eine E / A-Funktion auszuführen und die Ergebnisse in eine Variable einzufügen, verwenden Sie die Methode; Um die Ergebnisse einer reinen Funktion in einer Variablen zu speichern, würden Sie stattdessen verwenden Lassen.

Der nächste Teil wird ein bisschen schwierig sein. Wir haben alle Zeilen im String-Format, und jetzt müssen wir jede Zeile mit Werten durch einen Insert-String ersetzen, den MySQL versteht. Das Problem ist, dass sich die Tabellennamen in einem separaten Array befinden. also ein Doppelgänger Karte Funktion funktioniert in diesem Fall nicht wirklich. Wir hätten verwenden können Karte einmal, aber dann müssten wir die Listen zu einer zusammenfassen - möglicherweise mit Tupeln, weil Karte akzeptiert nur einen Eingabeparameter - daher entschied ich, dass es einfacher ist, nur neue rekursive Funktionen zu schreiben. Da wir ein dreischichtiges Array haben, benötigen wir drei separate rekursive Funktionen, damit jede Ebene ihren Inhalt an die nächste Ebene weitergeben kann. Hier sind die drei Funktionen zusammen mit einer Hilfsfunktion zum Generieren der eigentlichen SQL-Abfrage:

 flattenArgs [arg] = "\" ++ arg ++ "\" flattenArgs (arg1: args) = "" ++ arg1 ++ "", "++ (flattenArgs args) iQuery-Name args =" insert in "++ name ++" - Werte ("++ (flattenArgs args) ++"); \ n "insertStrRows name [arg] = iQuery-Name arg insertStrRows-Name (arg1: args) = (iQuery-Name arg1) ++ (insertStrRows name args) insertStrTables [Tabelle] [Zeilen] = InsertStrRows Tabellenzeilen: [] InsertStrTables (Tabelle1: Andere) (Zeilen1: usw.) = (InsertStrRows Tabelle1 Zeilen1):

Fügen Sie der Hauptfunktion wieder Folgendes hinzu:

 let insertStrs = insertStrTables-Tabellenzeichenfolge

Das flattenArgs und iQuery Funktionen arbeiten zusammen, um die eigentliche SQL-Einfügeabfrage zu erstellen. Danach haben wir nur die zwei rekursiven Funktionen. Beachten Sie, dass wir bei zwei der drei rekursiven Funktionen ein Array eingeben, die Funktion jedoch einen String zurückgibt. Auf diese Weise entfernen wir zwei der verschachtelten Arrays. Jetzt haben wir nur ein Array mit einer Ausgabezeichenfolge pro Tabelle. Der letzte Schritt besteht darin, die Daten tatsächlich in die entsprechenden Dateien zu schreiben. Dies ist wesentlich einfacher, da es sich jetzt nur um ein einfaches Array handelt. Hier ist der letzte Teil zusammen mit der Funktion zum Abrufen des Datums:

 dateStr = do t <- getCurrentTime return (showGregorian . utctDay $ t) filename name time = "Backups/" ++ name ++ "_" ++ time ++ ".bac" writeToFile name queries = do let output = (deleteStr name) ++ queries time <- dateStr createDirectoryIfMissing False "Backups" f <- openFile (filename name time) WriteMode hPutStr f output hClose f writeFiles [n] [q] = writeToFile n q writeFiles (n:n2) (q:q2) = do writeFiles [n] [q] writeFiles n2 q2

Das dateStr Funktion konvertiert das aktuelle Datum in eine Zeichenfolge mit dem Format, JJJJ-MM-TT. Dann gibt es die dateiname-Funktion, die alle Teile des Dateinamens zusammenfügt. Das writeToFile Funktion kümmert sich um die Ausgabe in die Dateien. Zuletzt die writeFiles Die Funktion durchläuft die Liste der Tabellen, sodass Sie pro Tabelle eine Datei erstellen können. Jetzt müssen Sie nur noch die Hauptfunktion mit dem Aufruf an beenden writeFiles, und fügen Sie eine Nachricht hinzu, die den Benutzer darüber informiert, wenn er fertig ist. Sobald Sie fertig sind, Ihre Main Funktion sollte so aussehen:

 main = do conn <- connectMySQL defaultMySQLConnectInfo  mysqlHost = "127.0.0.1", mysqlUser = "root", mysqlPassword = "pass", mysqlDatabase = "test"  tables <- getTables conn rows <- mapM (processTable conn) tables let stringRows = map (map sqlToArray) rows let insertStrs = insertStrTables tables stringRows writeFiles tables insertStrs putStrLn "Databases Sucessfully Backed Up"

Wenn eine Ihrer Datenbanken jemals ihre Informationen verliert, können Sie die SQL-Abfragen direkt aus ihrer Sicherungsdatei in ein MySQL-Terminal oder -Programm einfügen, das Abfragen ausführen kann. Die Daten werden zu diesem Zeitpunkt wiederhergestellt. Sie können auch einen Cron-Job hinzufügen, um diese stündlich oder täglich auszuführen, um Ihre Sicherungen auf dem neuesten Stand zu halten.


Beenden

Es gibt ein ausgezeichnetes Buch von Miran Lipovača mit dem Titel "Lernen Sie ein Haskell".

Das ist alles, was ich für dieses Tutorial habe! Wenn Sie daran interessiert sind, Haskell vollständig zu lernen, gibt es ein paar gute Ressourcen, die Sie überprüfen sollten. Es gibt ein ausgezeichnetes Buch von Miran Lipovača mit dem Titel "Learn you a Haskell", das sogar eine kostenlose Online-Version enthält. Das wäre ein hervorragender Start.

Wenn Sie nach bestimmten Funktionen suchen, sollten Sie sich an Hoogle wenden, eine Google-ähnliche Suchmaschine, mit der Sie nach Namen oder sogar nach Typ suchen können. Wenn Sie also eine Funktion benötigen, die einen String in eine Liste von Strings konvertiert, geben Sie Folgendes ein Zeichenfolge -> [Zeichenfolge], und Sie erhalten alle anwendbaren Funktionen. Es gibt auch eine Site namens hackage.haskell.org, die die Liste der Module für Haskell enthält. Sie können sie alle durch Kabale installieren.

Ich hoffe, Ihnen hat dieses Tutorial gefallen. Wenn Sie Fragen haben, können Sie unten einen Kommentar posten. Ich werde mein Bestes tun, um mich so schnell wie möglich bei Ihnen zu melden!