sobota 17. června 2017

Funkcionální programování

V poslední době jsem začal více využívat ve svém kódu principy funkcionálního programování. Zčásti to začalo použitím vzoru Maybe, ale rozvinul jsem to dále a zkušenost to byla tak dobrá, že se o ní podělím.

Hned na začátek pro ty, co mají přístup ke kurzům na pluralsight, uvádím odkaz na dobrý kurz, který vše pěkně dopodrobna vysvětluje - Applying Functional Principles in C# od Vladimira Khorikova.

Tento pán má i své vlastní stránky a dostupná je i knihovna s níže popsanými třídami

Pár základů

U funkcionálního programování se klade důraz na to, co se má udělat místo toho,jak se to má udělat. S funkcionálním přístupem k programování se už asi setkal každý, kdo kdy napsal něco v Linqu.

employees.Where(e => e.From > 2010).OrderBy(e => e.Firstname);

Tento dotazovací jazyk je typickým příkladem funkcionálního programování, pokud ho používáme, tak říkáme co se má dělat a málokdo se zabývá tím, jak se to vlastně pod pokličkou provádí.

Proč se vlastně funkcionální přístup používá?

K vysvětlení výhod funkcionálního přístupu je nejlepší použít kus nějakého klasického kódu. Třeba tento kód by měl něco uložit:
public void Save(object something)
{
    if (something == null)
        throw new ArgumentNullException();
 
    if (!(something is object))
        throw new ArgumentException();
 
    Storage.Save(something);
}
A v tomhle okamžiku začínají problémy. Kdykoliv budeme podobnou funkci volat, nemůžeme si být jistí výsledkem. Může ale nemusí dojít k výjimce a na tu musíme reagovat - a tak vlastně vědomě či nevědomě větvíme tok programu - navíc ošetření výjimky v bloku catch-try připomíná nechvalně známý příkaz go-to....
Funkcionální přístup se snaží tento problém řešit - metoda by neměla o své funkci lhát a tedy vždy by měla vracet, co slibuje. Výjimka nemá být způsobem, jak upozornit na problém, u funkcionálního přístupu je výjimka něco tak výjimečného, že je nejlepší v tomto případě ukončit raději celou aplikaci.
Ale před tím, než popíši, jak to vlastně funkcionální programování řeší, stojí za to se vrátit na chvíli k příkladu a podívat, jak ten se s výjimkou popere. Někde ve volacím řetězci nalezneme obvykle nějaký takový kód:
public string SaveOrReportBug(object something)
{
    try
    {
        Save(something);
    }
    catch (ArgumentNullException)
    {
        return "Cannot save null";
    }
    catch (ArgumentException)
    {
        return "Wrong type";
    }
    catch
    {
        return "Something happened";
    }
 
    return string.Empty;
}
Výjimka je přeložena na něco lidsky pochopitelného a takto pak  prezentována uživateli.

Funkcionální programování celý proces jen zkrátí a to zavedením typu Result - pokud metoda proběhne, jak bylo očekáváno (tzv. happy path), tak vrátí true v property IsSuccess , a pokud ne, tak je IsFailure nastaveno na true a v Error lze nalézt chybové hlášením.
public class Result
{
    protected Result(bool isSuccess, string error)
    {
        if (isSuccess && !string.IsNullOrWhiteSpace(error))
        {
            throw new InvalidOperationException();
        }
 
        if (!isSuccess && string.IsNullOrWhiteSpace(error))
        {
            throw new InvalidOperationException();
        }
 
        IsSuccess = isSuccess;
        Error = error;
    }
 
    public bool IsSuccess { get; }
 
    public string Error { getprivate set; }
 
    public bool IsFailure => !IsSuccess;
}
K tomu je možné si napsat extensivní metody, které vytváření objektů tohoto typu zkrátí:
public static Result Fail(string format, params object[] args)
{
    return new Result(
        false,
        string.Format(
            CultureInfo.InvariantCulture,
            format,
            args));
}
 
public static Result Ok()
{
    return new Result(truestring.Empty);
}
Původní příklad se ze zavedením třídy Result změní a návratový typ se změní z void (nic se nevrací) na Result:
public Result Save(object something)
{
    if (something == null)
        return Result.Fail("Cannot save null.");
 
    if (!(something is object))
        return Result.Fail("Wrong Type");
 
    try
    {
        Storage.Save(something);
    }
    catch
    {
        return Result.Fail("Cannot save ");
    }
 
    return Result.Ok();
}
Metody občas i něco vrací, to lze ošetřit zavedením generického typu Result<T> s property Value:
public class Result<T> : Result
{
    private readonly T value;
 
    protected internal Result(T value, bool isSuccess, string error) 
        : base(isSuccess, error)
    {
        this.value = value;
    }
 
    public T Value
    {
        get
        {
            if (!IsSuccess)
            {
                throw new InvalidOperationException();
            }
            return value;
        }
    }
}
Výhodou funkcionálního přístupu je fakt, že se můžeme spolehnout na to, jak se metoda chová a není nutné vyhledávat si v dokumentaci, jaké výjimky mohou nastat a následně je ošetřovat - metoda se chová skutečně jako funkce a  ne jako pseudo funkce, jak je ilustrováno na tomto obrázku:


Konflikt s principem CQS?

Zkratka CQS znamená  Command-Query Separation principle a byla takto definována Martinem Fowler (https://martinfowler.com/bliki/CommandQuerySeparation.html) - ve zkratce se metody dělí na dotazy a na příkazy.

  • Dotazy vždy něco vrací, ale nemění pozorovatelný stav systému
  • Příkazy nic nevrací, ale mění pozorovatelný stav systému

Použití funkcionálního přístupu nemusí nutně znamenat popření těchto pravidel, stačí si jen zavést tuto konvenci:

Command (can’t fail)        public void Save(Data data)
Query (can’t fail)               public Data GetById(int id)
Command (can fail)           public Result Save(Data data)
Query (can fail)                  public Result<Data> GetById(int id)

Konkrétní případ implementace funkcionálního programovaní popíšu příště.

4 komentáře:

  1. Thanks for the informative article. This is one of the best resources I have found in quite some time. Nicely written and great info. I really cannot thank you enough for sharing.

    Shriram Magizhchi
    Shriram Magizhchi Guduvancheri
    Shriram Magizhchi Price
    Shriram Magizhchi Chennai
    Shriram Magizhchi Apartments
    Shriram Magizhchi Flats
    Shriram Magizhchi Review

    OdpovědětVymazat
  2. All the latest updates from the Python Automationminds team. Python Automationminds lets you program in Python, in your browser. No need to install any software, just start coding straight away. There's a fully-functional web-based console and a programmer's text-editor
    Phyton training in Chennai

    OdpovědětVymazat
  3. All the latest updates from the Python Automationminds team. Python Automationminds lets you program in Python, in your browser. No need to install any software, just start coding straight away. There's a fully-functional web-based console and a programmer's text-editor
    Phyton training in Chennai

    OdpovědětVymazat
  4. All the latest updates from the Python Automationminds team. Python Automationminds lets you program in Python, in your browser. No need to install any software, just start coding straight away. There's a fully-functional web-based console and a programmer's text-editor
    Phyton training in Chennai

    OdpovědětVymazat