Монада Maybe на стероїдах, SavePearlHarbor
Ще одна копія хабора
Монада Maybe на стероїдах
Я не розписуватиму, що таке монада, я просто покажу одну кумедну реалізацію монади Maybe (ми ж у хабі «Ненормальне програмування»?).
Давайте оголосимо такий простий делегат:
Зараз я покажу, що такого простого визначення достатньо, щоб створити повноцінний опціональний тип (Optional type).
Монада повинна мати два методи – Return та Bind. Перший «загортає» немонадичний значення в монаду, другий - дозволяє пов'язувати два монадичне обчислення.
Для зручності створимо статичний клас і всі необхідні функції зробимо функціями-розширеннями (extension methods) від нашого типу і всі методи складатимемо в нього:
Перша функція – Return – досить проста. Зі значення ми повинні зробити делегат, який його повертає:
Maybe також має бути оголошено щось, що відповідають за відсутність значення. У нашому випадку це буде делегат, який кидає виняток:
Другий метод у монади - Bind - повинен пов'язувати два обчислення. Його сигнатура:
Давайте з ним розберемося ближче.
Перший аргумент – власне, перше монадичне значення. Другий аргумент - функція, яка із значення всередині монади створює нове монадичне значення. Реалізація методу Bind повинна вміти набувати значення з монади. У нашому випадку, щоб набути значення, досить просто викликати наш делегат.
Тут є певна хитрість. Метод Bind цілком міг мати і таку реалізацію:
Однак тут є каверза. Якщо першим аргументом ми передаємо Nothing, метод Bind викине виняток відразу після виклику. Але ми хочемо, щоб Bindзв'язувавдва обчислення, а невироблявїх. Тому Bind має відкластиодержання результату з першої монади та власне обчислення над значенням з монади доти, доки значення не знадобиться споживачеві нашої Maybe.
Додамо ще кілька методів для нашого Maybe: Select, Where, SelectMany
Метод Select здійснює деяку трансформацію над об'єктом усередині Maybe. Він може бути реалізований за допомогою Bind та Return:
Where фільтрує значення всередині Maybe і повертає Nothing, якщо значення не задовольняє предикату:
SelectMany - це аналог Bind, який дозволить писати нам вирази, використовуючи Linq синтаксис. Від простого Bind відрізняється наявністю фінальної проекції від значень обох монад:
Примітно, що для методів Select, Where і SelectMany нічого не знають про внутрішній пристрій нашого Maybe – вони використовують лише Bind, Return та порожнє значення (Nothing для Maybe). Ми могли б підставити іншу реалізацію Maybe і ці методи залишилися б незмінними. Більше того, ми могли б підставити іншу монаду, наприклад, List:
… і знову ці методи залишилися б такими самими. Якби в нас були тайп-класи (type class), ми оголосили б ці методи над тайп-класом Monad (як це робиться* в Хаскеллі) (*насправді ні).
Останнє, що залишилося - це, власне, використання нашого Maybe:
У нас немає іншого способу отримати значення з монади, крім як викликати делегат, що відбувається в останніх трьох рядках. Останній рядок очікувано падає з винятком «Не можна отримати значення».