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.