【问题标题】:Generate a function using Template Haskell使用 Template Haskell 生成函数
【发布时间】:2015-06-09 05:50:58
【问题描述】:

是否可以使用 Template Haskell 定义函数?例如

convertStringToValue :: String -> Int
convertStringToValue "three" = 3
convertStringToValue "four" = 4

我还有一个Map [Char] Int

fromList [("five",5),("six",6)]

如何添加功能

convertStringToValue "six" = 6
convertStringToValue "five" = 5

在编译时使用 Template Haskell 和 Map?为此目的使用 Template Haskell 似乎很愚蠢,但我还是想知道。

【问题讨论】:

  • 我可以理解想要一个学习 TH 的例子,但这会更容易做到 lookup' key = fromJust . Map.lookup key
  • @bheklilr 我很清楚这一点,但出于好奇,我只是想知道是否可以使用 TH 来完成。
  • 另一种不是 TH 的可能性是编写一个普通的 Haskell 函数,将 Haskell 源代码保存到另一个文件,然后正常编译该文件。只是把它扔在那里......
  • @MathematicalOrchid 这也是在不使用 TH 的情况下解决问题的一种选择

标签: haskell template-haskell


【解决方案1】:

您可以使用两个文件来做到这一点:

“制造商”文件:Maker.hs

module Maker where

{-# LANGUAGE TemplateHaskell #-}

import Language.Haskell.TH

maker items = do
    x <- newName "x"
    lamE [varP x] (caseE (varE x) (map (\(a,b) -> match (litP $ stringL a) (normalB $ litE $ integerL b) []) items))

和主文件:Main.hs

{-# LANGUAGE TemplateHaskell #-}

import Language.Haskell.TH
import Maker

function = $(maker [("five",5),("six",6)])

在这种情况下,function 将是 [Char] -&gt; Int 类型,并将编译为:

\x -> case x of
    "five" -> 5
    "six" -> 6

就好像你会这样写:

function = \x -> case x of
    "five" -> 5
    "six" -> 6

你自己。显然,这不会为两三个案例带来回报,但正如您自己在问题中所写的那样,当您想要使用数千个案例或列表理解生成的项目列表时,这开始得到回报。

自己制作模板 Haskell

本节旨在简要描述如何自己编写模板 Haskell。本教程不是“对...的完整介绍”:还有其他技术可以做到这一点。

为了编写Haskell模板,你可以先尝试几个表达式,然后尝试使用mapfold概括它们。

分析 AST 树

首先你最好看看 Haskell 是如何解析某个表达式本身的。您可以使用runQ 和括号[| ... |] 来执行此操作,... 是您要分析的表达式。比如:

$ ghci -XTemplateHaskell
GHCi, version 7.6.3: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> :m Language.Haskell.TH
Prelude Language.Haskell.TH> runQ [| \x -> case x of "five" -> 5; "six" -> 6 |]
Loading package array-0.4.0.1 ... linking ... done.
Loading package deepseq-1.3.0.1 ... linking ... done.
Loading package containers-0.5.0.0 ... linking ... done.
Loading package pretty-1.1.1.0 ... linking ... done.
Loading package template-haskell ... linking ... done.
LamE [VarP x_0] (CaseE (VarE x_0) [Match (LitP (StringL "five")) (NormalB (LitE (IntegerL 5))) [],Match (LitP (StringL "six")) (NormalB (LitE (IntegerL 6))) []])

因此,AST 是:

LamE [VarP x_0] (CaseE (VarE x_0) [Match (LitP (StringL "five")) (NormalB (LitE (IntegerL 5))) [],Match (LitP (StringL "six")) (NormalB (LitE (IntegerL 6))) []])

所以现在我们从该表达式派生 抽象语法树(AST)。一个提示是使表达式足够通用。例如,在 case 块中使用多个案例,因为使用单个案例并不能告诉您应该如何在表达式中添加第二个案例。现在我们希望自己创建这样的抽象语法树。

创建变量名

第一个方面是变量,例如VarP x_0VarE x_0。您不能简单地复制粘贴它们。这里x_0 是一个名字。为了确保您不使用已经存在的名称,您可以使用newName。现在您可以构造以下表达式来完全复制它:

maker = do
    x <- newName "x"
    return $ LamE [VarP x] (CaseE (VarE x) [Match (LitP (StringL "five")) (NormalB (LitE (IntegerL 5))) [],Match (LitP (StringL "six")) (NormalB (LitE (IntegerL 6))) []])

泛化函数

显然我们对构建一个固定的抽象语法树不感兴趣,否则我们可以自己编写它。现在的重点是你引入一个或多个变量,并对这些变量进行推理。对于每个元组("five",5) 等,我们引入一个Match 语句:

Match (LitP (StringL "five")) (NormalB (LitE (IntegerL 5))) []

现在我们可以使用\(a,b) 轻松概括这一点:

\(a,b) -> Match (LitP (StringL a)) (NormalB (LitE (IntegerL b))) []

然后使用map 遍历所有项目:

map (\(a,b) -> Match (LitP (StringL a)) (NormalB (LitE (IntegerL b))) []) items

items 是我们希望为其生成案例的元组列表。现在我们完成了:

maker items = do
    x <- newName "x"
    return $ LamE [VarP x] (CaseE (VarE x) (map (\(a,b) -> Match (LitP (StringL a)) (NormalB (LitE (IntegerL b))) []) items))

现在您可以简单地省略 return,因为库中所有这些项目都有小写变体。您还可以尝试稍微“cleanup”代码(例如 (NormalB (LitE (IntegerL b)))(NormalB $ LitE $ IntegerL b) 等);例如使用hlint

maker items = do
    x <- newName "x"
    lamE [varP x] (caseE (varE x) (map (\(a,b) -> match (litP $ stringL a) (normalB $ litE $ integerL b) []) items))

这里的 maker 是某种创建/构造函数的函数。

小心无限列表

请注意,编译器将评估美元括号 $() 之间的内容。例如,如果您将使用无限列表:

function = $(maker [(show i,i)|i<-[1..]]) -- Don't do this!

这将继续为抽象语法树分配内存并最终耗尽内存。编译器在运行时扩展 AST。

【讨论】:

    【解决方案2】:

    是的

    import Language.Haskell.TH
    
    generateDict :: String -> [(String, Int)] -> Q [Dec]
    generateDict fname sns = do
        let clauses = map clause sns
        return $ [FunD (mkName fname) clauses]
            where clause (s,n) =
                    Clause [LitP . IntegerL $ toInteger  n]
                           (NormalB . LitE $ StringL s )
                           []
    

    然后

    generateDict "myDict" $ zip (words "One Two Tree Four") [1..]
    
    myDict 1 -- => "One"
    

    【讨论】:

      猜你喜欢
      • 2012-12-08
      • 1970-01-01
      • 2013-05-13
      • 1970-01-01
      • 2016-02-16
      • 1970-01-01
      • 1970-01-01
      • 2019-12-23
      • 2012-01-12
      相关资源
      最近更新 更多