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 标记都这样做。在 Python 中,我会遍历 HTML 标记名称列表,将每个相应tag_with_attrs的结果从globals(). 简而言之,我会动态地将新条目插入符号表中。

这个习语的 Haskell 等价词是什么?

顺便说一句,我完全意识到我正在复制许多已经完成 HTML 标记的现有库的工作。我正在为一个玩具项目做这个,仅此而已:)

编辑:一些已发布的解决方案建议仍然依赖于逐一定义最终结果标签功能的机制。这违反了 DRY,否则我会按照我的方式去做。这是我试图回避的 DRY 违规行为。

4

5 回答 5

8

Haskell 是静态类型的,这意味着必须在编译时对所有符号进行类型检查。这意味着您不能在运行时将条目添加到符号表中。

你想要的是元编程。代码在编译时运行以生成其他代码(你自然而然地觉得懒惰打字)。这意味着类似于宏系统的东西。

Haskell 没有宏,但是 Haskell 有模板: http ://www.haskell.org/haskellwiki/Template_Haskell

与宏一样,其想法是编写一个生成 AST 的函数。元函数采用您要使用的函数的名称(在您的情况下为divulli)并生成具有该名称的函数的 AST。

有点矫枉过正,但如果你真的想这样做,这是一个相对简单的教程: http: //playingwithpointers.com/archives/615

于 2012-11-19T11:55:57.343 回答
6

好吧,你知道 Haskell 是 curried 并且函数是一流的,所以你真的不需要任何魔法来做到这一点。只需认识到您可以执行以下操作:

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而不是顶级地图。另一种选择是使用并发变量,如 anMVar或(可能更好) a TVar

于 2012-11-19T12:17:50.120 回答
6

这可以通过一些Template 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 回答
4

我想我会做的是为标签定义一个数据类型:

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

这是您对所有标签的单点定义。

然后定义一个将Tag值映射到适当函数的函数:

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

然后在你想调用h1, h2,的地方h3你改为调用tag H1, tag H2, tag H3, 等等。

请注意,这与定义函数、、等的详细程度相同;实际上,您只是获得了稍长的名称(恰好包含一个空格)。对我来说,这是 DRY 和“说出你的意思”的理想组合。无论如何,对我来说似乎不是一个功能;实际上,我更愿意认为我正在使用一个函数处理多个数据项,而不是我拥有一组庞大的函数。tag_h1tag_h2tag_h3h1

如果我对这个速度不满意(因为编译器可能不会优化掉所有的tagWithAttrs调用)并且我已经确定这是加速我的应用程序的“最容易实现的目标”,我会考虑记忆tagWithAttrstag,但在内部以保持相同的界面。一种快速的可能性:使用所有标签预先填充地图;您可以使用EnumandBounded实例来执行此操作,而无需明确重新列出所有标签(这是您无法使用函数或字符串表示的标签所做的事情)。非严格评估的一个附带好处是,这可能会导致tagWithAttrs对实际使用的每个标签只进行一次评估。

这仍然会在每次tag调用时留下数据结构查找(除非编译器足够聪明以优化它们,这并非不可能)。我怀疑这将是最重要的性能瓶颈,除非您对程序的其余部分进行了一些重大优化。要在编译时进行所有查找(不依赖优化器),我认为您确实需要 Template Haskell。在这种情况下,我可能不会走那么远,只是因为我真诚地怀疑我是否需要它更快(而且我的可用计算时间比我的时间多)。但即使我使用 Template Haskell 在编译时完成查找,我也不希望使其看起来像每个标签的单独顶级函数;我只是发现“标签和知道如何呈现标签的函数”比“标签,可以调用来呈现自己”是一种更自然和灵活的方法。

于 2012-11-21T01:52:32.993 回答
0

编写一个简单的代码生成器,输入您想要的标签列表,将输出包含为一个模块。

于 2012-11-19T15:25:54.847 回答