Beerway-orientierte Programmierung in F # [1]

Einführung

In diesem Blog-Beitrag werde ich einen Abstecher zu meinen regulären Astrophysik-Posts machen und über ein Nebenprojekt schreiben, an dem ich gearbeitet habe, um mich zu warnen, wenn es ein Update in der Bierauswahl meiner Lieblingsbrauerei gibt: Tired Hands. Ähnlich wie in meinen vorherigen Beiträgen verwende ich F # mit VS Code, Mono, Ionide, PAKET und FAKE auf einem Mac.

Ich habe vor, die Diskussion über die Entwicklung dieses Projekts in zwei Blogposts aufzuteilen: Der erste Blogpost bildet die Grundlage über die eisenbahnorientierte Programmierung und definiert die Domäne und die Grundfunktionen in der Pipeline. Im zweiten Abschnitt wird erläutert, wie der Code allgemeiner gestaltet werden kann, damit mehr Brauereien hinzugefügt werden können. Außerdem werden andere Funktionen eingeführt, um Überwachung, Planung und Tests zu verbessern.

Wir werden die grundlegende Pipeline entwickeln, die Folgendes umfasst:

  1. Kratzen Sie die neueste Bierliste von der Tired Hand-Website
  2. Vergleichen des Unterschieds zwischen dem vorherigen und dem neuesten Kratzer
  3. Benachrichtigung per Text, wenn es einen Unterschied gibt.

Vielen Dank, F #: Es ist ziemlich cool, meine Bemühungen beim Biertrinken mithilfe funktionaler Programmierkonzepte zu verbessern [M * ​​nads, algebraische Datentypen, Pattern Matching, um nur einige zu nennen].

Logo von Müde Hand

Impetus

Inspiriert von Scott Wlaschins Posts über Eisenbahnorientiertes Programmieren auf seiner großartigen Website, F # for Fun And Profit, wollte ich die Idee, den „Unhappy Path“ vom „Happy“ in diesem Projekt zu trennen, zusammen mit den anderen Geschenken einbeziehen Einbeziehung von ROP bringt.

Außerdem wollte ich eine Möglichkeit finden, um benachrichtigt zu werden, wenn sich die Auswahl an Bieren von Tired Hand aus deren Fermentaria geändert hat. Daher musste der Name nur ein Sinnbild für bier- und eisenbahnorientierte Programmierung sein.

Durch die Entwicklung dieses Projekts konnte ich mir erfolgreich einen Wettbewerbsvorteil gegenüber meinen Bierkollegen verschaffen, die diese Brauerei auch häufig besuchen, indem sie im Voraus wussten, welche neuen Biere sie vorab probieren sollten.

Einrichten

Wir beginnen mit der Erstellung eines neuen auf Ordnern und Konsolenanwendungen basierenden Projekts im VS-Code mit dem Namen „BeerwayOrientedProgramming“. Zum Erstellen des neuen Projekts öffnen Sie die Befehlspalette [Cmd + Shift + P] und führen die folgenden Schritte aus:

Das Ergebnis der Erstellung des neuen Projekts lässt unser neu erstelltes Verzeichnis ungefähr so ​​aussehen:

Wir werden die folgenden Bibliotheken verwenden, die in diesem und dem nächsten Blogpost verwendet werden:

  1. Der HtmlTypeProvider von FSharp.Data zum einfachen Durchsuchen der Website, um domänenspezifische Aufzeichnungen der neuesten Bierinformationen zu erstellen.
  2. Chiron, eine Bibliothek für die Zusammenarbeit mit JSON in F # zur Deserialisierung der Bierinformationen des vorherigen Scrapings und zur Serialisierung, um die aktuellen Bierinformationen des neuesten Scrapings beizubehalten.
  3. Twilio API zum Versenden eines Textes für den Fall, dass Unterschiede zwischen dem vorherigen und dem aktuellen Scrape festgestellt wurden.
  4. Logary, eine Bazooka einer Protokollierungsbibliothek, die auch Metriken bereitstellt. [Im nächsten Beitrag verwendet]
  5. FSharp.Configuration zum Analysieren der Konfigurationsdatei für die Twilio API-Konto-SID und das Authentifizierungstoken sowie der Intervallzeit zwischen Timer-Rückrufen. [Im nächsten Beitrag verwendet]

Wir beginnen mit dem Hinzufügen aller oben genannten Abhängigkeiten mit PAKET, nachdem Sie die fsproj-Datei zum ersten Mal geöffnet haben. Um das Hinzufügen von Abhängigkeiten über PAKET zu demonstrieren, wird als Beispiel das Hinzufügen von Chiron zum Projekt verwendet.

Wir können überprüfen, ob die Abhängigkeit erfolgreich hinzugefügt wurde, indem wir sicherstellen, dass der zuletzt hinzugefügte Verweis zu den Dateien fsproj, paket.references und paket.dependency geführt hat.

Eisenbahnorientierte Programmierung

Bahnorientierte Programmierung ist eine saubere, robuste und funktionale Möglichkeit, mit dem unglücklichen Pfad umzugehen. Der unglückliche Pfad ist der Pfad des gesamten smorgasbord von Ausnahmen, Fehlern und Tränen. Eine Präsentation, die diesem Thema von Scott Wlaschin wirklich gerecht wird, finden Sie hier.

Kurz gesagt, beinhaltet die eisenbahnorientierte Programmierung die Trennung des glücklichen und des unglücklichen Pfades der vorgeschlagenen Pipeline. Die Ausgabe jeder ROP-Funktion oder einer Funktion in dieser Pipeline gibt einen Ergebnistyp zurück, der entweder ein Erfolg oder ein Fehler ist.

Wenn das Ergebnis ein Fehler ist, umgehen wir die restlichen Funktionen in der Pipeline und geben den Fehler zurück. Anderenfalls geben wir eine Erfolgsausgabe zurück, die als Eingabe für die nächste Funktion in der Pipeline dient.

Infolgedessen werden die Fehlerfälle im Voraus ermittelt und dokumentiert, und es werden Zeittypprüfungen des definierten unglücklichen Pfades erstellt, der nun in unserer Pipeline den Status eines erstklassigen Bürgers erlangt.

Wir fügen dem Projekt eine neue Datei hinzu, indem wir eine neue Datei mit dem Namen ROP.fs erstellen, die alle unsere Funktionen zur eisenbahnorientierten Programmierung enthält:

Und fügen Sie die Datei zum Projekt hinzu. Da diese Datei von anderen Modulen verwendet wird, verschieben wir die Datei in der Kompilierungsreihenfolge nach oben.

Der Code aus diesem Modul sieht folgendermaßen aus: Ich habe das aus der Präsentation und dem Blogpost geklaut. Weitere Informationen finden Sie dort; Wie bereits erwähnt, gibt es meiner Meinung nach keine besseren Ressourcen als den Beitrag und die Präsentation von Herrn Wlaschin.

Die Domain

Wir definieren unseren Hauptdatensatztyp "BeerInfo" so, dass er aus "TimeOfScrape", einer DateTime und "Beers" besteht, einer Liste von Zeichenfolgen, die alle Biere aus diesem Scrape darstellen.

Um Chiron nutzen zu können, müssen zwei statische Member definiert werden: ToJson und FromJson, um den BeerInfo-Typ zu serialisieren und von diesem zu deserialisieren. Weitere Informationen zur Funktionsweise von Chiron finden Sie hier.

Das Ziel

Sobald wir unsere Domain definiert haben, definieren wir unser Ziel, um in der Lage zu sein, Folgendes in der Reihenfolge zu tun:

  1. Auf der Website von Tired Hand finden Sie eine Auswahl an Bieren. Erfolg ist, wenn wir den BeerInfo-Datensatz des letzten Scrapes erfolgreich abrufen. Ein Fehler ist ein ScrapeError, der auf eine Web-Ausnahme zurückzuführen sein kann, z. B. auf eine Zeitüberschreitung bei der Anforderung oder auf einen nicht autorisierten Zugriff auf die Website.
  2. Vergleichen Sie die Ergebnisse des letzten Kratzens mit denen des vorherigen und finden Sie Unterschiede. Erfolg ist, wenn der Unterschied zwischen dem vorherigen und dem letzten Kratzer besteht. Ein Fehler ist ein NoDifference-Fehler, wenn bei einer E / A-Ausnahme kein Unterschied zwischen den Scrapes oder einem CompareError besteht, während die vorherigen Scrape-Ergebnisse deserialisiert oder die neuesten Scrape-Ergebnisse serialisiert werden.
  3. Benachrichtigen Sie uns per SMS, falls ein Unterschied in der Bierauswahl festgestellt wurde. Erfolg ist der Vorgang des erfolgreichen Versendens eines Texts, und Fehler ist ein AlertError, wenn eine API-Ausnahme auftritt, wenn falsche Eingaben erfolgen oder einige Drosselungsgrenzen erreicht werden.

Unsere Pipeline sollte wie folgt lauten:

kratzen> => vergleichen> => alarmieren

Wir fügen die Fehler jedes einzelnen Schritts hinzu, indem wir eine neue Datei erstellen: Common.fs mit Modulen unserer gemeinsamen Komponenten, die in der Kompilierungsreihenfolge unter ROP.fs verschoben wurden:

Scrape: Scraping- und Type-Provider

Typanbieter waren einer der Hauptgründe, warum ich mich dazu entschlossen habe, in F # zu lernen, wie man sich entwickelt. Inspiriert von einer überwältigenden Präsentation von Rachel Reese [Dezember 2015 in Philadelphia], trat ich eine unermüdliche Reise an, indem ich mich mit F # als Feind des unkontrollierten veränderlichen Staates einschrieb.

Wir nutzen den hervorragenden HTML-Type-Provider in der FSharp.Data-Bibliothek, den wir derzeit bei der Einrichtung unseres Projekts über unsere PAKET-Exkursion herunterladen sollten. Wir beginnen mit dem Hinzufügen einer neuen Datei, "TiredHandsScraper.fs", die unsere TiredHands-Scrape-spezifischen Funktionen enthält, und verschieben diese Datei dann über BeerwayOrientedProgramming.fs, die unseren Einstiegspunkt und unsere Hauptfunktion enthält.

Um die Liste aller Biere zu erhalten, durchlaufen wir die erforderlichen Nachkommen des DOM und filtern die unerwünschten heraus. Dieser Prozess umfasste das Nachschlagen aller Divs mit dem Namen „Menüelementtitel“, das Entfernen einiger Randfälle und das Bereinigen der gefilterten Ergebnisse. Der Code sieht dann so aus:

Ich muss zugeben, dies war der schwierigste Teil des Projekts, da ich keine Ahnung hatte, wie das Markup dieser Website aufgebaut war, aber nach einigem Ausprobieren bekam ich die Daten ziemlich reibungslos.

Wir haben jetzt alle unsere Komponenten, um die Scrape-Funktion zu erstellen, die den Erfolg einer BeerInfo zurückgibt, wenn es keine Scrape-Ausnahmen oder einen Fehler in der Scrape-Ausnahmemeldung gibt. Diese Funktion sieht folgendermaßen aus:

Hier ist ein Beispiel für das Ergebnis der Kratzfunktion am 15. Juli 2017:

Erfolg
  {TimeOfScrape = 15.07.2017 02:25:35;
   Bier =
    ["HopHands"; "SaisonHands"; "Wunscherfüllung "; "Alien Church";
     "Fortschritt durch Verlernen II"; "Aloha Ra"; "Trendler Pilsner";
     "Ridiculoid"; "Erwartungen ausarbeiten"; "Sammler 2017";
     "Erzwungene Harmonie"; "Awake Minds Coffee"; "Iced Darjeeling Tea";
     "PopHands Soda Pop"; "Flaschenauswahl im Haus"; "Ourison"; "Chaotisch";
     "Augen geschlossen & offen"; "Sog"; "Hysterischer Obelisk"; "Obliterative";
     "Heavy Gem Virga"];}

Ja, ich weiß, ich sollte schlafen.

Vergleichen Sie: Deserialisierung, Unterschiede festlegen und Serialisierung

Unser nächster Schritt ist das Schreiben unserer Compare-Funktion, die:

  1. Deserialisiert vorherige Kratzer; Um die Dinge einfach zu halten, serialisiere ich einfach den JSON und schreibe ihn in eine Datei mit dem Namen "TiredHandsScrape.json". Wird diese Datei nicht gefunden, wird sie automatisch in Schritt 4 erstellt
  2. Vergleicht die Biere des vorherigen Kratzers mit dem aktuellen, indem Sie Unterschiede festlegen verwenden, um die in dem aktuellen Kratzer vorhandenen Biere zu ermitteln, die nicht im vorherigen Kratzer enthalten sind
  3. Gibt entweder einen Erfolg aus, der aus einer Differenz oder einem Fehler eines ComparerError oder eines NoDifference besteht
  4. Serialisiert das Ergebnis des Scrapes, um als vorheriges Scrape BeerInfo für das nächste zu dienen, und speichert es in der Datei TiredHandsScrape.json.

Wir verschieben die für diesen Vergleichsschritt relevanten Funktionen in ein eigenes Modul in Common.fs mit dem Namen Compare.

Insgesamt sieht dieses Modul so aus:

Die JSON-Datei TiredHandsScrape.json aus einem späteren Scrape mit der Vergleichsfunktion sieht folgendermaßen aus:

{
  "Biere": [
    "HopHands",
    "SaisonHands",
    "Wunscherfüllung ",
    "Alien Church",
    "Fortschritt durch Verlernen II",
    "Aloha Ra",
    "Trendler Pilsner",
    "Ridiculoid",
    "Erwartungen ausarbeiten",
    "Sammler 2017",
    "Erzwungene Harmonie",
    "Awake Minds Coffee",
    "Iced Darjeeling Tea",
    "PopHands Soda Pop",
    "In House Bottle Selections",
    "Ourison",
    "Chaotisch",
    "Augen geschlossen & offen",
    "Sog",
    "Hysterischer Obelisk",
    "Obliterative",
    "Schwerer Edelstein Virga"
  ],
  "TimeOfScrape": 2017-07-15T13: 21: 00.8194460Z
}

Ich weiß, dass das Hardcodieren des Namens einer Textdatei wahrscheinlich nicht die beste Idee ist, aber keine Sorge, wir werden dieses Modul im nächsten Blog-Beitrag allgemeiner gestalten.

Warnung: Senden eines Textes über die Twilio-API

Das Entwickeln des letzten Elements in der Pipeline ist recht einfach, da die Twilio-API recht einfach zu verwenden ist.

Twilio ist eine „Cloud-Kommunikationsplattform zum Erstellen von SMS-, Sprach- und Messaging-Anwendungen auf einer API, die für eine globale Skalierung entwickelt wurde.“

Wir beginnen mit der Erstellung eines neuen Moduls namens „Alert“ in unserer Datei Common.fs und verwenden die C # Twilio-API, um unseren Text zu senden, falls Unterschiede zwischen den beiden Scrapes bestehen. Ein großartiger Ort, um loszulegen, ist hier. Nachdem Sie ein Konto eingerichtet und die zu verwendende Telefonnummer, die Konto-SID und das Authentifizierungstoken erhalten haben, sollten Sie mit dem Senden von Textnachrichten beginnen können.

Der Code für dieses Modul sieht folgendermaßen aus:

Auch hier möchten wir sehr gerne eine Konfigurationsdatei erstellen, anstatt die Informationen von Twilio fest zu codieren, um einen Text zu versenden.

Randnotiz: Ich habe versucht, die Parameternamen der Create-Funktion hinzuzufügen, konnte dies jedoch nicht tun, da der Name des ersten Parameters "to" ist, ein Schlüsselwort in der Sprache F #, das zum Schleifen verwendet wird. Weitere Details hier. Vielleicht gibt es einen Weg, dies zu tun?

Die Pipeline: Alles zusammenbinden

Jetzt versuchen wir, alle Module, die wir entwickelt haben, zu kombinieren, um den neuesten Kratzunterschied, falls dieser gültig ist, über einen Text zu erhalten.

Dieser Kombinationsprozess ist äußerst unkompliziert: Alles, was wir tun müssen, ist, auf diesen Schmelztiegel der Modulfunktionen etwas Magie der Eisenbahnorientierten Programmierung anzuwenden. Insgesamt sieht BeerwayOrientedProgramming.fs so aus:

Schön einfach und doch leistungsstark genug, um alle unsere Fehler zu verarbeiten, nicht wahr?

Zum Erstellen verwenden wir FAKE, um unser Projekt zu erstellen, indem wir die Befehlspalette [Cmd + Shift + P] öffnen:

Wir können den Erfolg der Kompilierung bestätigen, indem wir uns die Build-Meldungen ansehen und überprüfen, ob wir in unserem Build-Zeit-Bericht ein OK erhalten.

Als Nächstes führen wir das Programm aus, indem wir Mono in der folgenden Weise von unserer Shell aus verwenden, indem wir die Verzeichnisse wechseln, um in unser oberstes BeerwayOrientedProgramming-Verzeichnis zu gelangen. Der Build-Ordner enthält unsere frisch gebackenen DLLs und die Haupt-Exe:

mono build / BeerwayOrientedProgramming.exe

Wenn keine Unterschiede in der Bierauswahl festgestellt werden, erhalten wir:

Failure NoDifference

Wenn wir unser Programm zum ersten Mal ausführen oder Unterschiede zum vorherigen Scrape festgestellt werden, erhalten wir über die Befehlszeile Folgendes:

Erfolg "Neue Nachricht erfolgreich gesendet"

Und wenn die Sterne ausgerichtet sind, erhalten wir einen Text ähnlich dem folgenden:

Fehlerbehebung

Die Reihenfolge der Kompilierung in unserer fsproj-Datei sollte folgendermaßen aussehen:

Wenn die Kompilierung fehlschlägt, müssen wir sicherstellen, dass die Kompilierungsreihenfolge ähnlich ist. Beachten Sie, dass BeerInfo.fs und ROP.fs ausgetauscht werden können, da sie unabhängig von anderen Modulen sind.

Ein anderes Problem ist die falsche Eingabe der Twilio-API-Details, die über unsere robuste Pipeline leicht zu identifizieren sein sollten.

Nächste Schritte

Nachdem wir eine grundlegende Pipeline erstellt haben, möchten wir einige weitere Funktionen hinzufügen, nämlich:

  1. Konfigurieren Sie die Literale so, dass keiner dieser hartcodierten Hokuspokus fortgesetzt wird
  2. Protokollierung der Ergebnisse auf dem Weg; Dies ist insbesondere in Situationen hilfreich, in denen sich das Markup erheblich ändert und keine Ergebnisse angezeigt werden. Darüber hinaus möchten wir eventuell die Regression nutzen, um die Muster der Bierfreisetzung basierend auf den Protokollinformationen vorherzusagen
  3. Planen Sie den Prozess so, dass er rechtzeitig ausgeführt wird
  4. Generalisierung des Schedulers zur Ausführung der Pipeline für mehrere Brauereien
  5. Testen unserer ROP-Funktionen
  6. Hinzufügen von asynchronen Workflows für alle unsere E / A-Interaktionen.

Wir werden dies in einem kommenden Beitrag besprechen. Bleiben Sie also auf dem Laufenden.

Fazit

In diesem Blogpost haben wir untersucht, wie wir die eisenbahnorientierte Programmierung einsetzen können, um uns auf den Fall aufmerksam zu machen, dass die Bierauswahl in der Brauerei Tired Hands geändert wurde. Im nächsten Beitrag planen wir, auf dieser Grundlage aufzubauen, um viel mehr Funktionen hinzuzufügen, wie oben erwähnt.

Den Code dieses Projekts finden Sie hier. Ich habe im Zweig "blogpost1" Code hinzugefügt, der für diesen Blogpost spezifisch ist.

Wie immer wird jedes Feedback sehr geschätzt! Ich hatte eine großartige Zeit, Code für dieses Projekt zu schreiben. Wenn ich es in irgendeiner Weise verbessere, werde ich ein noch glücklicherer Camper sein.

Ein zusätzlicher Dank geht an meinen verrückten Mitmenschen Nick A, der mich auf diese Idee aufmerksam gemacht hat und an Tired Hands Beer.