7

Haskellで関数を動的に定義する方法、またはHaskellの慣用的な同等物を探しています。

シナリオは次のとおりです。提供された引数tagWithAttrsに基づいて新しい関数を生成する関数があります。String定義は次のようになります。

tagWithAttrs :: String -> ([(String, String)] -> [String] -> String)
tagWithAttrs tagName = [...]  -- Implementation ommited to save room.

h1 :: [(String, String)] -> [String] -> String
h1 = tagWithAttrs "h1"

main :: IO ()
main = putStrLn $ h1 [("id", "abc"), ("class", "def")] ["A H1 Test"]

-- Should display '<h1 id="abc" class="def">A H1 Test</h1>'.

ここまでは順調ですね。しかし、私が割り当てる行は、h1私が定義しているすべてのHTMLタグに対してそれを行わなければならないので、多くの1つです。tag_with_attrsPythonでは、HTMLタグ名のリストをループして、から返された辞書にそれぞれの結果を挿入しglobals()ます。つまり、シンボルテーブルに動的に新しいエントリを挿入します。

このイディオムのハスケル同等物は何ですか?

ところで、私はすでにHTMLタグを実行している多くの既存のライブラリの作業を複製していることを十分に認識しています。私はおもちゃのプロジェクトのためにこれをやっています、それ以上は何もありません:)

編集:いくつかの投稿されたソリューションは、最終結果タグ関数を1つずつ定義することに依然依存しているメカニズムを提案しています。これはDRYに違反します。そうでなければ、私はそれを自分のやり方でやったでしょう。私が回避しようとしているのは、そのDRY違反です。

4

5 に答える 5

8

Haskellは静的に型付けされています。つまり、コンパイル時にすべてのシンボルの型をチェックする必要があります。つまり、実行時にシンボルテーブルにエントリを追加することはできません。

必要なのはメタプログラミングです。コードがコンパイル時に実行されて他のコードが生成される場合(自然にそして正しく入力するのが面倒だと感じる)。これは、マクロシステムのようなものを意味します。

Haskellにはマクロはありませんが、テンプレートHaskellがあります:http: //www.haskell.org/haskellwiki/Template_Haskell

マクロと同様に、ASTを生成する関数を作成するという考え方です。メタ関数は、使用する関数の名前(この場合は、、など)を取り、その名前の関数のdivASTを生成します。ulli

少しやり過ぎですが、本当にやりたいのであれば、これは比較的簡単なチュートリアルです:http: //playingwithpointers.com/archives/615

于 2012-11-19T11:55:57.343 に答える
6

これはいくつかのテンプレートHaskellで簡単に行うことができます:

{-# LANGUAGE TemplateHaskell #-}

import Control.Monad (forM)
import Language.Haskell.TH

tagWithAttrs :: String -> ([(String, String)] -> [String] -> String)
tagWithAttrs tagName = undefined

$(forM ["h1", "h2", "h3"] $ \tag ->
   valD (varP (mkName tag)) (normalB [| tagWithAttrs $(stringE tag) |]) [])

main :: IO ()
main = putStrLn $ h1 [("id", "abc"), ("class", "def")] ["A H1 Test"]

これにより、宣言、、、などが生成さh1 = tagWithAttrs "h1"れます。さらに追加するには、それらをリストに追加するだけです。h2 = tagWithAttrs "h2"h3 = tagWithAttrs "h3"

THでパターンをスプライスすることはできないため、コードは少し醜いです。そうでなければ、のようなものを書くことができたでしょう[d| $(mkName tag) = tagWithAttrs $(stringE tag) |]。代わりに、THコンビネータを使用して宣言を手動で作成する必要があります。

于 2012-11-19T12:10:45.590 に答える
6

ご存知のように、Haskellはカレーであり、関数はファーストクラスなので、それを行うのに魔法は必要ありません。次のようなことができることを認識してください。

import qualified Data.Map as M
import Data.Map (Map)
import Data.Text (Text)

type TagName = Text
type TagWithAttrs = Map TagName ([(String, String)] -> [String] -> String)

tagFuncs :: TagWithAttrs
tagFuncs =
    M.fromList $
    ("h1", \xs ys -> zs) :
    ("h2", \xs ys -> zs) :
    {- ... -}
    []

tagWithAttrs :: TagName -> [(String, String)] -> [String] -> String
tagWithAttrs = flip M.lookup tagFuncs

これはすべて通常の効率的なHaskellです。注:句を使用してtagFuncs、ローカル値として定義したくなる場合があります。これによりコードがより美しくなりますが、。を呼び出すたびにマップが再生成されます。tagWithAttrswheretagWithAttrs

マップに動的に物事を挿入するにtagWithAttrsは、トップレベルのマップの代わりにマップを引数にすることができます。もう1つの方法は、MVarまたは(おそらくより良い)のような並行変数を使用することTVarです。

于 2012-11-19T12:17:50.120 に答える
4

私がすることは、タグのデータ型を定義することだと思います。

data Tag = H1 | H2 | H3 ...
    deriving (Show, Eq, Ord, Enum, Bounded)

これは、存在するすべてのタグの単一の定義ポイントです。

Tag次に、値を適切な関数にマップする関数を定義します。

tag :: Tag -> [(String, String)] -> [String] -> String
tag = tagWithAttrs . show

そして、あなたが呼びたいところ、、、、h1あなたは代わりに、、、などh2を呼びます。h3tag H1tag H2tag H3

これは、関数、、、などを定義した場合と冗長性が同じであることに注意してください。事実上、名前が少し長くなっています(たまたまスペースが含まれています)。私にとって、これはDRYと「あなたが何を意味するかを言う」の理想的な組み合わせです。とにかく私には関数のようには見えません。実際には、巨大な関数のセットを持っているというよりも、1つの関数を使って多数のデータ項目を操作していると思います。tag_h1tag_h2tag_h3h1

その後、この速度に不満があり(コンパイラがすべてのtagWithAttrs呼び出しを最適化しない可能性があるため)これがアプリケーションを高速化するための「最も低い成果」であると判断した場合は、メモ化を検討します。tagWithAttrsまたはtag、ただし、同じインターフェイスを維持するために内部的に。1つの簡単な可能性:すべてのタグをマップに事前入力します。Enumandインスタンスを使用して、Boundedすべてのタグを明示的に再リストせずにこれを行うことができます(これは、関数または文字列で表されるタグでは実行できなかったことです)。非厳密な評価の副次的な利点は、tagWithAttrs実際に使用されるタグごとに1回だけ評価される可能性があることです。

それでも、呼び出しごとにデータ構造のルックアップが残りtagます(コンパイラーがそれらを最適化するのに十分賢い場合を除いて、不可能ではありません)。プログラムの残りの部分を大幅に最適化しない限り、これが最も重大なパフォーマンスのボトルネックになるとは思えません。コンパイル時に(オプティマイザーに依存せずに)すべてのルックアップを実行するには、TemplateHaskellが必要だと思います。この場合、私はおそらくそれほど遠くまでは行かないでしょう。なぜなら、もっと速く進む必要があるのではないかと心から疑っているからです(そして、私は自分の時間よりもはるかに多くの利用可能な計算時間を持っています)。しかし、テンプレートHaskellを使用してコンパイル時にルックアップを実行していたとしても、タグごとに個別のトップレベル関数のように見せます。「タグとタグのレンダリング方法を知っている関数」は、「自分自身をレンダリングするために呼び出すことができるタグ」よりも自然で柔軟なアプローチであることがわかりました。

于 2012-11-21T01:52:32.993 に答える
0

簡単なコードジェネレーターを作成し、必要なタグのリストを入力し、出力をモジュールとして含めます。

于 2012-11-19T15:25:54.847 に答える