Samstag, 26. November 2016

mein Browser versteht F#

zugegeben – die Überschrift lügt. Aber dank einiger Werkzeuge ist ein zunächst umständlich klingender Weg von F# zu JavaScript machbar und hat Vorteile gegenüber der direkten Programmierung in JavaScript.

  • F# wird zunächst mit dem fable Compiler in einen Syntaxbaum konvertiert.
  • diese Datenstruktur wird mit babel dann zu JavaScript umgewandelt.
  • wahlweise werden zahlreiche JavaScript Dateien mittels webpack zu einer Datei, was den Ladevorgang beschleunigt und vereinfacht.

Warum will man das tun?


Abgesehen davon, dass ich damit endlich einen Grund habe, mich mehr mit F# zu befassen, kann es durchaus Vorteile haben, in F# zu programmieren und die Starrheit bzw. den präzisen Umgang von F# mit Datentypen zu genießen. Gegenüber JavaScript bietet F# nämlich mindestens diese Vorteile:

  • F# ist stark typisiert. Arithmetische Operationen wie "5" + 2 compilieren gar nicht erst anstelle unerklärbares Verhalten wie in JavaScript zu präsentieren.
  • F# bietet algrabraische Datentypen: Bei einem Record müssen alle Felder gefüllt sein und eine discriminated Union benötigt pro Alternative nur die dafür definierten Werte, diese aber vollständig.
  • Der Umgang mit "null" oder "undefined" ist kein Thema für einen F# Entwickler, dafür nutzt man die sogenannten Options-Typen, die das Fehlen eines Wertes explizit modellieren. Werden diese Typen eingesetzt, ist eine Behandlung eines fehlenden Wertes zwingend notwendig damit das Programm compiliert.
  • Der Compiler bietet zwar Typ-Inferenz (der Datentyp von Parametern wird typischerweise nicht angegeben sondern aufgrund der Verwendung erkannt), besteht aber darauf, dass eine Funktion immer nur in der vorgesehenen Weise verwendet wird.
  • Fallunterscheidungen müssen vollständig sein – fehlende Behandlung von Alternativen führen zu Compiler-Fehlern.
  • Viele Datentypen sind unveränderlich (immutable) und garantieren damit, dass jeder Empfänger auch die gleichen Daten erhält.
diese Liste lässt sich noch beliebig verlängern. Selbstverständlich kann man mit der Kneifzange auch Konstrukte im Quellcode haben, die obige Einschränkungen umgehen, aber bei typischer Verwendung kann man durchaus die Vorteile von F# genießen und wird damit übliche Probleme bei der direkten Verwendung von JavaScript vermeiden.

Gibt es in F# programmierte Frameworks?

Selbstverständlich – und was für welche :-) Inspiriert von der Programmiersprache Elm gibt es mehrere Implementierungen der Elm Architektur für F#. Eine davon ist fable-elmish, der ich mich für den Rest dieses Artikels widmen möchte.

Berühmt geworden ist die Elm-Architektur durch ihre Einfachkeit. Eine damit programmierte Single-Page (oder auch iOS- bzw. Android-native) Applikation wird in typischerweise kleine Komponenten aufgeteilt, die durch geeignete Komposition dann zu einer zunehmend größeren Anwendung wachsen. Das ist bei funktionaler Programmierung ja nichts neues, für Leute, die eher an klassische JavaScript Frameworks denken erfordert es etwas Umdenken.

Jede Komponente besitzt hierbei

  • Einen Datentyp [Msg] für alle Nachrichten, die von oder zu dieser Komponente gesandt werden. Das ist meist eine discriminated Union mit allen notwendigen Alternativen nebst zusätzlichen Transport-Daten.
  • Ein weiterer Datentyp [Model] stellt die möglichen Zustände dar. Von einem leeren Datentyp für zustandslose Komponenten über einfache Typen wie boolsche Werte oder Zahlen bis hin zu Records ist hier alles denkbar. Was auch immer notwendig ist, den internen Zustand der Komponente zu speichern wird hier deklariert.
  • Eine Initialisierungs-Funktion [init] (wahlweise mit oder ohne Parameter) erzeugt eine basierend auf dem Parameter definierte initiale Befüllung für das Model sowie (falls notwendig) nach der Initialisierung abgesandter Nachrichten.
  • Eine Aktualisierungs-Funktion [update] erhält eine Nachricht und den bisherigen Zustand des Models und liefert einen neuen Wert für das Model sowie im Anschluß zu versendende Nachrichten.
  • Eine Anzeige-Funktion [view] erhält ein Model und liefert eine optische Repräsentation des Models. Das kann via VirtualDom Implementierungen oder durch den Einsatz von React passieren. Die Folge davon ist die Aktualisierung der Anzeige im Browser.


Besonders interessant an diesem Ansatz ist, dass jede Komponente ausschließlich für sich selbst sorgt und bestenfalls zusätzliche Dinge an Kind-Komponenten delegiert. In meinen Beispielen werden sämtliche Nachrichten und Modelle in der jeweiligen Komponente gespeichert, ein zentrales F#-Modul dafür wäre genau so gut denkbar und findet sich in zahlreichen Beispielen.

Hinter den Kulissen arbeitet im Kern der Fable-Elmish Anwendung ein F# MailboxProcessor (die F# Standard-Bibliothek Variante einer Actor-Model Implementierung) sämtliche auftretenden Nachrichten ab und koordiniert die notwendigen update- und view-Aufrufe.

Die Vorbereitung ist ein steiniger Weg

Um ein neues Projekt zu beginnen, sind eine Reihe von Dingen notwendig.

  • Anlegen eines F# Projektes (optimalerweise mit .fsproj Datei)
  • Erzeugung eines F# Moduls für die Haupt-Komponente (weitere dann später)
  • Erstellung einer package.json Datei und Installation diverser node.js Module
  • Um Tipparbeit zu sparen Hinterlegung zweier Konfigurations-Dateien für den fable-Compiler (fableconfig.json) und das Web-Pack (webpack.config.js). Letzteres ist sinnvoll, damit als Ergebnis des Compilierungs-Schrittes nur eine JavaScript Datei entsteht.
  • Bearbeitung einer index.html Datei über die das JavaScript in den Browser gelangt

Ein Anfang mit all diesen Dateien vorbereitet ist hier als Repository vorhanden.
Danach kann erstmals der Compiler angeworfen werden und unsere einfache Applikation kann gestartet werden. Selbstverständlich lässt sich die erzeugte JavaScript Datei auch mit anderen Web-Servern als denen von node.js (ASP.NET MVC oder Web API zum Beispiel) ausliefern. Spätestens wenn man JSON Daten via Web API laden möchte wird das erforderlich sein.

Eine zusätzliche Komponente aufnehmen

Gilt es eine bestehende Applikation zu erweitern, benötigen wir lediglich ein weiteres F# Modul für die weitere Komponente. 
  • die .fsproj Datei muss um den Pfad zu dieser Datei erweitert werden. Hierbei an die Compile-Reihenfolge denken und diese Datei vor der Haupt-Applikation compilieren. Visual Studio übernimmt das automatisch, andere Editoren erfordern diesen Schritt.
  • die Haupt-Applikation greift auf die neue Komponente zu und muss in ihren Funktionen init, update und view an entsprechenden Stellen auf die gleichnamigen Funktionen der Komponente delegieren.
  • Das Model der Haupt-Applikation muss ebenfalls den zustand der neuen Komponente mit speichern und dementsprechend erweitert werden.
Die Änderungen, die dafür notwendig sind, habe ich für eine relativ einfache Komponente im "add_component" Branch eingecheckt. Um es nicht zu einfach zu machen hat die Komponente zumindest ein simples Model sowie ein einfaches Verhalten, welches einen Ladevorgang (dieser müsste allerdings noch implementiert werden) simuliert.


War's das?

Keineswegs. Wir haben in diesem Artikel lediglich an der Oberfläche gekratzt. Dadurch dass die einzelnen Lebenszyklen und Verhaltensweisen auf puren Funktionsaufrufen basieren, sind sehr viele Erweiterungen denkbar. Für einige gibt es bereits fertige Module, die man in seine Programme einbinden kann. Zum Beispiel:

  • Aktualisieren der Browsernavigation beim Wechsel einer Seite. Umgekehrt führt eine eingegebene URL zum Ansteuern der gleichen Seite
  • Nachrichten können auch außerhalb der Komponenten erzeugt werden (z.B. Timer oder Websocket-Nachrichten) und gezielt an einzelne Komponenten weiter gegeben werden.
  • Nachrichten lassen sich mitlesen, zum Loggen zum Beispiel

Ich hoffe ich habe einigen von euch Appetit auf Fable gemacht und möchte allen Leuten, die hinter dem Fable-Projekt stehen einmal meinen Respekt und Dank aussprechen. Ich persönlich würde mich über Veröffentlichungen zu Erweiterungen, Tipps, Tricks und Anregungen rund um Fable sehr freuen!

Donnerstag, 23. Juni 2016

FAKE und Sass

Gerne wird CSS nicht mehr direkt selbst geschrieben, sondern mit Präprozessoren wie z.B. SASS erzeugt. Schnell werden dann Projekte mit meist node.js basierenden Hilfsprogrammen ausgestattet, die lokal natürlich immer prima funktionieren, aber auf CI-Servern durchaus Probleme bereiten können. Genau das ist uns kürzlich passiert.

Kurzerhand haben wir beschlossen, die wesentlichen Schritte auf unserem CI Server durch entsprechende Funktionalitäten von FAKE zu ersetzen, was neben durchgängiger Versionskontrolle nur noch Abhängigkeiten von NuGet Paketen hatte.

Und eine Bibliothek für die Compilierung von SASS in das FAKE Script einzubauen war selbst für mich als F# Neuling nicht das große Problem.

Die relevanten Teile des Build Scripts sehen so aus – vermutlich lässt sich noch einiges daran verbessern aber die Abhängigkeit zu "Fremdkörpern" ist hiermit verschwunden:

#r @"packages/FAKE/tools/FakeLib.dll"
#r @"packages/libsassnet/lib/net40/LibSass.x86.dll"
#r @"packages/libsassnet/lib/net40/libsassnet.dll"

open Fake
open System.IO
open LibSassNet

let compileScss files =
    let compile file =
        let filename ext = Path.ChangeExtension(file, ext)

        let compiler = new LibSassNet.SassCompiler()

        tracefn "Compiling %s..." file

        let generateOutput outputStyle cssFile =
            let mapFile = cssFile + ".map"
            let result = compiler.CompileFile(file, outputStyle, mapFile)
            File.WriteAllText(cssFile, result.CSS)
            File.WriteAllText(mapFile, result.SourceMap)

        generateOutput LibSassNet.OutputStyle.Compact (filename ".css")
        generateOutput LibSassNet.OutputStyle.Compressed (filename ".min.css")

    files

        |> Seq.iter compile

// Targets (Auszug)
Target "Css" (fun _ ->
    !! "**/Content/*.scss"
        |> compileScss


)
...

Donnerstag, 24. Dezember 2015

Akka.NET Adventskalender – Tür 24

Zum Schluss

Zum Abschluss unseres kleinen Ausflugs in die Welt von Akka.NET wollen wir uns noch ein ebenfalls häufig notwendiges Mittel ansehen: den Umgang mit Zeit. Relativ oft müssen wir nach einer bestimmten Zeitspanne entweder bestimmte Aktionen auslösen oder kontrollieren, ob eine Aktion ausgelöst wurde, damit wir eventuell passende Maßnahmen einleiten können. Der vollständige Code dieses Beispiels ist wieder im üblichen github Repository.

Und es ist eigentlich klar, dass wir nicht wieder selbst zum Schraubendreher greifen müssen. Auch dafür gibt es eine vorgefertigte Lösung. Nehmen wir an, wir hätten einen Aktor, dessen ActorRef über die Variable controller erreichbar ist und wir wollen ihm regelmäßig alle 300 Millisekunden aber frühestens in 2 Sekunden die Nachricht Tick senden. Das ist so einfach wie:

system
    .Scheduler
    .ScheduleTellRepeatedly(
        TimeSpan.FromSeconds(2),
        TimeSpan.FromMilliseconds(300),
        controller,
        new Tick(),
        ActorRefs.NoSender);

das einzige was zu erklären ist, ist das letzte Argument. Obiges Programmfragment stammt aus einem Kommandozeilen Programm. Das ist natürlich kein Aktor, kann also nicht als Absender der Nachricht eingetragen werden. Würden wir solch eine Nachricht aus einem Aktor heraus versenden, nutzen wir selbstverständlich Self (also uns) als Absender. Das ActorSystem ist von einem Aktor heraus über Context.System erreichbar.

Und damit sind wir so weit, dass wir unser letztes Akka.NET Programm in dieser Artikelserie von github herunterladen und einmal vorsichtig anstarten können.

Du wirst schnell die Logik hinter dem Programm verstehen: Sämtliche Ausgaben auf den Bildschirm werden aus Gründen der Synchronisierung durch den Writer Aktor ausgeführt, der Controller empfängt alle 300 Millisekunden einen Tick und reagiert, indem er Lichter einer bestimmten Farbe jeweils ein- oder ausschaltet. Jedes einzelne Licht wird durch jeweils einen eigenen Light Aktor repräsentiert. Und wie bei unserem Sudoku Programm nutzen wir den Publish/Subscribe Mechanismus zur Kommunikation, anstelle die einzelnen Aktoren miteinander bekannt zu machen.

Frohes Fest!

Wenn Dir die Artikelserie gefallen hat, schreib etwas darüber. Hat Dir die Artikelserie oder einzelne Teile davon nicht gefallen, schreib mir bitte. Wir haben große Teile von Akka.NET vollkommen unberührt gelassen z.B. Remote, Cluster, Persistence, Tests. Wenn Interesse daran besteht, wären weitere Artikel zu diesen Themen oder ein Workshop auf einer geeigneten Konferenz sicher denkbar.

Ich wünsche euch frohe Weihnachten und ein gutes neues Jahr!

Mittwoch, 23. Dezember 2015

Akka.NET Adventskalender – Tür 23

Hab was vergessen

Leider hat unsere gestrige Variante es nicht geschafft, das schwierige Sudoku zu lösen. Natürlich sind wir selbst daran schuld, denn wir haben eine Sudoku Regel schlicht vergessen zu implementieren. "Jede Ziffer darf pro Zeile, Spalte oder Block nur einmal auftreten." Dass das einfache Sudoku geklappt hat, war purer Zufall (ehrlich gesagt musste ich ein wenig suchen, um ein geeignetes einfaches Sodoku zu finden...). Wir brauchen also noch ein paar weitere Dinge, damit wir auch schwierige Sudoku Aufgaben lösen können.

Als erstes müssen wir uns darum kümmern, dass jemand die Häufigkeit der potentiell platzierbaren Ziffern pro Zeile, Spalte und Block für uns mit verfolgt. Jedesmal wenn eine Zelle eine Ziffer gesetzt bekommt, oder eine Ziffer als mögliche Lösung für eine Zelle ausschließt, müssen wir die passende Statistik dazu anpassen. Stellen wir dabei fest, dass eine Ziffer irgendwo exakt einmal auftritt, dann können wir daraus kombinieren, dass diese Ziffer in der jeweiligen Zeile, Spalte oder Block gesetzt werden darf – sie ist ja die einzige ihrer Art.

Grundsätzlich benötigen wir also 3 Typen (Zeile, Spalte, Block) mal 9 (für jede Ziffer) Aktoren für unsere Statistik nach diesem Muster. Aufgrund der einfacheren Lesbarkeit verzichten wir hier auf die Erzeugung einer Basisklasse und wiederholen den doch recht einfachen Code anstelle ihn zu generalisieren.

using System;
using Akka.Actor;
using SudokuSolver.Messages;
using System.Collections.Generic;
using System.Linq;

namespace SudokuSolver.Actors
{
    public class SudokuCol : SudokuActor
    {
        private readonly int col;

        private List<int> statistics;

        public SudokuCol(int col)
        {
            this.col = col;

            statistics = Enumerable.Range(1, 9).Select(_ => 9).ToList();

            Receive<SetDigit>(SetStatistics, s => s.Col == col);
            Receive<StrikeDigit>(UpdateStatistics, s => s.Col == col);
        }

        private void SetStatistics(SetDigit setDigit)
        {
            var digit = setDigit.Digit;

            statistics[digit - 1] = 1;
        }

        private void UpdateStatistics(StrikeDigit strikeDigit)
        {
            var digit = strikeDigit.Digit;

            if (--statistics[digit-1] == 1)
                Publish(new FindColDigit(col, digit));
        }
    }
}

Und wir müssen unsere Aktoren für die einzelnen Zellen dahingend schlauer machen, dass sie dann wenn Ziffern als potentielle Lösungen ausscheiden, jeweils die passende StrikeDigit Nachricht aussenden sowie auf FindXxxDigit Nachrichten reagieren.

Unsere Sudoku Aktoren für die einzelnen Zellen verarbeiten also diese 3 weiteren Nachrichten:

Receive<FindRowDigit>(FindDigitHandler, f => f.Row == row);
Receive<FindColDigit>(FindDigitHandler, f => f.Col == col);
Receive<FindBlockDigit>(FindDigitHandler, f => f.Block == block);

und reagieren darauf wie folgt:

private void FindDigitHandler(FindDigit findDigit)
{
    var digit = findDigit.Digit;

    if (possibleDigits.Contains(digit))
        Publish(new SetDigit(row, col, digit));
}


Damit haben wir auch die gestern vergessene letzte Sudoku Regel mit implementiert und zählen nun 108 Aktoren, die gemeinsam und allein durch wirres Hin- und Herschreien ein Sudoku lösen. Irgendwo faszinierend, dass das funktionert.

Wiederum ist für den fertigen Code ein github Repository vorhanden.

Dienstag, 22. Dezember 2015

Akka.NET Adventskalender – Tür 22

Lass mal hören

Gestern haben wir die Idee zur Lösung unseres Sudoku Spiels entwickelt (Jeder schreit herum, welche Ziffer er eben erhalten hat, andere ziehen daraus ihre Schlüsse).

Dazu müssen wir unser Feld aus 9x9 Aktoren aufbauen. Dabei müssen wir in keinster Weise daran denken, die einzelnen Aktoren miteinander zu verbinden, denn wir setzen ja auf das Muster "Publish/Subscribe", so dass Mitteilungen einfach abgesetzt werden und interessierte sich für den Empfang von Nachrichten anmelden. Damit ist die Konstruktion unseres Spielfeldes extrem einfach:

for (int row = 0; row < 9; row++)
    for (int col = 0; col < 9; col++)
        system.ActorOf(
            Props.Create(printer, row, col), 
            String.Format("{0}-{1}", row, col));

Die nahfolgend gelistete SudokuCell Klasse zeigt die wesentlichen Teile der Implementierung der Sudoku Zelle. Die einzige Nachricht, die wir bislang definiert haben, war SetCell, die das Kommando ist, mit dem wir die Ziffer einer Zelle direkt setzen können. Alle anderen Aktoren registrieren sich für diese Nachricht und verarbeiten sie, wenn die eben gesetzte Zelle in der gleichen Zeile, der gleichen Spalte oder im gleichen 3x3 Block sitzt.

Dazu nutzen wir eine spezielle Überladung der Receive<> Methode, die es uns erlaubt, ein Prädikat mit anzugeben, welches nur dann einen wahren Rückgabewert liefert, wenn wir uns für die Verarbeitung der Nachricht interessieren. Klingt in Worten schlimmer als im Code, den Du gleich sehen wirst. Die ersten Nachrichten vom Typ SetCell werden beim eintragen der berets bekannten Angaben unseres Sudokus ausgelöst. Damit allerdings werden weitere Zellen beeinflusst und das Sudoku löst sich so mit jeder zusätzlichen Angabe immer weiter.

public class SudokuCell : SudokuActor
{
    private readonly int row;
    private readonly int col;
    private readonly int block;
    private HashSet<int> possibleDigits;
 
    public SudokuCell(IActorRef printer, int row, int col)
        : base(printer)
    {
        this.row = row;
        this.col = col;
        this.block = row / 3 * 3 + col / 3;
  
        possibleDigits = new HashSet<int>(Enumerable.Range(1,9));
  
        Receive<SetDigit>(RestrictPossibilities, IsRowOrColOrBlock);
        Receive<SetDigit>(SolveCell, IsMyCell);
    }
 
    private bool IsRowOrColOrBlock(SetDigit setDigit)
    {
        if (IsMyCell(setDigit))
            return false;

        return setDigit.Row == row
            || setDigit.Col == col
            || setDigit.Block == block;
    }
 
    private bool IsMyCell(SetDigit setDigit)
    {
        return setDigit.Row == row && setDigit.Col == col;
    }


    private void RestrictPossibilities(SetDigit setDigit)
    {
        var digit = setDigit.Digit;
  
        if (possibleDigits.Contains(digit))
        {
            possibleDigits.Remove(digit);
            if (possibleDigits.Count == 1)
                Publish(new SetDigit(row, col, possibleDigits.First()));
        }
    }
    
    private void SolveCell(SetDigit setDigit)
    {
        var digit = setDigit.Digit;
  
        possibleDigits.Clear();
        possibleDigits.Add(digit);
    }
 
    private bool IsSolved(int digit)
    {
        return possibleDigits.Contains(digit) && possibleDigits.Count == 1;
    }
}

Eigentlich war es das. Gut, es kommen noch ein paar Kleinigkeiten dazu, damit das ganze tatsächlich funktioniert. Aber bevor wir solche Dinge herunterleiern und Du mühsam alles zusammentragen musst, lade Dir lieber den Code von meinem github Repository.

Wenn Du experimentierfreudig bist, und in der Kommandozeilen-Anwendung versuchst, das schwierige Soduko auflösen zu lassen, wirst Du eine Überraschung erleben – es löst sich nicht. Unser Ansatz war also doch zu einfach.

Müssen wir also morgen nochmal ran.

Montag, 21. Dezember 2015

Akka.NET Adventskalender – Tag 21

Was der Franke unter "blägn" versteht

Während der vergangenen Sprints haben wir uns immer an einzelne bekannte Aktoren gewandt, wenn wir Nachrichten verschickt haben. Diese Woche werden wir uns eine weitere Benachrichtigungs-Strategie ansehen: Ausstrahlung (Broadcast). Das bedeutet, dass wir einen Absender einer Nachricht und keinen, einen oder beliebig viele Empfänger dieser Nachricht haben werden.

Dieses Muster wird auch als "Publish/Subscribe" bezeichnet. Selbstverständlich beherrscht Akka.NET auch dieses Muster. Diesmal werden wir nicht nochmal das Rad neu erfinden, indem wir dieses Muster nachprogrammieren. Stattdessen werden wir direkt die Möglichkeiten nutzen, die wir standardgemäß zur Verfügung haben.

Um eine Nachricht oder ein Ereignis (Ereignisse waren definiert als etwas vergangenes, daher nutzen wir in solchen Fällen gerne die Vergangenheit beim Verb) ausstrahlen möchten, sieht das so aus:

Context.System.EventStream.Publish(new SomethingHappened());

Wenn wir auf ein Ereignis oder eine Nachricht lauschen wollen, dann können wir eine dieser Methoden dazu einsetzen:

protected override void PreStart()
{
    // the most basic way of subscribing
    Context.System.EventStream.Subscribe(Self, typeof(SomethingHappened));
 
    // if you are `using Akka.Event;` you might also do:
    Context.System.EventStream.Subscribe<SomethingHappened>(Self);
}

Um zu demonstrieren, wie leistungsfähig das Ausstrahlen und Abonnieren von Nachrichen ist, werden wir ein kleines Sudoku Spiel zusammen programmieren. Die Idee dazu kam mir kürzlich nach einem Event-Storming Workshop, bei dem knapp 20 Personen im Raum scheinbar planlos umherschossen und am Ende ein durchdachtes Konzept an der Tafel stand. Faszinierend :-)

Und genau so können wir beim Sudoku vorgehen. Wir setzen in jedes einzelne Feld einen Aktor, der für genau dieses Feld zuständig ist. Jeder dieser Aktoren schnappt wertvolle Hinweise darüber auf, ob in seiner Zeile, seiner Spalte oder seinem Block etwas verändert wurde und streicht eventuell eben gesetzte Ziffern von seiner Liste der noch zur Verfügung stehenden Möglichkeiten. Bleibt nur noch eine Ziffer übrig, so ist diese Zelle gelöst. Das wiederum erfahren alle anderen und so löst sich das Rätsel schrittweise von selbst.

Da wir noch mehr Aktoren brauchen werden, packen wir die von allen gemeinsam genutzten Funktionalitäten in eine Basisklasse (morgen gibt es wieder ein github Repository). In dieser Basisklasse verhalten wir uns auch sehr tolerant, indem wir nicht verarbeitete Ereignisse einfach ignorieren. Normalerweise sollte man sich so etwas wirklich gut überlegen.

using Akka.Actor;
using Akka.Event;
using SudokuSolver.Messages;

namespace SudokuSolver.Actors
{
    /// 
    /// Base class for all Sudoko actors
    /// 
    public class SudokuActor : ReceiveActor
    {
        private readonly IActorRef printActor;

        public SudokuActor(IActorRef printActor)
        {
            this.printActor = printActor;
        }

        protected override void PreStart()
        {
            Context.System.EventStream.Subscribe<SetDigit>(Self);
        }

        protected void Publish(object message)
        {
            Context.System.EventStream.Publish(message);
        }

        protected override void Unhandled(object message)
        {
            // we do nothing, just ignore unhandled messages
        }
    }
}

und natürlich müssen wir die Nachricht definieren, die die Ziffer einer Zelle setzt. Darauf lauschen ja alle Zellen und ziehen ihre Schlußfolgerungen.

namespace SudokuSolver
{
    public class SetDigit
    {
        public int Row { get; set; }
        public int Col { get; set; }
        public int Block { get { return Row / 3 * 3 + Col / 3; } }

        public int Digit { get; set; }

        public SetDigit(int row, int col, int digit)
        {
            Row = row;
            Col = col;
            Digit = digit;
        }
    }
}

Wie wir dann tatsächlich das Sudoku lösen, verrate ich euch morgen.

Sonntag, 20. Dezember 2015

Akka.NET Adventskalender – Tag 20

Nur zusammen ergibt alles Sinn

An den vergangenen zwei Tagen haben wir uns theoretisch mit Map/Reduce befasst. Heute wollen wir uns die Programmierung dazu näher ansehen (Hinweis: am Ende steht ein Link auf ein github Repository – Du musst also nicht so viel tippen heute).

Der erste Aktor, der die Rohdaten verarbeitet, ist der Map Aktor. Wie beabsichtigt erzeugt er eine Liste von {Sprache, Anzahl} Tupeln, wobei die Anzahl für alle erzeugten Listeneinträge "1" ist. Das Ergebnis wird dem Absender geantwortet und kommt so beim Master wieder an.

using System;
using MapReduce.Messages;
using Akka.Actor;
using System.IO;
using System.Linq;

namespace MapReduce.Actors
{
    public class Mapper : ReceiveActor
    {
        public Mapper()
        {
            Receive<string>(Map);
        }

        private void Map(string input)
        {
            var mapResult = new MapResult();

            using (var reader = new StringReader(input)) 
            {
                string line;
                while ((line = reader.ReadLine()) != null) 
                {
                    if (!String.IsNullOrWhiteSpace(line))
                    {
                        var language = line.Split(new [] { '|' }).Last().Trim();

                        mapResult.Counts.Add(new LanguageCount(language));
                    }
                }
            }

            Sender.Tell(mapResult);
        }
    }
}

Die MapResult Nachricht, die der Map Aktor erzeugt, wird durch den Master an den Reduce Aktor weiter gereicht, der sie dann entsprechend gruppiert:

using System;
using Akka.Actor;
using MapReduce.Messages;

namespace MapReduce.Actors
{
    public class Reducer : ReceiveActor
    {
        public Reducer()
        {
            Receive<MapResult>(Reduce);
        }

        private void Reduce(MapResult mapResult)
        {
            var reduceResult = new ReduceResult();

            foreach (var count in mapResult.Counts)
            {
                if (reduceResult.Result.ContainsKey(count.Language))
                {
                    reduceResult.Result[count.Language] += count.Count;
                }
                else
                {
                    reduceResult.Result[count.Language] = count.Count;
                }
            }

            Sender.Tell(reduceResult);
        }
    }
}

Da sowohl der Map Aktor als auch der Reduce Aktor parallel arbeiten und beide mehrfach existieren, brauchen wir noch einen einzelnen Aktor, der alle Ergebnisse nochmals zusammenfasst. Der Code ist relativ ähnlich zum Reduce Aktor, allerdings wird das finale Ergebnis im Aggregator Aktor gespeichert und kann anschließend abgerufen werden.

using System;
using Akka.Actor;
using MapReduce.Messages;

namespace MapReduce.Actors
{
    public class Aggregator : ReceiveActor
    {
        private ReduceResult reduceResult;

        public Aggregator()
        {
            reduceResult = new ReduceResult();

            Receive<ReduceResult>(Aggregate);
            Receive<GetResult>(_ => Sender.Tell(reduceResult));
        }

        private void Aggregate(ReduceResult result)
        {
            foreach (var language in result.Result.Keys)
            {
                if (reduceResult.Result.ContainsKey(language))
                {
                    reduceResult.Result[language] += result.Result[language];
                }
                else
                {
                    reduceResult.Result[language] = result.Result[language];
                }
            }
        }
    }
}

Wie erwähnt, sind alle Beispiele in diesem git Repository auf github abgelegt.

Liefen wirklich die Aktoren parallel?

Falls es Dir auch schon aufgefallen ist: Glückwunsch! Kaum etwas lief parallel bislang. Wir hatten jeweils nur einen Aktor und die Daten flossen vom einen zum anderen. Einzig zu dem Zeitpunkt, als wir mehr als ein "Dokument" analysieren ließen, konnten der eine Map Aktor und der eine Reduce Aktor parallel laufen.

Aber Du erinnerst Dich sicher noch an unser Konstrukt von vor 3 Tagen? Wenn Du das nutzt, dann werden wir parallel ablaufende Aufgaben erreichen.

someActor = Context.ActorOf(
    Props.Create<SomeActorClass>()
         .WithRouter(new RoundRobinPool(NrWorkers))

Falls Du noch mehr Beispiele für den Einsatz von Map/Reduce suchst, mir hat das Beispiel "Freunde finden" sehr gefallen.

Morgen werden wir unseren letzten Sprint starten. Wir haben bisher einzelne und zahlreiche Aktoren untersucht. Was Nachrichten anging, haben wir stets einen Empfänger addressiert. Das werden wir nächste Woche ändern.