是否可以使用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来达到这个目的似乎很愚蠢,但我还是想知道。
答案 0 :(得分:6)
您可以使用两个文件执行此操作:
“制造商”文件: 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] -> Int
类型,并将编译为:
\x -> case x of
"five" -> 5
"six" -> 6
因此你好像写了:
function = \x -> case x of
"five" -> 5
"six" -> 6
自己。显然,这不会为两三个案件带来回报,但正如您自己写的那样,当您想要使用数千个案例或列表理解产生的项目列表时,这开始得到回报。
本节旨在简要介绍如何自己编写模板Haskell。本教程不是“对...的完整介绍”:还有其他技术可以做到这一点。
为了编写模板Haskell,您可以先尝试一些表达式,然后尝试使用map
,fold
等概括它们。
首先,您最好先了解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_0
和VarE 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
,因为该库具有所有这些项的小写变体。您可以进一步尝试“清理”代码(例如(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。
答案 1 :(得分: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"