Вопрос по haskell – Что такого плохого в шаблоне Haskell?

245

Кажется, что шаблон Haskell часто рассматривается сообществом Haskell как неприятное удобство. Трудно выразить словами то, что я наблюдал в этом отношении, но рассмотрим эти несколько примеров.

Я видел различные посты в блоге, где люди делают довольно аккуратные вещи с шаблоном Haskell, позволяя использовать более приятный синтаксис, который просто невозможен в обычном Haskell, а также огромное сокращение шаблонов. Так почему же на шаблон Haskell смотрят свысока? Что делает это нежелательным? При каких обстоятельствах следует избегать Template Haskell и почему?

Error: User Rate Limit ExceededFAQError: User Rate Limit Exceededthere is no actual problem to be solvedError: User Rate Limit Exceededshould I avoid template haskell, tooError: User Rate Limit ExceededStack Overflow is not a Recommendation Engine. Erik Philips
Error: User Rate Limit Exceeded György Andrasek
Error: User Rate Limit ExceededGOTO still considered harmful? Dan Burton
Error: User Rate Limit Exceeded Gabriel Gonzalez
Error: User Rate Limit ExceededWhat's so bad about Lazy I/O?Error: User Rate Limit Exceeded Dan Burton

Ваш Ответ

6   ответов
7

Error: User Rate Limit Exceededhaskell.org/ghc/docs/7.6.2/html/users_guide/…
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
5

If you need to produce so much repetitive code that you find yourself trying to use TH to auto-generate it, you're doing it wrong!

need

Я не думаю, что это означает, что ваш язык или приложение подвели вас, особенно если мы рассматриваем QuasiQuotes. Когда дело доходит до синтаксиса, всегда есть компромиссы. Некоторый синтаксис лучше описывает определенный домен, поэтому вы хотите иногда иметь возможность переключаться на другой синтаксис. QuasiQuotes позволяют элегантно переключаться между синтаксисами. Это очень мощный инструмент, который используется Yesod и другими приложениями. Возможность писать код генерации HTML с использованием синтаксиса, который выглядит как HTML, - это удивительная особенность.
@CoolCodeBro Да, квазицитирование довольно приятно, и я полагаю, немного ортогонально к TH. (Очевидно, это реализовано поверх TH.) Я больше думал об использовании TH для генерации экземпляров классов, или для создания функций из нескольких арностей, или чего-то в этом роде.
166

You have no control over what kind of Haskell AST a piece of TH code will generate, beyond where it will appear; you can have a value of type Exp, but you don't know if it is an expression that represents a [Char] or a (a -> (forall b . b -> c)) or whatever. TH would be more reliable if one could express that a function may only generate expressions of a certain type, or only function declarations, or only data-constructor-matching patterns, etc. You can generate expressions that don't compile. You generated an expression that references a free variable foo that doesn't exist? Tough luck, you'll only see that when actually using your code generator, and only under the circumstances that trigger the generation of that particular code. It is very difficult to unit test, too.

Code that runs at compile-time can do arbitrary IO, including launching missiles or stealing your credit card. You don't want to have to look through every cabal package you ever download in search for TH exploits. TH can access "module-private" functions and definitions, completely breaking encapsulation in some cases.

TH code isn't always composable. Let's say someone makes a generator for lenses, and more often than not, that generator will be structured in such a way that it can only be called directly by the "end-user," and not by other TH code, by for example taking a list of type constructors to generate lenses for as the parameter. It is tricky to generate that list in code, while the user only has to write generateLenses [''Foo, ''Bar]. Developers don't even know that TH code can be composed. Did you know that you can write forM_ [''Foo, ''Bar] generateLens? Q is just a monad, so you can use all of the usual functions on it. Some people don't know this, and because of that, they create multiple overloaded versions of essentially the same functions with the same functionality, and these functions lead to a certain bloat effect. Also, most people write their generators in the Q monad even when they don't have to, which is like writing bla :: IO Int; bla = return 3; you are giving a function more "environment" than it needs, and clients of the function are required to provide that environment as an effect of that.

Opacity. When a TH function has type Q Dec, it can generate absolutely anything at the top-level of a module, and you have absolutely no control over what will be generated. Monolithism. You can't control how much a TH function generates unless the developer allows it; if you find a function that generates a database interface and a JSON serialization interface, you can't say "No, I only want the database interface, thanks; I'll roll my own JSON interface" Run time. TH code takes a relatively long time to run. The code is interpreted anew every time a file is compiled, and often, a ton of packages are required by the running TH code, that have to be loaded. This slows down compile time considerably.
Естьa proposal to fix some problems with TH.
Error: User Rate Limit Exceeded
И что случилось с обещанием Олега о безопасной для типов альтернативе TH? Я имею в виду его работу, основанную на его "Наконец без тегов, частично оцененных". бумага и еще несколько его заметокhereError: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
28

Error: User Rate Limit Exceeded
Error: User Rate Limit Exceededstackoverflow.com/questions/7107308/…Error: User Rate Limit Exceeded
Error: User Rate Limit ExceededOpacity/Monolithism:Error: User Rate Limit Exceeded[Dec]Error: User Rate Limit ExceededDecError: User Rate Limit ExceededQ монада, которая позволяет вам генерировать новые имена (и тому подобное), но не позволяетIO, так что, как вы говорите, можно фильтровать его результаты / делать другие композиции.
Error: User Rate Limit Exceededgithub.com/technogeeky/zerothError: User Rate Limit Exceeded
Ты когда-нибудь писал свой контраргумент? Все еще жду вашей обещанной ссылки. Для тех, кто ищет старое содержание этого ответа:stackoverflow.com/revisions/10859441/1Error: User Rate Limit Exceededstackoverflow.com/revisions/10913718/6 Dan Burton
49

It's ugly to use. $(fooBar ''Asdf) just does not look nice. Superficial, sure, but it contributes.

It's even uglier to write. Quoting works sometimes, but a lot of the time you have to do manual AST grafting and plumbing. The API is big and unwieldy, there's always a lot of cases you don't care about but still need to dispatch, and the cases you do care about tend to be present in multiple similar but not identical forms (data vs. newtype, record-style vs. normal constructors, and so on). It's boring and repetitive to write and complicated enough to not be mechanical. The reform proposal addresses some of this (making quotes more widely applicable).

The stage restriction is hell. Not being able to splice functions defined in the same module is the smaller part of it: the other consequence is that if you have a top-level splice, everything after it in the module will be out of scope to anything before it. Other languages with this property (C, C++) make it workable by allowing you to forward declare things, but Haskell doesn't. If you need cyclic references between spliced declarations or their dependencies and dependents, you're usually just screwed.

It's undisciplined. What I mean by this is that most of the time when you express an abstraction, there is some kind of principle or concept behind that abstraction. For many abstractions, the principle behind them can be expressed in their types. For type classes, you can often formulate laws which instances should obey and clients can assume. If you use GHC's new generics feature to abstract the form of an instance declaration over any datatype (within bounds), you get to say "for sum types, it works like this, for product types, it works like that". Template Haskell, on the other hand, is just macros. It's not abstraction at the level of ideas, but abstraction at the level of ASTs, which is better, but only modestly, than abstraction at the level of plain text.*

It ties you to GHC. In theory another compiler could implement it, but in practice I doubt this will ever happen. (This is in contrast to various type system extensions which, though they might only be implemented by GHC at the moment, I could easily imagine being adopted by other compilers down the road and eventually standardized.)

The API isn't stable. When new language features are added to GHC and the template-haskell package is updated to support them, this often involves backwards-incompatible changes to the TH datatypes. If you want your TH code to be compatible with more than just one version of GHC you need to be very careful and possibly use CPP.

There's a general principle that you should use the right tool for the job and the smallest one that will suffice, and in that analogy Template Haskell is something like this. If there's a way to do it that's not Template Haskell, it's generally preferable.

have

CPP

Error: User Rate Limit ExceededCOS, ChaosError: User Rate Limit Exceeded
13

It's ugly to use. $(fooBar ''Asdf) just does not look nice. Superficial, sure, but it contributes.

It's even uglier to write. Quoting works sometimes, but a lot of the time you have to do manual AST grafting and plumbing. The [API][1] is big and unwieldy, there's always a lot of cases you don't care about but still need to dispatch, and the cases you do care about tend to be present in multiple similar but not identical forms (data vs. newtype, record-style vs. normal constructors, and so on). It's boring and repetitive to write and complicated enough to not be mechanical. The [reform proposal][2] addresses some of this (making quotes more widely applicable).

The stage restriction is hell. Not being able to splice functions defined in the same module is the smaller part of it: the other consequence is that if you have a top-level splice, everything after it in the module will be out of scope to anything before it. Other languages with this property (C, C++) make it workable by allowing you to forward declare things, but Haskell doesn't. If you need cyclic references between spliced declarations or their dependencies and dependents, you're usually just screwed.

It's unprincipled. What I mean by this is that most of the time when you express an abstraction, there is some kind of principle or concept behind that abstraction. For many abstractions, the principle behind them can be expressed in their types. When you define a type class, you can often formulate laws which instances should obey and clients can assume. If you use GHC's [new generics feature][3] to abstract the form of an instance declaration over any datatype (within bounds), you get to say "for sum types, it works like this, for product types, it works like that". But Template Haskell is just dumb macros. It's not abstraction at the level of ideas, but abstraction at the level of ASTs, which is better, but only modestly, than abstraction at the level of plain text.

It ties you to GHC. In theory another compiler could implement it, but in practice I doubt this will ever happen. (This is in contrast to various type system extensions which, though they might only be implemented by GHC at the moment, I could easily imagine being adopted by other compilers down the road and eventually standardized.)

The API isn't stable. When new language features are added to GHC and the template-haskell package is updated to support them, this often involves backwards-incompatible changes to the TH datatypes. If you want your TH code to be compatible with more than just one version of GHC you need to be very careful and possibly use CPP.

Minimal binding sets are not declarable / compiler checkable. This could lead to inadvertent definitions that yield bottom due to mutual recursion.

Despite the great convenience and power this would yield, you cannot specify superclass defaults, due to orphan instances http://lukepalmer.wordpress.com/2009/01/25/a-world-without-orphans/ These would let us fix the numeric hierarchy gracefully!

Going after TH-like capabilities for method defaults led to http://www.haskell.org/haskellwiki/GHC.Generics . While this is cool stuff, my only experience debugging code using these generics was nigh-impossible, due to the size of the type induced for and ADT as complicated as an AST. https://github.com/mgsloan/th-extra/commit/d7784d95d396eb3abdb409a24360beb03731c88c

In other words, this went after the features provided by TH, but it had to lift an entire domain of the language, the construction language, into a type system representation. While I can see it working well for your common problem, for complex ones, it seems prone to yielding a pile of symbols far more terrifying than TH hackery.

TH gives you value-level compile-time computation of the output code, whereas generics forces you to lift the pattern matching / recursion part of the code into the type system. While this does restrict the user in a few fairly useful ways, I don't think the complexity is worth it.

Error: User Rate Limit Exceeded
Это правда. Я пытался прояснить, о чем идет речь, несмотря на. Возможно, я отредактирую, чтобы встроить точки иллисуиса.
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded
Error: User Rate Limit Exceeded

Похожие вопросы