Навіщо нам потрібні монади?


366

На мою скромну думку, відповіді на відоме питання "Що таке монада?" , особливо найбільш голосуючі, спробуйте пояснити, що таке монада, не чітко пояснюючи, чому монади справді необхідні . Чи можна їх пояснити як вирішення проблеми?




4
Які дослідження ви вже робили? Куди ви подивилися? Які ресурси ви знайшли? Ми очікуємо, що ви зробите значну кількість досліджень, перш ніж задавати питання, і покажіть нам у запитанні, які дослідження ви зробили . Є багато ресурсів, які намагаються пояснити мотивацію ресурсів - якщо ви їх взагалі не знайшли, можливо, вам доведеться провести ще кілька досліджень. Якщо ви знайшли деякі, але вони вам не допомогли, то це стане кращим питанням, якщо ви пояснили, що ви знайшли і чому конкретно вони не працювали для вас.
DW

8
Це, безумовно, краще підходить для Programmers.StackExchange, а не підходить для StackOverflow. Я хотів би проголосувати за міграцію, якщо зможу, але не можу. = (
jpmc26

3
@ jpmc26 Найімовірніше, він там закриється як "насамперед на основі думки"; тут, принаймні, є шанс (як показує величезна кількість оновлень, швидке відкриття вчора, і ще немає закритих голосів)
Ізката

Відповіді:


580

Навіщо нам потрібні монади?

  1. Ми хочемо програмувати лише за допомогою функцій . ("функціональне програмування (FP)" зрештою).
  2. Тоді у нас є перша велика проблема. Це програма:

    f(x) = 2 * x

    g(x,y) = x / y

    Як ми можемо сказати, що потрібно виконати спочатку ? Як ми можемо сформувати впорядковану послідовність функцій (тобто програму ), використовуючи не більше функцій ?

    Рішення: складати функції . Якщо ви хочете спочатку, gа потім f, просто напишіть f(g(x,y)). Таким чином, «програма» є функцією , а також: main = f(g(x,y)). Гаразд, але ...

  3. Більше проблем: деякі функції можуть вийти з ладу (тобто g(2,0)розділити на 0). У FP у нас немає "винятків" (виняток не є функцією). Як ми її вирішуємо?

    Рішення: Дозвольмо функціям повертати два види речей : замість того, щоб мати g : Real,Real -> Real(функція з двох реальних в реальну), давайте дозволимо g : Real,Real -> Real | Nothing(функція з двох реальних в (справжніх або нічого)).

  4. Але функції повинні (бути простішими) повертати лише одне .

    Рішення: давайте створимо новий тип даних, що підлягає поверненню, " тип боксу ", який закриває, можливо, справжній або просто нічого. Отже, ми можемо мати g : Real,Real -> Maybe Real. Гаразд, але ...

  5. Що зараз відбувається з f(g(x,y))? fне готовий до споживання Maybe Real. І ми не хочемо змінювати будь-яку функцію, з якою ми могли б з'єднатися, gщоб споживати a Maybe Real.

    Рішення: давайте мати спеціальну функцію для "підключення" / "складання" / "посилання" функцій . Таким чином, ми можемо, за лаштунками, адаптувати вихід однієї функції для подачі наступної.

    У нашому випадку: g >>= f(підключити / скласти gдо f). Ми хочемо >>=отримати gвихід, перевірити його і, якщо він Nothingпросто не дзвонить fі не повертається Nothing; або, навпаки, витягати коробку Realі годувати fнею. (Цей алгоритм є лише реалізацією >>=для Maybeтипу). Також зауважте, що >>=потрібно писати лише один раз на "тип боксу" (інше поле, різний алгоритм адаптації).

  6. Існує багато інших проблем, які можна вирішити за допомогою цього самого шаблону: 1. Використовуйте "поле" для кодування / зберігання різних значень / значень, і виконайте такі функції, gщо повертають ці "коробкові значення". 2. Попросіть композитора / лінкера, g >>= fякий допоможе підключити gвихід до fвходу, тому нам взагалі нічого не потрібно змінювати f.

  7. Чудовими проблемами, які можна вирішити за допомогою цієї методики, є:

    • маючи глобальний стан, що кожна функція в послідовності функцій ("програма") може розділяти: рішення StateMonad.

    • Нам не подобаються "нечисті функції": функції, які дають різний вихід за один і той же вхід. Тому позначимо ці функції, зробивши їх поверненням позначеного / коробкового значення: IOмонада.

Загальне щастя!


64
@Carl Будь ласка, напишіть кращу відповідь, щоб просвітити нас
XrXr

15
@Carl Я думаю, що у відповіді зрозуміло, що існує багато проблем, які отримують користь від цієї моделі (пункт 6), і що IOмонада - це ще одна проблема в списку IO(пункт 7). З іншого боку, IOз’являється лише один раз і наприкінці, тож не розумійте вашої «більшості часу, коли говорите ... про IO».
cibercitizen1

4
Великі хибні уявлення про монади: монади про державу; монади щодо поводження з винятками; немає способу впровадити IO у чистому FPL без монад; монади однозначні (контраргумент є Either). Найбільше відповідей - на те, «навіщо нам потрібні функтори?».
vlastachu

4
"6. 2. Майте композитора / лінкера, g >>= fщоб допомогти підключити gвихідний сигнал до fвхідного сигналу, тому нам взагалі нічого не потрібно міняти f." це зовсім не правильно . Перед тим, як f(g(x,y)), fможна було виготовити що завгодно. Це могло бути f:: Real -> String. З "монадичним складом" його потрібно змінити на отримання Maybe String, інакше типи не підходять. Більше того, >>=сама по собі не підходить !! Це те, >=>що робить ця композиція, а не >>=. Дивіться дискусію з dfeuer під відповіддю Карла.
Чи буде Несс

3
Ваша відповідь правильна в тому сенсі, що монади ІМО справді найкраще описуються як такі, що стосуються складу / сутності "функцій" (дійсно стрілки Клейслі), але точні подробиці того, який тип йде, куди - це те, що робить їх "монадами". ви можете підключити коробки різними способами (наприклад, Functor тощо). Цей специфічний спосіб з'єднання їх разом визначає "монаду".
Буде Несс

219

Відповідь, звичайно, "у нас немає" . Як і у всіх абстракціях, це не потрібно.

Haskell не потрібна абстракція монади. Це не потрібно для виконання IO чистою мовою. IOТип піклується про те тільки штрафом сам по собі. Існуючий Монадический desugaring з doблоків можуть бути замінені на desugaring bindIO, returnIOі failIOяк це визначено в GHC.Baseмодулі. (Це не задокументований модуль щодо злому, тому мені доведеться вказати на його джерело для документації.) Отже, ні, немає необхідності в абстракції монади.

Тож якщо вона не потрібна, навіщо вона існує? Тому що було встановлено, що багато моделей обчислень утворюють монадичні структури. Абстракція структури дозволяє писати код, який працює у всіх екземплярах цієї структури. Якщо говорити коротше - повторне використання коду.

У функціональних мовах найпотужнішим інструментом для повторного використання коду був склад функцій. Старий добрий (.) :: (b -> c) -> (a -> b) -> (a -> c)оператор надзвичайно потужний. Це дозволяє легко писати крихітні функції та склеювати їх разом із мінімальними синтаксичними чи семантичними накладними.

Але бувають випадки, коли типи виходять не зовсім правильно. Що ти робиш, коли маєш foo :: (b -> Maybe c)і bar :: (a -> Maybe b)? foo . barне вводить перевірку, тому що вони не є одним bі Maybe bтим же типом.

Але ... це майже правильно. Ви просто хочете трохи свободи. Ви хочете мати можливість ставитися Maybe bтак, ніби це в основному b. Це погана ідея, коли б просто розцінювати їх як один і той же тип. Це більш-менш те саме, що і нульові покажчики, які Тоні Хоаре чудово назвав помилкою в мільярд доларів . Тож якщо ви не можете ставитися до них як до одного типу, можливо, ви можете знайти спосіб розширити передбачений механізм композиції (.).

У цьому випадку важливо реально вивчити теорію, що лежить в основі (.). На щастя, хтось це вже зробив для нас. Виявляється, що комбінація (.)і idутворює математичну конструкцію відому як категорію . Але є й інші способи формування категорій. Наприклад, категорія Клейслі дозволяє трохи доповнити об'єкти, що складаються. Категорія Kleisli для Maybeскладатиметься із (.) :: (b -> Maybe c) -> (a -> Maybe b) -> (a -> Maybe c)та id :: a -> Maybe a. Тобто об’єкти в категорії збільшують (->)a з a Maybe, так і (a -> b)стає (a -> Maybe b).

І раптом ми розширили силу композиції на речі, над якими традиційна (.)операція не працює. Це джерело нової сили абстракції. Категорії Kleisli працюють з більшою кількістю типів, ніж просто Maybe. Вони працюють з кожним типом, який може скласти належну категорію, дотримуючись закони категорій.

  1. Ліва особа: id . f=f
  2. Правильна ідентичність: f . id=f
  3. Асоціативність: f . (g . h)=(f . g) . h

Поки ви зможете довести, що ваш тип дотримується цих трьох законів, ви можете перетворити його на категорію Клейслі. І що в цьому великого? Ну, виявляється, монади - це саме те саме, що і категорії Клейслі. Monad«И returnтак же , як Клейслі id. Monad«S (>>=)не збігається з Клейслі (.), але це виявляється дуже легко писати один з точки зору іншого. І закони категорій такі ж, як закони монад, коли ви перекладаєте їх через різницю між (>>=)і (.).

То чому б пройти через усе це турбування? Чому виникає Monadабстракція в мові? Як я вже нагадав вище, це дозволяє повторно використовувати код. Це навіть дозволяє повторно використовувати код у двох різних вимірах.

Перший вимір повторного використання коду відбувається безпосередньо від наявності абстракції. Ви можете написати код, який працює у всіх екземплярах абстракції. Є весь пакет монад-циклів, що складається з циклів, які працюють з будь-яким екземпляром Monad.

Другий вимір - непрямий, але він випливає з існування композиції. Коли композиція проста, цілком природно писати код невеликими шматками для багаторазового використання. Це той самий спосіб, коли (.)оператор функцій заохочує писати невеликі функції багаторазового використання.

То чому існує абстракція? Тому що це доведено як інструмент, який дозволяє більше складу в коді, в результаті чого створюється багаторазовий код і заохочується до створення більш багаторазового коду. Повторне використання коду є одним із святих граалів програмування. Абстракція монади існує тому, що вона трохи рухає нас до цього святого грааля.


2
Чи можете ви пояснити взаємозв'язок між категоріями і категоріями Kleisli? Три закони, які ви описуєте, дотримуються в будь-якій категорії.
dfeuer

1
@dfeuer О. Для того, щоб помістити його в код, newtype Kleisli m a b = Kleisli (a -> m b). Категорії Kleisli - це функції, де категоричний тип повернення ( bв даному випадку) є аргументом конструктору типів m. Iff Kleisli mутворює категорію, mє монадою.
Карл

1
Що таке категоричний тип повернення? Kleisli mздається, утворює категорію, об'єктами якої є типи Haskell і такою, що стрілки від aна b- це функції від aдо m b, з id = returnі (.) = (<=<). Це правильно, чи я змішую різні рівні речей чи щось таке?
dfeuer

1
@dfeuer Це правильно. Об'єкти всіх типів, а морфізм між типами aі b, але вони не прості функціями. Вони прикрашені додатковою mв зворотному значенні функції.
Карл

1
Чи справді потрібна термінологія Теорія категорій? Можливо, Haskell було б простіше, якби ти перетворив типи на зображення, де типом буде ДНК для того, як малюються зображення (хоча типи залежать *), а потім ти використовуєш зображення для написання програми, імена якої мають маленькі рубінові символи над іконою.
aoeu256

24

Про це заявив Бенджамін Пірс у TAPL

Система типу може розглядатися як обчислення свого роду статичного наближення до поведінки термінів у програмі.

Ось чому мова, оснащена потужною системою типу, суворо виразніше, ніж погано набрана мова. Ви можете думати про монадів аналогічно.

Як @Carl та sigfpe , ви можете оснастити тип даних усіма потрібними вам операціями, не вдаючись до монад, класів типу чи будь-яких інших абстрактних матеріалів. Однак монади дозволяють вам не тільки писати код для багаторазового використання, але й абстрагувати всі зайві деталі.

Наприклад, скажімо, що ми хочемо відфільтрувати список. Найпростіший спосіб - використовувати filterфункцію:, filter (> 3) [1..10]яка дорівнює [4,5,6,7,8,9,10].

Трохи складніша версія filter, що також передає акумулятор зліва направо, є

swap (x, y) = (y, x)
(.*) = (.) . (.)

filterAccum :: (a -> b -> (Bool, a)) -> a -> [b] -> [b]
filterAccum f a xs = [x | (x, True) <- zip xs $ snd $ mapAccumL (swap .* f) a xs]

Щоб отримати все i, таке, що i <= 10, sum [1..i] > 4, sum [1..i] < 25ми можемо написати

filterAccum (\a x -> let a' = a + x in (a' > 4 && a' < 25, a')) 0 [1..10]

що дорівнює [3,4,5,6].

Або ми можемо перезначити nubфункцію, яка видаляє повторювані елементи зі списку, з точки зору filterAccum:

nub' = filterAccum (\a x -> (x `notElem` a, x:a)) []

nub' [1,2,4,5,4,3,1,8,9,4]дорівнює [1,2,4,5,3,8,9]. Список передається як акумулятор. Код працює, тому що можна залишити монаду списку, тому весь обчислення залишається чистим ( notElemфактично не використовується >>=, але він міг би). Однак не можна безпечно залишити монаду IO (тобто ви не можете виконати дію IO і повернути чисте значення - значення завжди буде обгорнуте монадою IO). Іншим прикладом є змінні масиви: після того, як ви покинули монаду ST, де живе змінений масив, ви більше не можете оновлювати масив у постійний час. Тому нам потрібна монадійна фільтрація з Control.Monadмодуля:

filterM          :: (Monad m) => (a -> m Bool) -> [a] -> m [a]
filterM _ []     =  return []
filterM p (x:xs) =  do
   flg <- p x
   ys  <- filterM p xs
   return (if flg then x:ys else ys)

filterMвиконує монадійну дію для всіх елементів зі списку, поступаючись елементам, для яких повертається монадійна дія True.

Приклад фільтрації з масивом:

nub' xs = runST $ do
        arr <- newArray (1, 9) True :: ST s (STUArray s Int Bool)
        let p i = readArray arr i <* writeArray arr i False
        filterM p xs

main = print $ nub' [1,2,4,5,4,3,1,8,9,4]

відбитки, [1,2,4,5,3,8,9]як очікувалося.

І версія з монадою IO, яка запитує, які елементи повернути:

main = filterM p [1,2,4,5] >>= print where
    p i = putStrLn ("return " ++ show i ++ "?") *> readLn

Напр

return 1? -- output
True      -- input
return 2?
False
return 4?
False
return 5?
True
[1,5]     -- output

І як остаточну ілюстрацію filterAccumможна визначити через filterM:

filterAccum f a xs = evalState (filterM (state . flip f) xs) a

з StateTмонадою, яка використовується під кришкою, є просто звичайним типом даних.

Цей приклад ілюструє, що монади дозволяють не лише абстрагувати обчислювальний контекст і писати чистий код для багаторазового використання (за рахунок компонованості монад, як пояснює @Carl), але й одночасно обробляти визначені користувачем типи даних та вбудовані примітиви.


1
Ця відповідь пояснює, навіщо нам потрібен клас Monad. Найкращий спосіб зрозуміти, навіщо нам потрібні монади, а не щось інше, - це прочитати про різницю між монадами та прикладними функціонерами: один , два .
користувач3237465

20

Я не думаю, що його IOслід розглядати як особливо видатну монаду, але це, безумовно, одна з найбільш приголомшливих для початківців, тому я використаю це для свого пояснення.

Наївно будувати систему вводу-виводу для Haskell

Найпростіша можлива система вводу-виводу для чисто функціональної мови (і фактично тієї, з якої розпочався Haskell):

main :: String -> String
main _ = "Hello World"

З ледачою, цього простого підпису достатньо, щоб насправді створити інтерактивні термінальні програми - хоча це дуже обмежено. Найбільше засмучує те, що ми можемо виводити лише текст. Що робити, якщо ми додали ще кілька захоплюючих можливостей виведення?

data Output = TxtOutput String
            | Beep Frequency

main :: String -> [Output]
main _ = [ TxtOutput "Hello World"
          -- , Beep 440  -- for debugging
          ]

мило, але, звичайно, набагато реалістичнішим "альтернативним результатом" було б написання файлу . Але тоді ви також хочете прочитати з файлів якийсь спосіб . Будь-який шанс?

Що ж, коли ми беремо нашу main₁програму і просто передаємо файл до процесу (використовуючи засоби операційної системи), ми по суті реалізували читання файлів. Якби ми могли спровокувати це читання файлів з мови Haskell ...

readFile :: Filepath -> (String -> [Output]) -> [Output]

Це використовує "інтерактивну програму" String->[Output], подає їй рядок, отриманий з файлу, і дає неінтерактивну програму, яка просто виконує задану програму.

Тут є одна проблема: ми насправді не маємо поняття, коли файл читається. У цьому [Output]списку впевнений наказ про результати , але ми не отримуємо замовлення про те, коли будуть зроблені входи .

Рішення: внесіть вхідні події також пункти в список речей, які потрібно зробити.

data IO = TxtOut String
         | TxtIn (String -> [Output])
         | FileWrite FilePath String
         | FileRead FilePath (String -> [Output])
         | Beep Double

main :: String -> [IO₀]
main _ = [ FileRead "/dev/null" $ \_ ->
             [TxtOutput "Hello World"]
          ]

Гаразд, тепер ви можете помітити дисбаланс: ви можете прочитати файл і зробити висновок залежним від нього, але ви не можете використовувати вміст файлу, щоб вирішити, наприклад, також прочитати інший файл. Очевидне рішення: зробити результат вхідних подій також чимось типовим IO, а не просто Output. Це впевнено включає простий текст, але також дозволяє читати додаткові файли тощо.

data IO = TxtOut String
         | TxtIn (String -> [IO₁])
         | FileWrite FilePath String
         | FileRead FilePath (String -> [IO₁])
         | Beep Double

main :: String -> [IO₁]
main _ = [ TxtIn $ \_ ->
             [TxtOut "Hello World"]
          ]

Це фактично дозволить вам виразити будь-яку операцію з файлом, яку ви хочете отримати в програмі (хоча, можливо, не з хорошою продуктивністю), але це дещо ускладнюється:

  • main₃дає цілий список дій. Чому ми просто не використаємо підпис :: IO₁, який має особливий випадок?

  • Список справді вже не дає надійного огляду потоку програми: більшість наступних обчислень буде "оголошено" лише в результаті деякої операції введення. Таким чином, ми можемо також скинути структуру списку і просто скласти "і тоді зробити" для кожної операції виводу.

data IO = TxtOut String IO
         | TxtIn (String -> IO₂)
         | Terminate

main :: IO
main = TxtIn $ \_ ->
         TxtOut "Hello World"
          Terminate

Не дуже погано!

Отже, яке все це стосується монад?

На практиці ви не хочете використовувати звичайні конструктори для визначення всіх своїх програм. Потрібно мати пару таких фундаментальних конструкторів, але для більшості матеріалів вищого рівня ми хотіли б написати функцію з гарним підписом високого рівня. Виявляється, більшість із них виглядатимуть досить схоже: прийміть якесь змістовно набране значення та отримаєте в результаті дії IO.

getTime :: (UTCTime -> IO₂) -> IO
randomRIO :: Random r => (r,r) -> (r -> IO₂) -> IO
findFile :: RegEx -> (Maybe FilePath -> IO₂) -> IO

Очевидно, тут є візерунок, і ми краще запишемо його як

type IO a = (a -> IO₂) -> IO    -- If this reminds you of continuation-passing
                                  -- style, you're right.

getTime :: IO UTCTime
randomRIO :: Random r => (r,r) -> IO r
findFile :: RegEx -> IO (Maybe FilePath)

Тепер це починає виглядати знайомим, але ми все ще маємо справу лише з тонко замаскованими простими функціями під кришкою, і це ризиковано: кожна «дія-значення» несе відповідальність за фактичне передавання отриманої дії будь-якої міститься функції (інше потік управління всією програмою легко порушується однією недоброзичливою дією посередині). Ми краще зробимо цю вимогу явною. Ну, виявляється, це закони монад , хоча я не впевнений, що ми можемо реально їх сформулювати без стандартних операторів зв'язування / приєднання.

У будь-якому випадку ми зараз дійшли до формулювання IO, що має належний монадний екземпляр:

data IO a = TxtOut String (IO a)
           | TxtIn (String -> IO a)
           | TerminateWith a

txtOut :: String -> IO ()
txtOut s = TxtOut s $ TerminateWith ()

txtIn :: IO String
txtIn = TxtIn $ TerminateWith

instance Functor IO where
  fmap f (TerminateWith a) = TerminateWith $ f a
  fmap f (TxtIn g) = TxtIn $ fmap f . g
  fmap f (TxtOut s c) = TxtOut s $ fmap f c

instance Applicative IO where
  pure = TerminateWith
  (<*>) = ap

instance Monad IO where
  TerminateWith x >>= f = f x
  TxtOut s c >>= f = TxtOut s $ c >>= f
  TxtIn g >>= f = TxtIn $ (>>=f) . g

Очевидно, що це не ефективна реалізація IO, але вона в принципі корисна.


@jdlugosz : IO3 a ≡ Cont IO2 a. Але я мав на увазі цей коментар більше як кивок для тих, хто вже знає монаду про продовження, оскільки він точно не має репутації як сприятливого для початківців.
Ліворуч

4

Монади - це просто зручна основа для вирішення класу повторюваних проблем. По-перше, монади повинні бути функторами (тобто повинні підтримувати відображення, не дивлячись на елементи (або їх тип)), вони також повинні принести операцію прив'язки (або ланцюжка) та спосіб створити монадичне значення з типу елемента ( return). Нарешті, bindі returnмає задовольняти двом рівнянням (ліва і права тотожність), які також називаються законами монад. (Крім того, можна визначити монади, щоб мати aflattening operation замість зв'язування.)

Список монада зазвичай використовуються для боротьби з Індетермінізм. Операція зв’язування вибирає один елемент списку (інтуїтивно їх усі в паралельних світах ), дозволяє програмісту робити деякі обчислення з ними, а потім об'єднує результати у всіх світах до одного списку (шляхом об'єднання чи вирівнювання вкладеного списку ). Ось як можна було б визначити функцію перестановки в монадійних рамках Haskell:

perm [e] = [[e]]
perm l = do (leader, index) <- zip l [0 :: Int ..]
            let shortened = take index l ++ drop (index + 1) l
            trailer <- perm shortened
            return (leader : trailer)

Ось приклад сеансу відбиття :

*Main> perm "a"
["a"]
*Main> perm "ab"
["ab","ba"]
*Main> perm ""
[]
*Main> perm "abc"
["abc","acb","bac","bca","cab","cba"]

Слід зазначити, що монада списку аж ніяк не є стороною, що впливає на обчислення. Математична структура, яка є монадою (тобто відповідає вищезгаданим інтерфейсам і законам), не передбачає побічних ефектів, хоча побічні явища часто добре вписуються в монадійні рамки.


3

Монади служать в основному для складання функцій разом у ланцюжку. Період.

Тепер спосіб їх складання відрізняється від існуючих монад, внаслідок чого виникає різна поведінка (наприклад, для імітації змінного стану в монаді стану).

Плутанина в монадах полягає в тому, що будучи настільки загальним, тобто механізмом складання функцій, їх можна використовувати для багатьох речей, тим самим змушуючи людей вірити в те, що монади - це про стан, про IO тощо, коли йдеться лише про "композиційні функції" ".

Тепер одна цікава річ про монади - це те, що результат композиції завжди має тип "M a", тобто значення всередині конверта з позначкою "M". Ця функція виявляється дуже приємною для здійснення, наприклад, чіткого поділу між чистим від нечистого коду: оголосити всі нечисті дії як функції типу "IO a" і не надавати жодної функції при визначенні монади IO, щоб вийняти " значення "всередині" IO a ". Результат полягає в тому, що жодна функція не може бути чистою і одночасно виводити значення з "IO a", оскільки немає можливості приймати таке значення, залишаючись чистим (функція повинна бути всередині монади "IO", щоб використовувати таке значення). (ПРИМІТКА: ну, нічого не ідеально, тому "IO straitjacket" можна зламати за допомогою "unsafePerformIO: IO a -> a"


2

Вам потрібні монади, якщо у вас є конструктор типів і функції, які повертають значення цього сімейства типів . Зрештою, ви хочете поєднати такі функції разом . Це три ключові елементи, щоб відповісти, чому .

Дозвольте мені детальніше. Ви маєте Int, Stringі Realі функції типу Int -> String, String -> Realі так далі. Ви можете легко комбінувати ці функції, закінчуючи Int -> Real. Життя чудове.

Потім, одного дня, вам потрібно створити нове сімейство типів . Це може бути тому, що вам потрібно врахувати можливість повернення значення ( Maybe), повернення помилки ( Either), декількох результатів (List ) тощо.

Зверніть увагу, що Maybeце конструктор типу. Він приймає тип, як Intі повертає новий тип Maybe Int. Перше, що потрібно пам’ятати, ні конструктор типів, ні монада.

Звичайно, ви хочете використовувати у своєму коді конструктор типів , і незабаром ви закінчите такі функції, як Int -> Maybe StringіString -> Maybe Float . Тепер ви не можете легко поєднувати свої функції. Життя вже не добре.

І ось коли на допомогу приходять монади. Вони дозволяють знову поєднувати такі функції. Потрібно просто змінити склад . для > == .


2
Це не має нічого спільного з типами сімей. Про що ти насправді говориш?
dfeuer
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.