使用Template Haskell生成函数

use*_*324 10 haskell template-haskell

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

convertStringToValue :: String -> Int
convertStringToValue "three" = 3
convertStringToValue "four" = 4
Run Code Online (Sandbox Code Playgroud)

我也有Map [Char] Int.

fromList [("five",5),("six",6)]
Run Code Online (Sandbox Code Playgroud)

我该如何添加功能

convertStringToValue "six" = 6
convertStringToValue "five" = 5
Run Code Online (Sandbox Code Playgroud)

在编译时使用Template Haskell和Map?使用Template Haskell来达到这个目的似乎很愚蠢,但我还是想知道.

Wil*_*sem 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))
Run Code Online (Sandbox Code Playgroud)

和主文件Main.hs::

{-# LANGUAGE TemplateHaskell #-}

import Language.Haskell.TH
import Maker

function = $(maker [("five",5),("six",6)])
Run Code Online (Sandbox Code Playgroud)

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

\x -> case x of
    "five" -> 5
    "six" -> 6
Run Code Online (Sandbox Code Playgroud)

因此,好像你会写:

function = \x -> case x of
    "five" -> 5
    "six" -> 6
Run Code Online (Sandbox Code Playgroud)

你自己.显然,这不会为两三个案例带来回报,但正如您自己写的那样,当您想要使用数千个案例或列表理解产生的项目列表时,这开始有了回报.

自己制作模板Haskell

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

为了写模板哈斯克尔,你可以先尝试一些表情,然后尝试推广使用它们map,fold等等.

分析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))) []])
Run Code Online (Sandbox Code Playgroud)

因此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))) []])
Run Code Online (Sandbox Code Playgroud)

所以现在我们从该表达式派生抽象语法树(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))) []])
Run Code Online (Sandbox Code Playgroud)

概括功能

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

Match (LitP (StringL "five")) (NormalB (LitE (IntegerL 5))) []
Run Code Online (Sandbox Code Playgroud)

现在我们可以通过以下方式轻松概括\(a,b):

\(a,b) -> Match (LitP (StringL a)) (NormalB (LitE (IntegerL b))) []
Run Code Online (Sandbox Code Playgroud)

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

map (\(a,b) -> Match (LitP (StringL a)) (NormalB (LitE (IntegerL b))) []) items
Run Code Online (Sandbox Code Playgroud)

使用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))
Run Code Online (Sandbox Code Playgroud)

现在您可以简单地省略,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))
Run Code Online (Sandbox Code Playgroud)

这里的制造商是某种制作/构建功能的功能.

小心无限的名单

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

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

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