Кажется, что шаблон Haskell часто рассматривается сообществом Haskell как неприятное удобство. Трудно выразить словами то, что я наблюдал в этом отношении, но рассмотрим эти несколько примеров.
- Шаблон Haskell указан в разделе «Гадкий (но необходимый)» в ответ на вопрос: Какие расширения Haskell (GHC) следует использовать / избегать пользователям?
- Шаблон Haskell считается временным / неполноценным решением в Unboxed Векторы потока значений newtype'd (список рассылки библиотек)
- Yesod часто критикуют за то, что он слишком полагается на Template Haskell (см. Сообщение в блоге в ответ на это мнение)
Я видел различные посты в блогах, где люди делают довольно аккуратные вещи с Template Haskell, позволяя использовать более симпатичный синтаксис, который просто невозможен в обычном Haskell, а также огромное сокращение шаблонов. Так почему же на шаблон Хаскелла смотрят свысока? Что делает это нежелательным? При каких обстоятельствах следует избегать Template Haskell и почему?
haskell
template-haskell
Дэн Бертон
источник
источник
Ответы:
Одна из причин, по которой следует избегать Template Haskell, заключается в том, что он вообще не является типобезопасным, что противоречит большей части «духа Haskell». Вот несколько примеров этого:
Exp
, но вы не знаете, является ли это выражением, представляющим a[Char]
или a(a -> (forall b . b -> c))
или что-то еще. TH был бы более надежным, если бы можно было выразить, что функция может генерировать только выражения определенного типа, или только объявления функций, или только шаблоны соответствия конструкторам данных и т. Д.foo
, которая не существует? Не повезло, вы увидите это только при использовании генератора кода и только при обстоятельствах, которые инициируют генерацию этого конкретного кода. Модульное тестирование тоже очень сложно.ТХ тоже откровенно опасен
IO
, включая запуск ракет или кражу вашей кредитной карты. Вам не нужно просматривать все загруженные пакеты, чтобы найти эксплойты TH.Тогда есть некоторые проблемы, которые делают функции TH менее увлекательными для использования в качестве разработчика библиотеки:
generateLenses [''Foo, ''Bar]
.forM_ [''Foo, ''Bar] generateLens
?Q
это просто монада, так что вы можете использовать все обычные функции на нем. Некоторые люди не знают этого, и из-за этого они создают несколько перегруженных версий по существу одних и тех же функций с одинаковыми функциями, и эти функции приводят к определенному эффекту раздувания. Кроме того, большинство людей пишут свои генераторы вQ
монаде, даже когда им это не нужно, что походит на написаниеbla :: IO Int; bla = return 3
; вы предоставляете функции больше «среды», чем нужно, и клиенты этой функции обязаны предоставлять эту среду в качестве эффекта этого.Наконец, есть некоторые вещи, которые делают функции TH менее увлекательными для использования в качестве конечного пользователя:
Q Dec
, она может генерировать абсолютно все на верхнем уровне модуля, и вы не имеете абсолютно никакого контроля над тем, что будет сгенерировано.источник
Это исключительно мое собственное мнение.
Это некрасиво использовать.
$(fooBar ''Asdf)
просто не выглядит красиво. Поверхностно, конечно, но это способствует.Еще страшнее писать. Цитирование иногда срабатывает, но большую часть времени вам приходится делать прививку AST вручную и сантехнику. API является большим и громоздким, всегда есть много случаев , вы не заботитесь о том, но все же нужно отправки, и случаи , которые вы делаете заботу о , как правило, присутствует в нескольких сходных , но не идентичных форм (данные против Newtype, запись стиль против нормальных конструкторов и т. д.). Писать скучно и многократно, достаточно сложно, чтобы не быть механическим. Предложение по реформе решает некоторые из этих вопросов (делая цитаты более применимыми).
Сценическое ограничение - ад. Неспособность объединить функции, определенные в одном и том же модуле, является меньшей его частью: другое следствие - если у вас есть объединение верхнего уровня, все, что находится после него в модуле, будет вне области действия чего-либо до него. Другие языки с этим свойством (C, C ++) делают его работоспособным, позволяя вам перенаправлять объявления, но Haskell этого не делает. Если вам нужны циклические ссылки между объединенными объявлениями или их зависимостями и зависимостями, вы обычно просто испорчены.
Это недисциплинировано. Под этим я подразумеваю, что в большинстве случаев, когда вы выражаете абстракцию, за этой абстракцией стоит какой-то принцип или концепция. Для многих абстракций принцип, лежащий в их основе, может быть выражен в их типах. Для классов типов вы можете часто формулировать законы, которым должны подчиняться экземпляры и которые могут принимать клиенты. Если вы используете новую универсальную функцию GHC для абстрагирования формы объявления экземпляра для любого типа данных (в пределах границ), вы можете сказать: «для типов суммы это работает так, для типов продукта - так». Шаблон Haskell, с другой стороны, является просто макросом. Это не абстракция на уровне идей, а абстракция на уровне AST, что лучше, но скромно, чем абстракция на уровне простого текста. *
Это связывает вас с GHC. Теоретически другой компилятор мог бы реализовать это, но на практике я сомневаюсь, что это когда-нибудь случится. (Это в отличие от различных расширений системы типов, которые, хотя они могут быть реализованы только GHC на данный момент, я легко мог представить себе, что они будут приняты другими компиляторами в будущем и в конечном итоге стандартизированы.)
API не стабилен. Когда в GHC добавляются новые языковые функции, а пакет template-haskell обновляется для их поддержки, это часто связано с несовместимыми с обратным характером изменениями типов данных TH. Если вы хотите, чтобы ваш код TH был совместим с более чем одной версией GHC, вам нужно быть очень осторожным и, возможно, использовать его
CPP
.Существует общий принцип, что вы должны использовать правильный инструмент для работы и самый маленький, который будет достаточным, и в этой аналогии шаблон Haskell выглядит примерно так . Если есть способ сделать это, а не Template Haskell, это, как правило, предпочтительнее.
Преимущество Template Haskell состоит в том, что вы можете делать с ним вещи, которые вы не могли бы сделать другим способом, и это большое. В большинстве случаев то, для чего используется TH, могло бы быть сделано иначе, только если они были реализованы непосредственно как функции компилятора. У TH чрезвычайно выгодно иметь и то и другое, потому что он позволяет вам делать эти вещи, и потому что он позволяет вам создавать прототипы потенциальных расширений компилятора гораздо более легким и многократно используемым способом (см., Например, различные пакеты объективов).
Подводя итог, почему я думаю, что существуют негативные чувства по отношению к шаблону Haskell: он решает много проблем, но для любой конкретной проблемы, которую он решает, он чувствует, что должно быть лучшее, более элегантное, дисциплинированное решение, лучше подходящее для решения этой проблемы, тот, который не решает проблему, автоматически генерируя шаблон, но устраняя необходимость иметь шаблон.
* Хотя я часто чувствую, что
CPP
соотношение сил к весу лучше для тех проблем, которые он может решить.РЕДАКТИРОВАТЬ 23-04-14: То, что я часто пытался получить в вышеизложенном, и только недавно понял точно, что есть важное различие между абстракцией и дедупликацией. Правильная абстракция часто приводит к дедупликации как побочному эффекту, а дублирование часто является явным признаком неадекватной абстракции, но это не то, почему это ценно. Правильная абстракция - это то, что делает код правильным, понятным и обслуживаемым. Дедупликация только делает ее короче. Шаблон Haskell, как и макросы в целом, является инструментом для дедупликации.
источник
Я хотел бы остановиться на нескольких моментах, которые поднимает dflemstr.
Я не нахожу тот факт, что вы не можете перепроверять TH, чтобы это беспокоило. Зачем? Потому что даже если есть ошибка, это все равно будет время компиляции. Я не уверен, усиливает ли это мои аргументы, но по духу это похоже на ошибки, которые вы получаете при использовании шаблонов в C ++. Я думаю, что эти ошибки более понятны, чем ошибки C ++, поскольку вы получите довольно напечатанную версию сгенерированного кода.
Если выражение TH / квазиквотер делает что-то настолько продвинутое, что хитрые углы могут скрыться, то, возможно, это опрометчиво?
Я немного нарушаю это правило с помощью квазиквотеров, над которыми я работал в последнее время (используя haskell-src-exts / meta) - https://github.com/mgsloan/quasi-extras/tree/master/examples . Я знаю, что это приводит к некоторым ошибкам, таким как неспособность объединить обобщенные списки. Тем не менее, я думаю, что есть хороший шанс, что некоторые идеи из http://hackage.haskell.org/trac/ghc/blog/Template%20Haskell%20Proposal окажутся в компиляторе. До тех пор библиотеки для разбора Haskell на TH-деревья являются почти идеальным приближением.
Что касается скорости / зависимостей компиляции, мы можем использовать «нулевой» пакет для вставки сгенерированного кода. Это, по крайней мере, хорошо для пользователей данной библиотеки, но мы не можем добиться большего успеха в случае редактирования библиотеки. Могут ли зависимости TH раздуть сгенерированные двоичные файлы? Я думал, что это исключило все, на что не ссылается скомпилированный код.
Ограничение этапов / разбиение этапов компиляции модуля Haskell отстой.
RE Непрозрачность: это то же самое для любой библиотечной функции, которую вы вызываете. Вы не можете контролировать, что будет делать Data.List.groupBy. У вас просто есть разумная «гарантия» / соглашение о том, что номера версий говорят вам о совместимости. Это несколько другой вопрос изменений, когда.
Здесь использование нуля окупается - вы уже создаете версии сгенерированных файлов - так что вы всегда будете знать, когда изменилась форма сгенерированного кода. Тем не менее, просмотр больших различий может показаться немного сложным для больших объемов сгенерированного кода, так что это единственное место, где полезен лучший интерфейс разработчика
RE Монолитизм: Вы можете, конечно, постобработать результаты выражения TH, используя свой собственный код времени компиляции. Было бы не так уж много кода для фильтрации по типу / имени объявления верхнего уровня. Черт возьми, вы можете себе представить написание функции, которая делает это в общем. Для модификации / демонолитизации квазиквотеров вы можете сопоставить шаблон с «QuasiQuoter» и извлечь использованные преобразования или сделать новые с точки зрения старых.
источник
[Dec]
и удалить ненужные элементы, но допустим, что функция читает внешний файл определения при генерации интерфейса JSON. Тот факт, что вы не используете его,Dec
не останавливает генератор при поиске файла определения, что приводит к сбою компиляции. По этой причине было бы неплохо иметь более ограниченную версиюQ
монады, которая позволяла бы вам генерировать новые имена (и тому подобное), но не позволяла быIO
, чтобы, как вы говорите, можно было фильтровать его результаты / делать другие композиции ,Этот ответ является ответом на вопросы, поднятые Иллисусом, пункт за пунктом:
Я согласен. Я чувствую, что $ () был выбран, чтобы выглядеть так, как будто он был частью языка - используя знакомую символьную палитру Хаскелла. Тем не менее, это именно то, что вы / не хотите / хотите в символах, используемых для сплайсинга макросов. Они определенно смешиваются слишком много, и этот косметический аспект очень важен. Мне нравится внешний вид {{}} для сростков, потому что они довольно визуально различимы.
Я также согласен с этим, однако, как отмечают некоторые из комментариев в «Новых направлениях для TH», отсутствие хороших готовых цитат из AST не является критическим недостатком. В этом пакете WIP я пытаюсь решить эти проблемы в виде библиотеки: https://github.com/mgsloan/quasi-extras . До сих пор я допускаю сращивание в нескольких местах, чем обычно, и могу сопоставлять шаблоны на AST.
Я столкнулся с проблемой невозможности определения циклического TH раньше ... Это довольно раздражает. Есть решение, но оно уродливо - оберните вещи, связанные с циклической зависимостью, в выражение TH, которое объединяет все сгенерированные объявления. Одним из таких генераторов объявлений может быть просто квазиквотер, который принимает код на Haskell.
Это только беспринципно, если вы делаете с ним беспринципные вещи. Единственное отличие состоит в том, что с компилятором реализованы механизмы для абстракции, у вас больше уверенности в том, что абстракция не протекает. Возможно, демократизация языкового дизайна звучит немного страшно! Создатели библиотек TH должны хорошо документировать и четко определять значение и результаты инструментов, которые они предоставляют. Хорошим примером принципиального TH является производный пакет: http://hackage.haskell.org/package/derive - он использует DSL, так что пример многих дериваций / указывает / фактический деривация.
Это очень хороший момент - TH API довольно большой и неуклюжий. Реализация кажется, что это может быть сложно. Однако есть только несколько способов решить проблему представления AST на Haskell. Я полагаю, что копирование TH ADTs и написание конвертера во внутреннее представление AST поможет вам в этом. Это было бы эквивалентно (что немаловажно) усилиям по созданию haskell-src-meta. Это также можно было бы просто повторно реализовать, просто распечатав TH AST и используя внутренний синтаксический анализатор компилятора.
Хотя я могу ошибаться, я не вижу в TH сложность расширения компилятора с точки зрения реализации. Это на самом деле одно из преимуществ «сохранения простоты» и отсутствия фундаментального уровня в некоторой теоретически привлекательной, статически проверяемой шаблонной системе.
Это тоже хороший момент, но несколько драматичный. Хотя в последнее время были добавлены API, они не вызывали частых поломок. Кроме того, я думаю, что благодаря вышеприведенному цитированию AST, о котором я упоминал ранее, API, который фактически должен использоваться, может быть существенно сокращен. Если никакое конструирование / сопоставление не требует отдельных функций и вместо этого выражается в виде литералов, то большая часть API исчезает. Более того, код, который вы пишете, будет проще переносить в представления AST для языков, похожих на Haskell.
Подводя итог, я думаю, что TH - это мощный инструмент, которым почти не уделяется должного внимания. Меньшее количество ненависти может привести к созданию более оживленной экосистемы библиотек, способствующей внедрению большего количества прототипов языковых функций. Было замечено, что TH - это мощный инструмент, который может позволить вам / делать / почти все. Анархия! Что ж, по моему мнению, эта мощь может позволить вам преодолеть большинство ее ограничений и создавать системы, способные к совершенно принципиальным подходам метапрограммирования. Стоит использовать уродливые хаки для имитации «правильной» реализации, поскольку таким образом дизайн «правильной» реализации постепенно станет понятен.
В моей личной идеальной версии нирваны, большая часть языка фактически переместилась бы из компилятора в библиотеки этого разнообразия. Тот факт, что функции реализованы в виде библиотек, не сильно влияет на их способность точно абстрагироваться.
Каков типичный ответ Хаскелла на стандартный код? Абстракция. Какие наши любимые абстракции? Функции и классы типов!
Классы типов позволяют нам определить набор методов, которые затем могут использоваться во всех видах функций, общих для этого класса. Тем не менее, кроме этого, единственный способ, которым классы помогают избежать шаблонов, заключается в предложении «определений по умолчанию». Теперь вот пример беспринципной возможности!
Наборы минимальных привязок не объявляются / проверяется компилятором. Это может привести к непреднамеренным определениям, дающим основание из-за взаимной рекурсии.
Несмотря на большое удобство и мощь, которые это дает, вы не можете указать значения по умолчанию для суперкласса, поскольку экземпляры-сироты 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 состоит в том, что он работает только тогда, когда доступен интерпретатор байт-кода GHC, что не имеет место на всех архитектурах. Поэтому, если ваша программа использует Template Haskell или использует библиотеки, которые его используют, она не будет работать на компьютерах с процессором ARM, MIPS, S390 или PowerPC.
На практике это актуально: git-annex - это инструмент, написанный на Haskell, который имеет смысл запускать на машинах, которые беспокоятся о хранилище, такие машины часто имеют не i386-CPU. Лично я запускаю git-Annex на NSLU 2 (32 МБ ОЗУ, процессор 266 МГц; знаете ли вы, что Haskell отлично работает на таком оборудовании?) Если он будет использовать Template Haskell, это невозможно.
(Ситуация с GHC на ARM в наши дни значительно улучшается, и я думаю, что 7.4.2 даже работает, но точка зрения остается неизменной)
источник
ghci -XTemplateHaskell <<< '$(do Language.Haskell.TH.runIO $ (System.Random.randomIO :: IO Int) >>= print; [| 1 |] )'
)Почему TH плох? Для меня все сводится к следующему:
Подумай об этом. Половина привлекательности Haskell заключается в том, что его высокоуровневый дизайн позволяет вам избежать огромного количества бесполезного шаблонного кода, который вы должны писать на других языках. Если вам нужна генерация кода во время компиляции, вы в основном говорите, что ваш язык или дизайн вашего приложения не помогли вам. И мы, программисты, не любим проваливаться.
Иногда, конечно, это необходимо. Но иногда вы можете избежать необходимости TH, просто будучи немного умнее со своими проектами.
(Другое дело, что TH довольно низкоуровневый. Великолепного высокоуровневого дизайна нет; многие детали внутренней реализации GHC раскрыты. И это делает API склонным к изменениям ...)
источник