Creating a function using a Haskell template

Is it possible to define a function using a Haskell template? for example

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

I also have Map [Char] Int .

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

How to add features

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

at compile time using the Haskell and Map pattern? It's pretty silly to use Template Haskell for this purpose, but I would still like to know.

+8
haskell template-haskell
source share
2 answers

You can do this using two files:

creator 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 main file: Main.hs :

 {-# LANGUAGE TemplateHaskell #-} import Language.Haskell.TH import Maker function = $(maker [("five",5),("six",6)]) 

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

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

So you would write:

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

by yourself. Obviously, this will not pay off in two or three cases, but, as you yourself wrote the question, when you want to use thousands of cases or a list of elements created by understanding the list, it starts to pay off.

Create a Haskell template yourself

This section briefly describes how to write a Haskell template yourself. This tutorial is not a "complete introduction to ...": there are other methods for this.

To write a Haskell template, you can first try several expressions, and then try to generalize them with map , fold , etc.

AST Tree Analysis

First, you better take a look at how Haskell will parse a particular expression. You can do this with runQ and brackets [| ... |] [| ... |] with ... expression you want to parse. For example:

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

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

So now we have deduced the abstract syntax tree (AST) from this expression. The hint is to make the expressions fairly generalized. For example, use several cases in a case block, since using one case does not tell you how you should add a second expression to your expression. Now we want to create such an abstract syntax tree.

Create Variable Names

The first aspect is variables, such as VarP x_0 and VarE x_0 . You cannot just copy them. Here x_0 is the name. To make sure that you are not using a name that already exists, you can use newName . Now you can build the following expression for its full replication:

 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 function

Obviously, we are not interested in building a fixed abstract syntax tree, otherwise we could write it ourselves. Now the point is that you enter one or more variables and talk about these variables. For each set ("five",5) , etc. Enter the Match operator:

 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 map to iterate over all elements:

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

with items list of tuples for which we want to generate cases. Now we are 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 return , because the library has lowercase options for all of these elements. You can also try to β€œclear” the code a bit (for example, from (NormalB (LitE (IntegerL b))) to (NormalB $ LitE $ IntegerL b) , etc.); for example 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)) 

A creator is some kind of function that makes / builds a function.

Caution For Endless Lists

Remember that the compiler will evaluate that it is between the $() dollar brackets. If you, for example, would use an infinite list:

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

This saves the memory allocation for the abstract syntax tree and ultimately ends up with a lack of memory. The compiler does not extend the AST at runtime.

+3
source share

Yes

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

And then

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

All Articles