Перше, що слід усвідомити щодо продовження монади, це те, що, по суті, це насправді взагалі нічого не робить . Це правда!
Основна ідея продовження загалом полягає в тому, що воно представляє решту обчислень . Скажімо , у нас є такий вислів , як це: foo (bar x y) z. Тепер витягніть лише частину в дужках, bar x y--це частина загального виразу, але це не просто функція, яку ми можемо застосувати. Замість цього, це те , що нам потрібно застосувати функцію до . Отже, ми можемо говорити про "решту обчислень" у даному випадку як про те \a -> foo a z, що ми можемо застосувати bar x yдля реконструкції повної форми.
Зараз трапляється, що ця концепція "решти обчислень" корисна, але з нею незручно працювати, оскільки це щось поза підвиразом, який ми розглядаємо. Для того, щоб зробити речі краще працювати, ми можемо перетворити речі навиворіт: екстракт підвираз ми зацікавлені в тому , а потім оберніть його в функцію , яка приймає аргумент , який представляє інші обчислення: \k -> k (bar x y).
Ця модифікована версія надає нам велику гнучкість - вона не тільки витягує підвираз із свого контексту, але й дозволяє нам маніпулювати цим зовнішнім контекстом у самому підвиразі . Ми можемо сприймати це як своєрідне призупинене обчислення , що дає нам явний контроль над тим, що відбувається далі. Тепер, як ми могли б це узагальнити? Ну, підвираз майже незмінний, тож давайте просто замінимо його параметром на функцію \x k -> k xвивороту , надаючи нам - іншими словами, не що інше, як додаток функції, зворотне . Ми могли б так само легко написати flip ($)або додати трохи екзотичного смаку іноземної мови та визначити його як оператора |>.
Тепер перекласти кожен фрагмент виразу в цю форму було б просто, хоч і нудно і жахливо заплутано. На щастя, є кращий спосіб. Як програмісти Haskell, коли ми думаємо будувати обчислення у фоновому контексті, наступне, що, на наш погляд, це сказати, це монада? І в цьому випадку відповідь - так , так.
Щоб перетворити це на монаду, ми почнемо з двох основних будівельних блоків:
- Для монади
mзначення типу m aпредставляє наявність доступу до значення типу aв контексті монади.
- Основою наших "призупинених обчислень" є перевернутий додаток функцій.
Що означає мати доступ до чогось типу aв цьому контексті? Це просто означає , що для деякого значення x :: a, ми застосували flip ($)до x, що дає нам функцію , яка приймає функцію , яка приймає аргумент типу a, і застосовує цю функцію x. Скажімо, у нас призупинено обчислення, що містить значення типу Bool. Який тип це нам дає?
> :t flip ($) True
flip ($) True :: (Bool -> b) -> b
Тож для призупинених обчислень тип m aпрацює на (a -> b) -> b... що, мабуть, є антиклімаксом, оскільки ми вже знали підпис Cont, але поки що мені сподобатися.
Цікаво відзначити, що своєрідний "розворот" також застосовується до типу монади: Cont b aпредставляє функцію, яка приймає функцію a -> bта обчислює b. Як продовження представляє "майбутнє" обчислення, так і тип aу підписі представляє в якомусь сенсі "минуле".
Отже, замінивши (a -> b) -> bна Cont b a, який монадичний тип для нашого основного будівельного блоку програми зворотних функцій? a -> (a -> b) -> bперекладається на a -> Cont b a... підпис того ж типу, що returnі, власне, це саме те, що воно є.
З цього моменту все майже випадає безпосередньо з типів: По суті, немає розумного способу реалізації, >>=крім фактичної реалізації. Але що це насправді робить ?
На цьому етапі ми повертаємось до того, що я сказав спочатку: продовження монади насправді нічого не робить . Щось типу Cont r aтривіально еквівалентно чомусь просто типу a, просто подаючи idяк аргумент призупиненого обчислення. Це може змусити запитати, якщо Cont r aмонада, але перетворення настільки тривіальна, чи не повинно aпоодинці також бути монадою? Звичайно, це не працює так, як є, оскільки немає конструктора типу, який можна визначити як Monadекземпляр, але, скажімо, ми додаємо тривіальну обгортку, наприклад data Id a = Id a. Це справді монада, а саме монада ідентичності.
Що робить >>=для ідентичності монади? Підпис типу є Id a -> (a -> Id b) -> Id b, що еквівалентно a -> (a -> b) -> b, що є просто простою функціональною програмою знову. Встановивши, що Cont r aтривіально еквівалентно Id a, ми можемо зробити висновок, що і в цьому випадку (>>=)це просто застосування функції .
Звичайно, Cont r aце божевільний перевернутий світ, де у кожного є козли, тож те, що насправді відбувається, включає перемішування речей навколо неясними способами, щоб об’єднати два призупинених обчислення в нове призупинене обчислення, але по суті, насправді не відбувається нічого незвичного на! Застосування функцій до аргументів, хо хом, ще один день у житті функціонального програміста.