Поради (Новічкова) жахи Хаскеля

Я - програміст на Хаскела, і поки я ще пам'ятаю все, чим він страшний. І це хочу записати. Відразу скажу, коли я приступав до Хаскела, я ще не знав практично нічого про функціональне програмуванні, тому одночасно з мовою, потрібно було освоювати нові ідеї і спосіб мислення. І взагалі-то це було здорово. А у страху, як відомо, очі великі. Загалом, я думаю, ця замітка може бути корисна і іншим початківцям. У ній я вкажу на п'ять незрозумілих мені спочатку, але відносно нескладних речей, зрозумівши які хоча б приблизно, освоїти мову мені було набагато легше.

1. Ламбда-функції

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

Ламбда-вираз - сама суть «функцій» - це вираз виду
Значення цього виразу є поки ще безіменна функція одного аргументу (x), щось з ним обчислює (а саме, вираз праворуч від точки). З лямбда-функціями пов'язана серйозна математична теорія, але з точки зору програмування можна вважати ключовим словом для визначення функцій. Дійсно, коли я і до цього вже користувався лямбда в Пітоні (і майже у всіх інших сучасних мовах вони теж є). У Пітоні вони виглядають ось так:
Ними було дуже зручно користуватися в filter () та reduce (). І взагалі, майже всюди, де в якості аргументу слід вказати ім'я функції. Однак у лямбда-функцій немає імен, і саме тому їх ще називають анонімними (безіменними) функціями. У Пайтон іноді я давав їм імена прямо на льоту:
Тепер ім'я add_42 вказує на функцію. Точно такий же результат можна було отримати, записавши визначення функції як зазвичай:
А що ж щодо Хаскеля? Так майже те ж саме. Символ \ замінює, -> служить замість точки. Все разом записується так:
І ми навіть можемо давати імена таким безіменним функцій, так само як і в Пітоні:
Погодьтеся, дуже схоже.

У Хаскела всі функції є функціями одного аргументу. Спочатку це може здатися обмеженням, але на ділі це дуже зручна і практична ідея. Будь-яку функцію n аргументів можна представити як функцію одного аргументу, що повертає іншу функцію n-1 аргементов. І по науці це азивается каррінг. Ця ідея, зокрема, дозволяє передавати функції тільки частина аргументів.

Навіть ці прості поняття про лямбда-Функ були вже достатні, щоб почати користуватися Хаскела і зрозуміти більшість прикладів і пояснень.

2. Знак рівності

- Ну що, якщо тут немає сенсу, - сказав Король, - тоді у нас гора з плечей: нам нема чого намагатися його знайти! Заощадимо купу роботи! І всеж.

По-моєму, знак рівності (=) - найважливіший символ в Хаскела. Зрозуміти його важливо для розуміння мови. І мені здається, сенс рівності недостатньо підкреслюється у всіляких підручниках. Наприклад, це єдине ключове слово, якого немає у списку ключових слів Хаскеля в його вікі.

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

«Рівна» - не означає «стає». Це означає, що щось одно чогось ще. Завжди. Як в математиці. a = b в Хаскела означає, що a одно b за визначенням. a еквівалентно b.

Таким чином, = в Хаскела служить для запису визначень. «Так само» може визначати самі різні речі, але визначає їх статично. Воно не залежить від порядку виконання операцій. На нього можна покластися.

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

3. Класи типів

Significant benefits arise from sharing a common type system, a common toolset, and so forth. These technical advantages translate into important practical benefits such as enabling groups with moderately differing needs to share a language rather than having to apply a number of specialized languages. - приписується Б. Страуструпом

Система типів в Хаскела просто прекрасна. У ній дуже легко і природно виражаються багато ідей. І можливо, саме класи типів - це найменш чужа концепція для тих, хто приходить в Хаскель з процедурного та об'єктно-орієнтованого світу. У всякому разі, мені так здалося. Однак класи типів - це зовсім не те ж саме, що класи в Сі ++ або в Джаві. Набагато більше вони схожі на абстрактні шаблони класів в Сі ++, тому що класи типів
  • визначають тільки абстрактний інтерфейс
  • дозволяють створювати кілька незалежних реалізацій інтерфейсу (таким чином, для будь-якого типу можна визначити екземпляр класу, якщо надати реалізацію його методів)
  • поліморфні за своєю природою і підтримують успадкування
  • не можуть мати змінних стану

Як тільки ми звикнемо, що типи класів - це не класи С ++, а абстрактні інтерфейси, і екземпляри класів це не «об'єкти», а конкретні реалізації абстрактних інтерфейсів, Хаскель відразу стане звичним і затишним.

Я дуже раджу почитати вікі-статтю OOP vs type classes. яка набагато більш детально порівнює об'єктно-орієнтований підхід і класово-типовий.

І так як будь-яке справжнє стан простий субстанції, природно, є наслідок її попереднього стану, то даний її загрожує майбутнім, - Лейбніц, «Монадологія»

Не важливо, наскільки м'яке введення в Хаскель. рано чи пізно його читач упреться чолом в міцну стіну з монад. Так, це вам не плюшки тирити, це вам серйозна математика. Десь за цією стіною.

Але ось що я зрозумів: вивчати абстрактну математику зовсім не обов'язково, щоб монади використовувати, а вони таки справді трохи витончена програмістська техніка. Спочатку вони здавалися мені трохи дивними, але зрозуміти раз і назавжди монади набагато легше, ніж запам'ятовувати (і правильно застосовувати!) Незліченні шаблони ГО-проектування. Монада логічніше.

Оскільки тьюторіалов по Монада сила-силенна, я не буду їх тут повторювати і очікую, що ви їх вже прочитав парочку. Що ж не так з Монада? Для людини, яка звикла до імперативним мови, зіпсованому роками об'єктно-орієнтованого мислення монади здаються дивними. Вони виглядають як абстрактний клас-контейнер з загадковим методом >> =:
Добре, якщо return - конструктор, то чому таке дивне ім'я? Якщо це клас-контейнер, то як з нього що-небудь витягти? І який сенс застосовувати функцію всередині контейнера (а саме це робить метод >> =. Званий також операцією зв'язування), якщо ми не можемо витягти результат з цього контейнера?

Відповім спочатку на останнє запитання. Навіщо потрібно зв'язування (>> =)? Монада є і одночасно не є контейнерами. Вони - обгортки, упаковки для обчислень. а не для значень (return). Однак вони обертають обчислення не для того, щоб їх було зручніше зберігати в монадних коробочках, а щоб їх можна було зручніше з'єднувати один з одним. Замість «коробочок» уявіть звичайні цеглини, які рівно кладуться один до одного. Це, до речі, схоже на шаблон Adapter в ОО-проектуванні. Кожна монада визначає якийсь спосіб передавати результат від одного обчислення до іншого і реалізує стандартний інтерфейс, щоб цей спосіб використовувати (>> =). І що б не трапилося, результат завжди залишиться в тій же монаді (навіть, якщо станеться збій, fail).

Монада дуже схожі. >> = бере внутрішнє обчислення з монади зліва і підставляє його в обчислення справа, яке завжди повинно створювати ще одну Монада того ж типу.

У більшості мов return повертає результат обчислення з функції. У Хаскела ж він конструктор для монад. Це дуже дивно. Однак подивимося як працює >> =. ця операція витягує значення з монади зліва, а потім пов'язує його з аргументом функції праворуч (звідси, до речі, й іншу назву методу - bind). А функція справа повинна повернути значення назад в Монада, щоб можна було передати естафетну паличку далі наступної операції >> =. Це перше мнемонічне правило: return - повертає обчислене значення назад в Монада.

Друга мнемоніка. Функція верхнього рівня будь-якої програми на Хаскела виконується в монаді IO (тип функції main - IO ()). Ця монада дозволяє виконувати введення-виведення і взагалі будь-які послідовні дії. Таким чином, монадності код виконується на самому верхньому рівні програми, і саме він викликає будь-який «чистий» код в міру необхідності, а не навпаки. Таким чином, будь-яке «чисте» значення, якщо не відкидається, то рано чи пізно повертається в Монада її викликала.

Сподіваюся, що після цих пояснень ім'я return для монадного конструктора більше не здається таким вже дивним. Я, однак, не стверджую, що мої пояснення 100% технічно вірні.


Висновок по монадам

Отже, зв'язування (>> =) дозволяє об'єднувати різні монадние обчислення разом. Майже всюди, де є ланцюг обчислень, монади дуже підходять. Конкретні реалізації монад можуть містити різні правила комбінування обчислень. Ім'я методу return збиває з пантелику початківців, це метод повертає результа обчислення назад в Монада, а не з монади. Загалом, коли я зрозумів ці прості ідеї, це сильно допомогло.

5. Страшні слова

Я знаю тільки те, що нічого не знаю.

Навіть через місяці після того, як я почав вчити Хаскель, вміючи написати якісь корисні програми на ньому, я бачу навколо, в світі Хаскеля, ще багато понять, про які не знаю нічого або маю тільки дуже туманне уявлення. Я називаю такі поняття «страшними словами». І я бачу, що є люди, які створюють і використовують бібліотеки, що втілюють ці поняття в життя.

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

Наприклад, є сучасна XML-бібліотека HXT. Вона дуже багато використовує стрілки. Стрілки - більш універсальні комбінатори обчислень, ніж монади, але мені треба було набагато більше, ніж один день, щоб їх більш-менш зрозуміти. Строго кажучи, стрілки не є частиною мови, але вони - поняття, яке застосовують користувачі цієї мови. І таких прикладів чимало. Хоча тим, кому стрілки освоювати не хочеться, можна користуватися більш традиційної і активно підтримується XML-бібліотекою HaXml.

Я думаю, важливо не боятися «страшних слів». На щастя, основоположні ідеї добре описані. Як правило, є статті їх дуже детально пояснюють. Я сам вирішив освоювати такі ідеї в міру необхідності. Це обіцяє бути і захоплюючим, і одночасно посильним.

висновок

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

Сподіваюся, мої поради будуть корисні і ще кому-небудь.

Схожі статті