Монада Може би на стероиди
Няма да описвам какво е монада, просто ще покажа една забавна реализация на монадата Maybe (в центъра за ненормално програмиране ли сме?). Нека декларираме прост делегат по следния начин:
Сега ще покажа, че такава проста дефиниция ще бъде достатъчна, за да се създаде пълноценен незадължителен тип.
Монадата трябва да има два метода - Return и Bind. Първият "обвива" немонадична стойност в монада, вторият ви позволява да свържете две монадични изчисления.
За удобство нека създадем статичен клас и да направим всички необходими функции функции за разширение (методи за разширение) от нашия тип и да добавим всички методи към него:
Първата функция, Return, е доста проста. От стойността трябва да направим делегат, който да я връща:
May също трябва да има нещо декларирано, за да се гарантира, че няма стойност. В нашия случай това ще бъде делегатът, който хвърля изключението:
Вторият метод на монадата, Bind, трябва да обвърже две изчисления. Неговият подпис:
Нека го разгледаме по-отблизо.
Първият аргумент всъщност е първата монадична стойност. Вторият аргумент е функция, която създава нова монадична стойност от стойността вътре в монадата. Реализацията на метода Bind трябва да може да получи стойността от монадата. В нашия случай, за да получите стойността, просто се обадете на нашия делегат.
Тук има трик. Методът Bind би могъл да има следната реализация:
Има обаче една уловка. Ако подадем Nothing като първи аргумент, тогава методът Bind ще хвърли изключение веднага след извикването. Но това, което искаме, е Bindда свържедве изчисления, вместода ги извърши. Следователно Bind трябва да отложи получаването на резултата от първата монада и действителното изчисление да приключистойност от монадата, докато стойността не е необходима на нашия Maybe потребител.
Нека добавим още няколко метода към нашето Може би: Select, Where, SelectMany
Методът Select прави известна трансформация на обекта вътре в Maybe. Може да се приложи с Bind и Return:
Where филтрира стойността вътре в Maybe и връща нищо, ако стойността не отговаря на предиката:
SelectMany е аналог на Bind, който ще ни позволи да пишем изрази, използвайки синтаксиса на Linq. Различава се от простото свързване с наличието на крайна проекция от стойностите на двете монади:
Забележително е, че методите Select, Where и SelectMany не знаят нищо за вътрешността на нашето Maybe - те използват само Bind, Return и празна стойност (Нищо за Maybe). Можем да заменим друга реализация на Може би - и тези методи ще останат непроменени. Освен това можем да заменим друга монада, като например List:
... и отново тези методи ще останат същите. Ако имахме типови класове, щяхме да декларираме тези методи над класа тип Monad (както се прави* в Haskell) (*всъщност не).
Последното нещо, което остава, всъщност е използването на нашето Може би:
Нямаме друг начин да получим стойността от монадата, освен като извикаме делегата, което се случва в последните три реда. Последният ред, както се очаква, се проваля с изключение „Не може да се получи стойност“.
Всички заедно можете да видите тук.
Hardcore conf в C++. Каним само професионалисти.