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

Lou*_*man 7 haskell functional-programming function 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>'.
Run Code Online (Sandbox Code Playgroud)

到现在为止还挺好.但是我分配的行h1是其中之一,因为我必须为我定义的每个HTML标记执行此操作.在Python中,我循环遍历HTML标记名称列表,将每个相应的结果插入tag_with_attrs到返回的字典中globals().简而言之,我将动态地在符号表中插入新条目.

这个成语的Haskell等价是什么?

顺便说一句,我完全清楚我正在复制已经有HTML标签的许多现有库的工作.我正在做一个玩具项目,仅此而已:)

编辑:一些发布的解决方案建议仍然依赖于逐个定义最终结果标记函数的机制.这违反了DRY,否则我就会如何做到这一点.这是DRY违规,我正试图支持.

Fai*_*aiz 8

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

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

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

与宏一样,我们的想法是编写一个生成AST的函数.元功能把你要使用的函数的名称(在你的情况,div,ul,li等),并产生一个功能性的AST使用该名称.

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


ham*_*mar 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"]
Run Code Online (Sandbox Code Playgroud)

这产生了声明h1 = tagWithAttrs "h1",h2 = tagWithAttrs "h2",h3 = tagWithAttrs "h3",等等.要添加更多内容,只需将它们添加到列表中即可.

代码有点难看,因为它不可能在TH中拼接模式.否则,我们本来可以写出类似的东西[d| $(mkName tag) = tagWithAttrs $(stringE tag) |].相反,我们必须使用TH组合器手动构造声明.


小智 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
Run Code Online (Sandbox Code Playgroud)

这都是常规高效的Haskell.注意:您可能想通过使用子句将其定义tagFuncs为本地值.虽然这可以使您的代码更漂亮,但它也会导致为每次调用重新生成映射.tagWithAttrswheretagWithAttrs

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