Давайте спочатку розмежимо вивчення абстрактних понять та вивчення конкретних їх прикладів .
Ви не збираєтеся дуже далеко ігнорувати всі конкретні приклади з тієї простої причини, що вони абсолютно всюдисущі. Насправді абстракції існують значною мірою, тому що вони уніфікують те, що ви робили б у будь-якому випадку з конкретними прикладами.
З іншого боку, самі абстракції, безумовно, корисні , але вони не відразу необхідні. Можна досить далеко ігнорувати абстракції і просто використовувати різні типи безпосередньо. Ви хочете зрозуміти їх з часом, але ви завжди зможете повернутися до цього пізніше. Насправді я майже можу гарантувати, що якщо ви це зробите, коли ви повернетесь до нього, ви ляпнете по лобі і задумаєтесь, чому ви витратили весь цей час, роблячи речі важким шляхом, а не використовуючи зручні інструменти загального призначення.
Візьмемо Maybe a
як приклад. Це лише тип даних:
data Maybe a = Just a | Nothing
Це все, крім самодокументування; це необов'язкове значення. Або у вас є "просто" щось типу a
, або у вас немає нічого. Скажімо, у вас є якась функція пошуку, яка повертається, Maybe String
щоб відображати пошук String
значення, яке може бути відсутнім. Таким чином, ви узгоджуєте відповідність за значенням, щоб побачити, що це:
case lookupFunc key of
Just val -> ...
Nothing -> ...
Це все!
Дійсно, нічого іншого вам не потрібно. Ні Functor
s, ні Monad
, ні щось інше. Вони виражають загальні способи використання Maybe a
значень ... але вони просто ідіоми, "дизайнерські зразки", як би ви цього не хотіли назвати.
Єдине місце, якому ви справді не можете цього уникнути повністю, є IO
, але все одно це загадкова чорна скринька, тому не варто намагатися зрозуміти, що це означає як Monad
щось таке.
Насправді, ось чіт-лист для всього, про що ви справді потрібно знати IO
:
Якщо щось має тип IO a
, це означає, що це процедура , яка щось робить і випльовує a
значення.
Коли у вас є код коду, який використовує do
нотації, напишіть щось подібне:
do -- ...
inp <- getLine
-- etc...
... означає виконати процедуру праворуч від <-
і призначити результат імені зліва.
Якщо у вас є щось подібне:
do -- ...
let x = [foo, bar]
-- etc...
... це означає призначити значення простого виразу (а не процедури) праворуч від =
імені зліва.
Якщо ви помістите щось там, не призначаючи значення, наприклад:
do putStrLn "blah blah, fishcakes"
... це означає виконати процедуру і проігнорувати все, що вона повертає. Деякі процедури мають тип IO ()
- ()
тип - це такий собі заповнювач, який нічого не говорить, так що просто означає, що процедура щось робить і не повертає значення. Начебто void
функція в інших мовах.
Виконання однієї і тієї ж процедури більше одного разу може дати різні результати; ось така ідея. Ось чому немає ніякого способу "вилучити" значення IO
зі значення, тому що щось у IO
значенні не є значенням, це процедура отримання значення.
Останній рядок у do
блоці повинен бути простою процедурою без призначення, де повернене значення цієї процедури стає зворотним значенням для всього блоку. Якщо ви хочете, щоб повернене значення використовувало якесь вже присвоєне значення, return
функція приймає звичайне значення і надає вам процедуру неоперації, яка повертає це значення.
Крім цього, в цьому немає нічого особливого IO
; ці процедури насправді самі по собі є простими значеннями, і ви можете передавати їх і комбінувати по-різному. Тільки коли вони виконуються в do
блоці, який називається десь main
, вони роблять все, що завгодно.
Отже, у чомусь подібному настільки нудною, стереотипною прикладною програмою:
hello = do putStrLn "What's your name?"
name <- getLine
let msg = "Hi, " ++ name ++ "!"
putStrLn msg
return name
... ви можете прочитати його так само, як і імперативна програма. Ми визначаємо процедуру з назвою hello
. Після його виконання спочатку виконується процедура надрукування повідомлення із запитом вашого імені; далі він виконує процедуру, яка зчитує рядок введення, і присвоює результат name
; тоді він присвоює вираз імені msg
; потім він друкує повідомлення; тоді він повертає ім'я користувача як результат усього блоку. Оскільки name
це a String
, це означає, що hello
це процедура, яка повертає a String
, тому вона має тип IO String
. А тепер ви можете виконати цю процедуру в іншому місці, як і вона getLine
.
Пффф, монади. Кому їх потрібно?