265

テンプレートHaskellは、Haskellコミュニティから不幸な便利さとして見られることが多いようです。この点に関して私が観察したことを正確に言葉で表すのは難しいですが、これらのいくつかの例を検討してください

テンプレートHaskellを使って人々がかなりきちんとしたことをし、通常のHaskellでは単純に不可能なより美しい構文を可能にし、ボイラープレートを大幅に削減するさまざまなブログ投稿を見てきました。では、なぜテンプレートHaskellがこのように見下されているのでしょうか?何がそれを望ましくないのですか?テンプレートHaskellを避けるべき状況とその理由は何ですか?

4

6 に答える 6

176

Template Haskellを回避する理由の1つは、全体としてタイプセーフではないため、「Haskellの精神」の多くに反することです。これのいくつかの例はここにあります:

  • THコードの一部が表示される場所を超えてどのような種類のHaskellASTを生成するかを制御することはできません。タイプの値を持つことができますが、それがaまたはaなどExpを表す式であるかどうかはわかりません。THは、関数が特定のタイプの式のみ、関数宣言のみ、またはデータコンストラクターマッチングパターンのみを生成する可能性があることなどを表現できれば、より信頼性が高くなります。[Char](a -> (forall b . b -> c))
  • コンパイルされない式を生成できます。foo存在しない自由変数を参照する式を生成しましたか?運が悪いと、実際にコードジェネレーターを使用しているとき、およびその特定のコードの生成をトリガーする状況でのみ、それがわかります。ユニットテストも非常に困難です。

THも完全に危険です:

  • IOコンパイル時に実行されるコードは、ミサイルの発射やクレジットカードの盗難など、任意の処理を実行できます。THエクスプロイトを探すために、ダウンロードしたすべてのカバールパッケージを調べる必要はありません。
  • THは「モジュールプライベート」関数と定義にアクセスでき、場合によってはカプセル化を完全に破ります。

次に、TH関数をライブラリ開発者として使用するのが面白くなくなるいくつかの問題があります。

  • THコードは常に構成可能であるとは限りません。誰かがレンズ用のジェネレーターを作ったとしましょう。多くの場合、そのジェネレーターは、他のTHコードではなく、「エンドユーザー」だけが直接呼び出すことができるように構成されます。パラメータとしてレンズを生成する型構築子のリスト。そのリストをコードで生成するのは難しいですが、ユーザーはを書くだけで済みますgenerateLenses [''Foo, ''Bar]
  • 開発者は、THコードを作成できることすら知りません。あなたはあなたが書くことができることを知っていましたforM_ [''Foo, ''Bar] generateLensか?Qは単なるモナドなので、通常のすべての関数を使用できます。一部の人々はこれを知りません、そしてそれのために、彼らは同じ機能で本質的に同じ機能の複数のオーバーロードされたバージョンを作成します、そしてこれらの機能は特定の肥大化効果につながります。Qまた、ほとんどの人は、必要がない場合でもモナドでジェネレーターを作成します。これは、作成するのと同じbla :: IO Int; bla = return 3です。関数に必要以上の「環境」を与えているので、関数のクライアントはその効果としてその環境を提供する必要があります。

最後に、TH関数をエンドユーザーとして使用するのが面白くなくなるいくつかのことがあります。

  • 不透明度。TH関数のタイプQ Decがである場合、モジュールのトップレベルで絶対に何でも生成でき、生成されるものを完全に制御することはできません。
  • モノリシズム。開発者が許可しない限り、TH関数が生成する量を制御することはできません。データベースインターフェイスとJSONシリアル化インターフェイスを生成する関数を見つけた場合、「いいえ、データベースインターフェイスのみが必要です。ありがとうございます。自分のJSONインターフェイスをロールします」とは言えません。
  • 実行時間。THコードの実行には比較的長い時間がかかります。コードはファイルがコンパイルされるたびに新たに解釈され、多くの場合、実行中のTHコードには大量のパッケージが必要であり、それらをロードする必要があります。これにより、コンパイル時間が大幅に遅くなります。
于 2012-06-01T20:57:38.203 に答える
54

これは私自身の意見です。

  • 使うのは醜いです。$(fooBar ''Asdf)見栄えが悪いだけです。表面的なことは確かですが、貢献しています。

  • 書くのはさらに醜いです。引用は時々機能しますが、多くの場合、手動のASTグラフトと配管を行う必要があります。APIは大きくて扱いにくく、気にしないがディスパッチする必要があるケースは常にたくさんあります。気になるケースは、類似しているが同一ではない複数の形式で存在する傾向があります(データとニュータイプ、レコード-スタイルと通常のコンストラクターなど)。書くのは退屈で反復的であり、機械的ではないほど複雑です。改革案はこれのいくつかに対処します(引用をより広く適用できるようにします)。

  • ステージ制限は地獄です。同じモジュールで定義された関数をスプライスできないことは、その小さな部分です。他の結果として、トップレベルのスプライスがある場合、モジュール内のそれ以降のすべてが、その前のすべてのスコープから外れます。このプロパティを持つ他の言語(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は、一般的なマクロと同様に、重複排除のためのツールです。

于 2012-06-03T15:16:49.023 に答える
31

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ツリーに解析するためのライブラリはほぼ完全な近似です。

コンパイル速度/依存関係に関しては、「zeroth」パッケージを使用して、生成されたコードをインライン化できます。これは、特定のライブラリのユーザーにとっては少なくとも良いことですが、ライブラリを編集する場合には、これ以上のことはできません。THの依存関係は、生成されたバイナリを肥大化させる可能性がありますか?コンパイルされたコードで参照されていないものはすべて省略されていると思いました。

Haskellモジュールのステージング制限/コンパイルステップの分割は最悪です。

RE不透明度:これは、呼び出すライブラリ関数の場合と同じです。Data.List.groupByが何をするかを制御することはできません。バージョン番号が互換性について何かを教えてくれるという合理的な「保証」/慣習があります。いつ変更するかは多少異なります。

これは、ゼロを使用することで利益が得られる場所です(生成されたファイルはすでにバージョン管理されているため)。したがって、生成されたコードの形式がいつ変更されたかを常に知ることができます。ただし、生成された大量のコードの場合、diffを確認するのは少し厄介かもしれません。そのため、より優れた開発者インターフェイスが便利な場所の1つです。

REモノリシズム:独自のコンパイル時コードを使用して、TH式の結果を確実に後処理できます。トップレベルの宣言タイプ/名前でフィルタリングするのはそれほどコードではありません。一体、これを一般的に行う関数を書くことを想像することができます。準クォーターを変更/非モノリタイズする場合は、「QuasiQuoter」でパターンマッチングを行い、使用されている変換を抽出するか、古い変換を新しいものにすることができます。

于 2012-06-02T02:54:36.663 に答える
15

この答えは、illissiusによって提起された問題に1つずつ対応しています。

  • 使うのは醜いです。$(fooBar'' Asdf)は見栄えがよくありません。表面的なことは確かですが、貢献しています。

同意します。$()は、言語の一部であるように見えるように選択されたように感じます-Haskellのよく知られたシンボルパレットを使用しています。ただし、これは、マクロスプライシングに使用される記号に/望まない/必要なことです。それらは間違いなく混ざりすぎており、この化粧品の側面は非常に重要です。スプライスの{{}}の外観は、視覚的に非常に異なるため、気に入っています。

  • 書くのはさらに醜いです。引用は時々機能しますが、多くの場合、手動のASTグラフトと配管を行う必要があります。[API] [1]は大きくて扱いにくく、気にしないがディスパッチする必要のあるケースは常にたくさんあります。気になるケースは、類似しているが同一ではない複数の形式で存在する傾向があります(データ対newtype、レコードスタイル対通常のコンストラクターなど)。書くのは退屈で反復的であり、機械的ではないほど複雑です。[改革案][2]はこれのいくつかに対処します(引用をより広く適用できるようにします)。

私もこれに同意しますが、「THの新しい方向性」のコメントの一部が観察しているように、すぐに使用できるASTの適切な引用の欠如は重大な欠陥ではありません。このWIPパッケージでは、これらの問題にライブラリ形式で対処しようとしています:https ://github.com/mgsloan/quasi-extras 。これまでのところ、通常よりもいくつかの場所でスプライシングを許可し、ASTでパターンマッチングを行うことができます。

  • ステージ制限は地獄です。同じモジュールで定義された関数をスプライスできないことは、その小さな部分です。他の結果として、トップレベルのスプライスがある場合、モジュール内のそれ以降のすべてが、その前のすべてのスコープから外れます。このプロパティを持つ他の言語(C、C ++)は、前方宣言を許可することで機能しますが、Haskellはそうではありません。スプライスされた宣言間、またはそれらの依存関係と依存関係の間で循環参照が必要な場合は、通常、単に失敗します。

私は以前に循環TH定義が不可能であるという問題に遭遇しました...それはかなり迷惑です。解決策はありますが、それは醜いです-循環依存関係に関係するものを、生成されたすべての宣言を組み合わせたTH式でラップします。これらの宣言ジェネレーターの1つは、Haskellコードを受け入れる準クォーターである可能性があります。

  • それは無原則です。これが意味するのは、抽象化を表現する場合、ほとんどの場合、その抽象化の背後にはある種の原則または概念があるということです。多くの抽象化では、それらの背後にある原則をそれらのタイプで表現できます。型クラスを定義するとき、多くの場合、インスタンスが従う必要があり、クライアントが想定できる法則を定式化できます。GHCの[新しいジェネリック機能][3]を使用して、任意のデータ型(範囲内)でインスタンス宣言の形式を抽象化すると、「合計型の場合はこのように機能し、製品型の場合は次のように機能します。 "。しかし、TemplateHaskellは単なるマクロです。これは、アイデアのレベルでの抽象化ではなく、ASTのレベルでの抽象化です。これは、プレーンテキストのレベルでの抽象化よりも優れていますが、控えめです。

あなたがそれで無原則なことをするならば、それは無原則です。唯一の違いは、コンパイラが抽象化のために実装したメカニズムを使用すると、抽象化がリークされないという確信が持てるようになることです。おそらく、言語設計を民主化することは少し怖いように聞こえます!THライブラリの作成者は、提供するツールの意味と結果を適切に文書化し、明確に定義する必要があります。原則的なTHの良い例は、派生パッケージです。http: //hackage.haskell.org/package/derive-DSLを使用して、多くの派生の例が実際の派生を/指定/します。

  • それはあなたをGHCに結びつけます。理論的には別のコンパイラがそれを実装できますが、実際にはこれが起こるとは思えません。(これは、現時点ではGHCによってのみ実装されている可能性がありますが、将来的に他のコンパイラーに採用され、最終的に標準化されるさまざまな型システム拡張機能とは対照的です。)

これはかなり良い点です。THAPIはかなり大きくて不格好です。再実装するのは難しいようです。ただし、HaskellASTを表現する問題を切り分ける方法は実際にはほんのわずかしかありません。TH ADTをコピーし、内部AST表現へのコンバーターを作成することで、そこまでかなりの道のりが得られると思います。これは、haskell-src-metaを作成する(重要ではない)努力と同等です。TH ASTをきれいに印刷し、コンパイラの内部パーサーを使用することで、簡単に再実装することもできます。

私は間違っているかもしれませんが、実装の観点からは、THがコンパイラ拡張のように複雑であるとは考えていません。これは、実際には「シンプルに保つ」ことの利点の1つであり、基本的なレイヤーが理論的に魅力的で静的に検証可能なテンプレートシステムである必要がありません。

  • APIは安定していません。新しい言語機能がGHCに追加され、それらをサポートするようにtemplate-haskellパッケージが更新されると、多くの場合、THデータ型に対する後方互換性のない変更が含まれます。THコードを複数のバージョンのGHCと互換性があるようにする場合は、細心の注意を払い、場合によってはを使用する必要がありますCPP

これも良い点ですが、やや劇的です。最近APIが追加されましたが、それらは広範囲に破損を誘発していません。また、前述の優れたAST引用符を使用すると、実際に使用する必要のあるAPIを大幅に削減できると思います。構築/マッチングに個別の関数が必要でなく、代わりにリテラルとして表現されている場合、ほとんどのAPIは表示されなくなります。さらに、あなたが書いたコードは、Haskellに似た言語のAST表現に簡単に移植できます。


要約すると、THは強力で半ば無視されているツールだと思います。憎しみが減ると、図書館のより活発なエコシステムにつながり、より多くの言語機能のプロトタイプの実装が促進される可能性があります。THは強力なツールであり、ほとんど何でも/実行/できることが確認されています。アナーキー!そうですね、この力によって、その制限のほとんどを克服し、非常に原理的なメタプログラミングアプローチが可能なシステムを構築できると思います。「適切な」実装の設計が徐々に明らかになるため、「適切な」実装をシミュレートするために醜いハックを使用する価値があります。

私の個人的な理想的なバージョンのnirvanaでは、言語の多くは実際にはコンパイラーからこれらの種類のライブラリーに移動します。機能がライブラリとして実装されているという事実は、忠実に抽象化する能力に大きな影響を与えません。

ボイラープレートコードに対する典型的なHaskellの答えは何ですか?抽象化。私たちのお気に入りの抽象化は何ですか?関数と型クラス!

型クラスを使用すると、一連のメソッドを定義できます。これらのメソッドは、そのクラスで一般的なあらゆる種類の関数で使用できます。ただし、これ以外に、クラスが定型文を回避するのに役立つ唯一の方法は、「デフォルト定義」を提供することです。ここに、非原理的な機能の例があります!

  • 最小限のバインディングセットは宣言可能/コンパイラチェック可能ではありません。これは、相互再帰のためにボトムをもたらす不注意な定義につながる可能性があります。

  • これにより得られる優れた利便性とパワーにもかかわらず、孤立したインスタンスがあるため、スーパークラスのデフォルトを指定することはできません http://lukepalmer.wordpress.com/2009/01/25/a-world-without-orphans/ これらにより、数値階層を優雅に!

  • メソッドのデフォルトに対するTHのような機能を追求すると、http: //www.haskell.org/haskellwiki/GHC.Genericsにつながりました。これはすばらしいことですが、これらのジェネリックを使用してコードをデバッグした唯一の経験は、誘導された型のサイズとASTと同じくらい複雑なADTのため、ほとんど不可能でした。https://github.com/mgsloan/th-extra/commit/d7784d95d396eb3abdb409a24360beb03731c88c

    言い換えれば、これはTHによって提供される機能の後に続きましたが、言語のドメイン全体、つまり構築言語を型システム表現に持ち上げる必要がありました。私はそれがあなたの一般的な問題に対してうまく機能しているのを見ることができますが、複雑な問題については、THハッカーよりもはるかに恐ろしいシンボルの山を生み出す傾向があるようです。

    THは、出力コードの値レベルのコンパイル時計算を提供しますが、ジェネリックスは、コードのパターンマッチング/再帰部分を型システムに持ち上げることを強制します。これはいくつかのかなり便利な方法でユーザーを制限しますが、複雑さはそれだけの価値があるとは思いません。

THとlispのようなメタプログラミングの拒否は、インスタンスの宣言のようなより柔軟なマクロ拡張ではなく、method-defaultsのようなものへの選好につながったと思います。予期しない結果につながる可能性のあるものを回避するという規律は賢明ですが、Haskellの対応型システムが他の多くの環境よりも信頼性の高いメタプログラミングを可能にすることを無視してはなりません(生成されたコードをチェックすることによって)。

于 2012-06-06T11:55:41.937 に答える
8

Template Haskellのかなり実用的な問題の1つは、GHCのバイトコードインタープリターが利用可能な場合にのみ機能することです。これは、すべてのアーキテクチャに当てはまるわけではありません。したがって、プログラムがTemplate Haskellを使用している場合、またはそれを使用するライブラリに依存している場合、ARM、MIPS、S390、またはPowerPCCPUを搭載したマシンでは実行されません。

これは実際には関係があります。git-annexはHaskellで書かれたツールであり、ストレージを心配しているマシンで実行するのが理にかなっています。そのようなマシンには多くの場合、非i386-CPUが搭載されています。個人的には、NSLU 2(32 MBのRAM、266 MHzのCPU、Haskellがそのようなハードウェアで正常に動作することをご存知ですか?)でgit-annexを実行します。テンプレートHaskellを使用する場合、これは不可能です。

(ARMでのGHCの状況は最近大幅に改善されており、7.4.2でも機能すると思いますが、要点はまだ残っています)。

于 2012-06-07T10:14:42.390 に答える
6

THはなぜ悪いのですか?私にとって、それはこれに帰着します:

THを使用して自動生成しようとしているほど多くの反復コードを生成する必要がある場合は、間違っています。

考えてみてください。Haskellの魅力の半分は、その高レベルの設計により、他の言語で書かなければならない大量の無駄な定型コードを回避できることです。コンパイル時のコード生成が必要な場合は、基本的に、言語またはアプリケーションの設計のいずれかが失敗したと言っています。そして、私たちプログラマーは失敗するのが好きではありません。

もちろん、必要な場合もあります。ただし、デザインをもう少し賢くするだけで、THの必要性を回避できる場合があります。

(もう1つは、THが非常に低レベルであるということです。壮大な高レベルの設計はありません。GHCの内部実装の詳細の多くが公開されています。そのため、APIが変更される傾向があります...)

于 2014-01-05T20:46:07.627 に答える