Nachdem ich gerade mit dem YouTube Kanal von Amichai Mantinband sozusagen die beste Serie des Jahres gekührt habe, kommmt jetzt der beste “Film”. Es ist der Plural Sight Kurs Functional C# 10 von Zoran Horvat. Der Kurs kam im November 2022 heraus. Warum er C# 10 und nicht C# 11 heißt, ist mir ein Rätsel. Davon abgesehen fand ich den Kurs sehr erfrischend, einfach weil Zoran eine wahnsinnig tolle Erzählstimme hat. Noch nie habe ich einen so mitreißenden Videokurs gesehen. Normalerweise schaue ich mir die Kurse auf 2-facher Geschwindigkeit an, das war hier viel zu schnell. Zum einen spricht Zoran schnell, zum anderen war die Informationsdichte (angenehmerweise) sehr hoch.

Jetzt aber zum Inhalt: Der Kurs setzt keine Erfahrung mit dem Thema Funktionale Programmierung voraus. Aber auch, wenn man schon einiges zum Thema gelesen und ausprobiert hat, kann man (wie ich selbst) einiges Wissen aus dem Kurs ziehen. Verwendet wird C# 11 mit .net 7, was bspw. bei der Anwendung des neuen Keywords required und auch bei Switch-Expressions hilfreich für natürlichere Funktionale Programmierung in C# ist. Zuerst wird das Thema Funktionale Programmierung motiviert. Danach wird das Typsystem betrachtet, Pure Functions erklärt, bevor fortgeschrittenere Themen erklärt werden. Das Beispiel, wie man in C# eine Discriminated Union erzeugt, fand ich so gut, dass ich es hier gerne teilen möchte:

Der Beispielcode behandelt Messwerte. Diese können entweder diskret oder kontinuierlich sein, aber nie beides gleichzeitig. Deshalb also discriminated union. In echten funktionalen Programmiersprachen wird dies vom Typsystem unterstützt, in C# (bisher noch) nicht. In der Methode MapAny() wird AsDiscriminatedUnion() genutzt, um sicherzustellen, dass der nachfolgende Aufruf nur von einer Discriminated Union erreicht wird:

public abstract record Measure(string Unit);

public record DiscreteMeasure(string Unit, uint Value) : Measure(Unit);
public record ContinuousMeasure(string Unit, decimal Value) : Measure(Unit);

public static class MeasureExtensions
{
    public static Measure AsDiscriminatedUnion(this Measure m) =>
        m switch
        {
            DiscreteMeasure or ContinuousMeasure => m,
            _ => throw new InvalidOperationException(
                    $"Not defined for object of type {m?.GetType().Name ?? "<null>"}")
        };
    

//beispielhafte Nutzung 
    public static TResult MapAny<TResult>(this Measure m,
        Func<DiscreteMeasure, TResult> discrete,
        Func<ContinuousMeasure, TResult> continuous) =>
        m.AsDiscriminatedUnion() switch
        {
            DiscreteMeasure d => discrete(d),
            ContinuousMeasure c => continuous(c),
            _ => default! //steht hier nur, damit der Compiler keine Warning herausgibt
        };
}

Die beiden konkreten Klassen sind deshalb von ihrer Base-Klasse abgeleitet. Etwas, was man in funktionalen Sprachen nicht machen würde, aber als Workaround für C# aktuell eine sinnvolle Lösung ist.

Angenehm fand ich an dem Kurs, dass die Anwendung der funktionalen Paradigmen nicht dogmatisch erfolgte. So empfahl Zoran, dass man durchaus Methoden auf Klassen/Structs definieren kann, wenn diese sich nur genau auf ihre eigene Klasse bzw. eine eindeutig zugehörige Klasse beziehen. Als Beispiel nannte er ein record struct Year, in dem es eine Methode IEnumerable<Month> GetMonths() gibt.

Mir hat der Kurs sehr gut gefallen und wird sicherlich Einfluss auf meinen weiteren Coding-Stil haben.