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.
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.
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í.
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:
Výjimka je přeložena na něco lidsky pochopitelného a takto pak prezentována uživateli.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; }
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 { get; private set; } public bool IsFailure => !IsSuccess; }
K tomu je možné si napsat extensivní metody, které vytváření objektů tohoto typu zkrátí:
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 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(true, string.Empty); }
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ě.
Žádné komentáře:
Okomentovat