Монада Може би на стероиди

Няма да описвам какво е монада, просто ще покажа една забавна реализация на монадата 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++. Каним само професионалисти.