Ця відповідь відповідає на питання, поставлені ілліссієм, точка за пунктом:
- Це некрасиво використовувати. $ (fooBar '' Asdf) просто не виглядає приємно. Поверхневий, звичайно, але це сприяє.
Я згоден. Я відчуваю, що $ () був обраний так, щоб він виглядав як частина мови - використовуючи звичний піддон символів Haskell. Однак це саме те, що ви / не / бажаєте в символах, використовуваних для вашої макроскладки. Вони безумовно поєднуються занадто багато, і цей косметичний аспект є досить важливим. Мені подобається вигляд {{}} для сплайсів, тому що вони досить візуально виразні.
- Ще пишніше писати. Цитування спрацьовує іноді, але багато часу доводиться робити вручну прищеплення та сантехніки AST. [API] [1] є великим і громіздким, завжди багато справ, які вам не цікаві, але все одно потрібно відправляти, а справи, які вас цікавлять, як правило, присутні в декількох подібних, але не однакових формах (дані порівняно з новим типом, стилем запису проти звичайних конструкторів тощо). Писати нудно і повторюється досить складно, щоб не бути механічним. [Пропозиція щодо реформи] [2] вирішує деякі з них (роблячи цитати більш широкими).
Я також погоджуюся з цим, однак, як зауважують деякі коментарі у "Нових напрямках для TH", відсутність хорошого котирування AST поза коробкою не є критичним недоліком. У цьому пакеті WIP я намагаюся вирішити ці проблеми в бібліотечній формі: https://github.com/mgsloan/quasi-extras . Поки що я дозволяю спланувати в ще декількох місцях, ніж зазвичай, і можу узгоджувати AST.
- Етапне обмеження - пекло. Неможливо спланувати функції, визначені в одному модулі, є його меншою частиною: інший наслідок - якщо у вас є сплайс верхнього рівня, все після нього в модулі вийде за межі будь-якого раніше. Інші мови з цією властивістю (C, C ++) роблять її працездатною, дозволяючи пересилати декларування речей, але Haskell цього не робить. Якщо вам потрібні циклічні посилання між з'єднаними деклараціями або їх залежностями та залежними, зазвичай вас просто накручують.
Я зіткнувся з тим, що питання циклічних TH-визначень раніше було неможливим ... Це дуже дратує. Є рішення, але це некрасиво - оберніть речі, що беруть участь у циклічній залежності, у виразі TH, який поєднує всі створені декларації. Один з таких генераторів декларацій може бути просто квазі-котирувачем, який приймає код Haskell.
- Це безпринципно. Я маю на увазі під цим те, що більшість часу, коли ви висловлюєте абстракцію, за цією абстракцією стоїть якийсь принцип або концепція. Для багатьох абстракцій принцип, що стоїть за ними, може бути виражений у їх типах. Коли ви визначаєте клас типу, ви можете часто формулювати закони, яким повинні дотримуватися екземпляри, і клієнти можуть їх припускати. Якщо ви використовуєте [нову функцію генерики] [3] GHC, щоб абстрагувати форму декларації екземпляра над будь-яким типом даних (у межах), ви скажете "для типів суми, це працює так, для типів продуктів, він працює так ". Але Шаблон Haskell - це просто німі макроси. Це не абстракція на рівні ідей, а абстракція на рівні AST, що краще, але лише скромно, ніж абстракція на рівні простого тексту.
Це безпринципне лише, якщо ви робите з ним безпринципні речі. Єдина відмінність полягає в тому, що з реалізованими компілятором механізмами абстракції ви маєте більше впевненості в тому, що абстракція не є герметичною. Можливо, демократизація мовного дизайну здається трохи страшною! Творцям бібліотек TH необхідно добре документувати та чітко визначати значення та результати інструментів, які вони надають. Хорошим прикладом принципового TH є пакет виведення: http://hackage.haskell.org/package/derive - він використовує DSL таким чином, що приклад багатьох похідних / вказує / фактичне виведення.
- Це пов'язує вас з GHC. Теоретично інший компілятор міг би його реалізувати, але на практиці я сумніваюся, що це коли-небудь станеться. (Це на відміну від розширень системи різних типів, які, хоча вони можуть бути реалізовані лише GHC на даний момент, я можу легко уявити, що їх прийняли інші компілятори в дорозі і врешті-решт стандартизували.)
Це досить хороший момент - TH API досить великий і незграбний. Повторне впровадження здається, що це може бути важко. Однак існує лише лише кілька способів вирішити проблему представлення Haskell AST. Я думаю, що копіювання TH-ADT та написання перетворювача у внутрішнє представлення AST дозволить вам досягти великого шляху. Це було б рівнозначно (не незначним) зусиллям створення haskell-src-meta. Це також може бути просто реалізовано за допомогою гарного друку TH AST та використання внутрішнього аналізатора компілятора.
Хоча я можу помилятися, я не бачу TH, що є складним розширенням компілятора, з точки зору реалізації. Це фактично одна з переваг "простоти" та відсутності фундаментального шару бути якоюсь теоретично привабливою, статично перевіряється системою шаблонів.
- API не стабільний. Коли до GHC додаються нові мовні функції та оновляється пакет шаблонів haskell для їх підтримки, це часто включає зміни, несумісні із змінами типів даних TH. Якщо ви хочете, щоб ваш TH-код був сумісний з більш ніж однією версією GHC, вам потрібно бути дуже обережним і, можливо, використовувати
CPP
.
Це теж хороший момент, але дещо драматизований. Незважаючи на те, що останнім часом були доповнення API, вони не мали великого рівня злому. Крім того, я вважаю, що при вищенаведеному цитуванні AST, про який я згадував раніше, API, який насправді потрібно використовувати, може бути дуже істотно скорочений. Якщо жодна побудова / відповідність не потребує чітких функцій, а замість цього виражається у формі літералів, то більша частина API зникає. Більше того, написаний вами код буде легше переноситись на представлення AST для мов, схожих на Haskell.
Підводячи підсумок, я вважаю, що TH - це потужний, напівзанедбаний інструмент. Менша ненависть може призвести до більш живої екосистеми бібліотек, що сприятиме впровадженню більше мовних прототипів функцій. Помічено, що TH - це дуже потужний інструмент, який дозволяє вам / робити / майже все. Анархія! Ну, на мою думку, ця потужність може дозволити вам подолати більшість його обмежень і побудувати системи, здатні досить принципово підходи метапрограмування. Варто використовувати некрасиві хаки для імітації «належної» реалізації, оскільки таким чином конструкція «належної» реалізації поступово стане зрозумілою.
У моїй особистій ідеальній версії нірвани значна частина мови фактично перейшла б із компілятора, в бібліотеки такого різноманіття. Те, що функції реалізовані як бібліотеки, не сильно впливає на їх здатність вірно абстрагуватися.
Яка типова відповідь Haskell на код котла? Абстракція. Які наші улюблені абстракції? Функції та типові класи!
Класи типу дозволяють нам визначити набір методів, які потім можуть бути використані у всіх функціях, загальних для цього класу. Однак, окрім цього, єдиний спосіб, що допомагає уникнути котлована, пропонує "визначення за замовчуванням". Тепер ось приклад безпринципної функції!
Мінімальні множини прив'язки не підлягають декларуванню / компілятор можна перевірити. Це може призвести до ненавмисних визначень, які дають нижнє значення через взаємної рекурсії.
Незважаючи на велику зручність та потужність, які це дасть, ви не можете вказати параметри за замовчуванням для суперкласових класів через випадки сиріт http://lukepalmer.wordpress.com/2009/01/25/a-world-without-orphans/ Це дозволить нам виправити виправлення числова ієрархія витончено!
Виконання TH-подібних можливостей для налаштування за замовчуванням призвело до http://www.haskell.org/haskellwiki/GHC.Generics . Незважаючи на те, що це класний матеріал, мій єдиний досвід налагодження коду з використанням цих дженериків був майже неможливим, через розмір типу, індукований для ADT і такий складний, як AST. https://github.com/mgsloan/th-extra/commit/d7784d95d396eb3abdb409a24360beb03731c88c
Іншими словами, це пішло після функцій, наданих TH, але воно повинно було підняти цілий домен мови, мову побудови, у системне представлення типу. Хоча я можу бачити, як це працює добре для вашої поширеної проблеми, для складних, здається, схильна до того, що ви купуєте символи, набагато страшніші, ніж TH хакерство.
TH дає вам обчислення часу вибору коду на рівні значення, тоді як generics змушує вас зняти частину коду, що відповідає / рекурсії коду, в систему типів. Хоча це обмежує користувача кількома досить корисними способами, я не думаю, що складність цього не варта.
Я думаю, що відхилення TH та lisp-подібного метапрограмування призвело до переваги таких речей, як метод за замовчуванням, а не до більш гнучких, макророзширень, як декларацій екземплярів. Дисципліна уникання речей, які можуть призвести до непередбачених результатів, є мудрою, однак, ми не повинні ігнорувати, що здатна система Haskell забезпечує більш надійну метапрограмування, ніж у багатьох інших середовищах (перевіряючи створений код).