Skip to content

Instantly share code, notes, and snippets.

@chrisdone
Last active May 8, 2022 12:29
Show Gist options
  • Save chrisdone/bda0708a3b605d6c0008975e2a6e2803 to your computer and use it in GitHub Desktop.
Save chrisdone/bda0708a3b605d6c0008975e2a6e2803 to your computer and use it in GitHub Desktop.
Template Haskell quickstart

Template Haskell Quick Start

Use the template haskell (abbreviated to "TH") extension in a module with:

{-# LANGUAGE TemplateHaskell #-}

There are a few key pieces of syntax:

  • $(code here) - this is called a splice. It runs the code here Haskell code that produces an AST, either a declaration or expression, depending on context.
  • [|code here|] - this is a quote. It quotes the code here Haskell code and produces an AST of it. You can use this to generate code automatically, or to pass code to a splice, etc. It produces a Q Exp.
  • 'myfunc - you can quote an individual variable name with this. It produces a Name.
  • ''mytype - you can quote a type name with this. It produces a Name.
  • [name|arbitrary text|] - this is called a quasi quote. It runs the code defined in name against the arbitrary text string, producing an AST.

The template-haskell package provides the AST:

Splicing and quoting

Here's an example that just creates a string literal.

myString :: String
myString = $(stringE "hello!")

where

stringE :: String -> Q Exp

Here's a more complicated example:

> let foo = 1 : $(varE 'foo)
> take 10 foo
[1,1,1,1,1,1,1,1,1,1]

where

varE :: Name -> Q Exp

It quotes the name foo and then splices that back in as a variable.

Here's a generalized variant of fst, snd that works on any tuple length:

> let nth n m = lamE [tupP (map (\i -> if i == n then varP (mkName "x") else wildP) [1..m])] (varE (mkName "x"))
> $(nth 3 5) (1,2,3,4,5)
3

With -ddump-splices:

> :set -ddump-splices
> $(nth 3 5) (1,2,3,4,5)
<interactive>:72:3-9: Splicing expression
    nth 3 5 ======> \ (_, _, x, _, _) -> x
3

You can refer to variables trivially with 'foo:

> $(varE 'map) (*2) [1..5]
<interactive>:79:3-11: Splicing expression varE 'map ======> map
[2,4,6,8,10]

If you need to make up a name, use mkName.

> $(varE (mkName "map")) (*2) [1..5]
<interactive>:79:3-11: Splicing expression varE 'map ======> map
[2,4,6,8,10]

Running IO

Use the runIO :: IO a -> Q a function to run arbitrary I/O in a splice.

If you read from a file, you probably want to qAddDependentFile to make sure that the Haskell module will be recompiled when that file changes.

Querying info

Use reify to get info about variables and types.

Handy links

@vdkhvb
Copy link

vdkhvb commented May 8, 2022

Link to 'Defining a quasi quoter' is dead, click here for an archived version.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment