Haskell以编程方式/动态定义函数

时间:2012-11-19 11:33:18

标签: function haskell functional-programming higher-order-functions

我正在寻找一种在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违规,我正试图支持。

5 个答案:

答案 0 :(得分:8)

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

你想要的是元编程。代码在编译时运行以生成其他代码(您自然而且正确地感觉懒得键入)。这意味着像宏观系统。

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

与宏一样,我们的想法是你编写一个生成一个函数的函数 AST。元函数采用您想要使用的函数的名称(在您的 case,divulli等)并生成具有该名称的函数的AST。

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

答案 1 :(得分: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组合器手动构造声明。

答案 2 :(得分: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子句将tagWithAttrs定义为where的本地值。虽然这可以使您的代码更加美观,但也会导致每次调用tagWithAttrs时重新生成地图。

要动态地将内容插入地图,您可以使地图成为tagWithAttrs的参数,而不是顶级地图。另一种方法是使用并发变量,如MVar或(可能更好)TVar

答案 3 :(得分:4)

我认为我要做的是为标签定义数据类型:

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

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

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

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

然后,您要拨打h1h2h3,而是拨打tag H1tag H2tag H3

请注意,如果您定义了函数tag_h1tag_h2tag_h3等,则相同的详细程度;实际上你只是稍微长一点的名字(恰好包括一个空格)。对我来说,这是DRY和“说出你的意思”的理想组合。 h1对我来说似乎不是一个功能;我实际上更倾向于认为我在一些数据项上使用了一个函数而不是我有一组巨大的函数。

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

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

答案 4 :(得分:1)

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