使用模板Haskell生成一个函数 [英] Generate a function using Template Haskell

查看:106
本文介绍了使用模板Haskell生成一个函数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

  convertStringToValue :: String  - > Int 
convertStringToValuethree= 3
convertStringToValuefour= 4

我还有一个 Map [Char] Int

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

/ p>

  convertStringToValuesix= 6 
convertStringToValuefive= 5

在编译时使用Template Haskell和 Map ?使用Template Haskell看起来很愚蠢,但我想知道。

解决方案

您可以使用以下两个文件来完成此操作:

制造商文件: Maker.hs

 模块Maker其中

{ - #LANGUAGE TemplateHaskell# - }

import Language.Haskell.TH

制造商物品=做
x < - newNamex
lamE [varP x](caseE(varE x)(map(\(a,b) - > match(litP $ stringL a)(normalB $ litE $ integerL b )

和主文件: Main.hs

  { - #LANGUAGE TemplateHaskell# - } 

import Language.Haskell.TH
import Maker

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

在这种情况下, function code> [Char] - > Int 并将编译为:

  \x  - >案例x的
五 - > 5
六 - > 6

因此,您好像已经写下了:

  function = \ x  - >案例x的
五 - > 5
六 - > 6

自己。很明显,这不会为两到三个案例带来回报,但正如你自己写过这个问题,当你想要使用成千上万的案例或者列表理解所产生的一系列项目时,这就开始取得成效。您可以自己编写模板Haskell

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



为了编写模板Haskell ,您可以先尝试几个表达式,然后使用 map , fold 尝试概括 c>等。

分析AST树



首先您最好看看Haskell如何解析某种表达本身。您可以使用 runQ 和括号 [| ...] ... 您希望分析的表达式。例如:

  $ ghci -XTemplateHaskell 
GHCi,版本7.6.3:http://www.haskell .org / ghc /:?寻求帮助
加载包ghc-prim ...链接...完成。
加载包integer-gmp ...链接...完成。
加载程序包库...链接...完成。
前奏> :m Language.Haskell.TH
Prelude Language.Haskell.TH> runQ [| \x - > 五的情况x - > 5; 六 - > 6 |]
加载软件包array-0.4.0.1 ...链接...完成。
加载包deepseq-1.3.0.1 ...链接...完成。
加载包装容器-0.5.0.0 ...链接...完成。
加载包pretty-1.1.1.0 ...链接...完成。
加载包模板-haskell ...链接...完成。
LamE [VarP x_0](CaseE(VarE x_0)[Match(LitP(StringLfive))(NormalB(LitE(IntegerL 5)))[],Match(LitP(StringLsix))( NormalB(LitE(IntegerL 6)))[]])

AST是:

(b)

$ p $ LamE [VarP x_0](CaseE(VarE x_0)[Match(LitP(StringLfive))(NormalB(LitE(IntegerL 5 ))[],[],[],[],[],[
$ b

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

创建变量名称



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

  
return $ LamE [VarP x](CaseE(VarE x)[Match(LitP(StringLfive))(NormalB(LitE(IntegerL 5)))[],Match(LitP(StringLsix)) )(NormalB(LitE(IntegerL 6)))[]])



泛化函数



显然我们不想构造一个固定的抽象语法树,否则我们可能自己编写它。现在的重点是你引入一个或多个变量,以及有关这些变量的原因。对于每个元组(five,5)等,我们引入一个 Match 语句:

$ (LitP(StringLfive))(NormalB(LitE(IntegerL 5)))[]

 现在我们可以用 \(a,b)来简单概括一下: 

p>

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

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

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

项目我们希望生成案例的元组列表。现在我们完成了:

  maker items = do 
x< - newNamex
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

 制造商物品=做
x < - newNamex
lamE [varP x](caseE(varE x)(map(\(a,b) - > match(litP $ stringL a)(normalB $ litE $ integerL b) [])项目))

制造商这里是某种功能



小心无限列表



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

  function = $(maker [(show i,i)| i < -  < [1 ..]]) - 不要这样做! 

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


Is it possible to define a function using Template Haskell? For example

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

I also have a Map [Char] Int.

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

How can I add functions

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

at compile time using Template Haskell and the Map? It appears quite silly to use Template Haskell for this purpose but I would like to know nevertheless.

解决方案

You can do this using two files:

a "maker" file: 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))

and the main file: Main.hs:

{-# LANGUAGE TemplateHaskell #-}

import Language.Haskell.TH
import Maker

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

In that case function will be of the type [Char] -> Int and will be compiled as:

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

It is thus as if you would have written:

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

yourself. Evidently that's not going to pay off for two or three cases, but as you have written in the question yourself, when you want to use thousands of cases, or a list of items generated by list comprehension, this starts to pay off.

Making template Haskell yourself

This section aims to briefly describe how to write template Haskell yourself. This tutorial is not "a complete introduction to...": there are other techniques to do this.

In order to write template Haskell, you can first try a few expressions, and then try to generalize them using map, fold, etc.

Analyze the AST tree

First you better take a look on how Haskell would parse a certain expression itself. You can do this with runQ and brackets [| ... |] with ... the expression you wish to analyze. So for instance:

$ 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))) []])

The AST is thus:

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

So now we have derived the Abstract Syntax Tree (AST) from that expression. A hint is to make the expressions generic enough. For instance use multiple cases in the case block, since using a single case doesn't tell you how you should add a second one to your expression. Now we wish to create such abstract syntax tree ourselves.

Create variable names

A first aspect is the variables, like VarP x_0 and VarE x_0. You cannot simply copy-paste them. Here x_0 is a name. In order to make sure you don't use a name that already exists, you can use newName. Now you can construct the following expression to fully replicate it:

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))) []])

Generalize the function

Evidently we are not interested in constructing a fixed abstract syntax tree, otherwise we could have written it ourselves. Now the point is that you introduce one or more variables, and reason about that variables. For every tuple ("five",5), etc. we introduce a Match statement:

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

Now we can easily generalize this with \(a,b):

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

and then use the map to iterate over all items:

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

with items the list of tuples for which we wish to generate cases. Now we're done:

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))

Now you can simply omit the return because the library has lowercase variants for all these items. You can furthermore try to "cleanup" the code a little bit (like for instance (NormalB (LitE (IntegerL b))) to (NormalB $ LitE $ IntegerL b), etc.); for instance using 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))

The maker here is some kind of function that makes/constructs the function.

Careful for infinite lists

Be aware that the compiler will evaluate what it is in between the dollar brackets $(). If you for instance would use an infinite list:

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

This will keep allocating memory for the abstract syntax tree and eventually run out of memory. The compiler does not expand an AST at run time.

这篇关于使用模板Haskell生成一个函数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆