24 days of Hackage, 2015: day 9: Template Haskell goodies: here, interpolate, file-embed
Dec 9, 2015 · 3 minute read · CommentsHaskellHackageTemplate Haskellhereinterpolatefile-embedstrings
Table of contents for the whole series
A table of contents is at the top of the article for day 1.
Day 9
A stray negative remark I made on day 8 regarding Haskell and its unergonomic support for multi-line string literals and interpolation led to good comments that I was being misleading because there actually exist good solutions. I already use one of them, but had not brought them into the picture because I didn’t want to be distracting in that post by bringing in other libraries, especially since they are implemented in Template Haskell, the GHC extension that is “macros for Haskell”, enabling compile-time metaprogramming (see the 2014 Day of Hackage article about Template Haskell. On day 2 I already introduced a package that uses Template Haskell, so it looks like I’ll be continuing to do that today.
These packages require only that you turn on QuasiQuotes
in modules
where you use them, so our example source code will have the header
{-# LANGUAGE QuasiQuotes #-}
Better string literals with here
I’ve been using here
for
“here” strings and interpolation. Check out the
documentation for full details,
but for context, here are some examples using data we already have.
Imports:
import Data.String.Here (hereLit, here, hereFile, i)
import Test.Hspec (Spec, hspec, describe, it, shouldBe)
A trimmed multi-line literal, where leading and trailing white space
are removed, making it particularly easy to just copy and paste blocks
of text in between the here
quasiquoter brackets.
it "makes trimmed multi-line strings prettier" $ do
-- In this case we want trimming.
let original = "words 3\n\
\I 2\n\
\have 2\n\
\so 2\n\
\for 1\n\
\like 1\n\
\many 1\n\
\to 1"
let trimmedHereDoc = [here|
words 3
I 2
have 2
so 2
for 1
like 1
many 1
to 1
|]
trimmedHereDoc `shouldBe` original
For more control (typically what I do for small examples), use hereLit
:
it "makes literal multi-line strings prettier" $ do
-- In this case assume we want the trailing newline.
let original = "words 3\n\
\I 2\n\
\have 2\n\
\so 2\n\
\for 1\n\
\like 1\n\
\many 1\n\
\to 1\n"
let literalHereDoc = [hereLit|words 3
I 2
have 2
so 2
for 1
like 1
many 1
to 1
|]
literalHereDoc `shouldBe` original
But why stop at copy/paste? Much better to embed an actual file with
hereFile
. We use our own little HSpec discovery test/Spec.hs
file
as an example:
it "allows file embed" $ do
[hereFile|test/Spec.hs|] `shouldBe`
"{-# OPTIONS_GHC -F -pgmF hspec-discover #-}"
Finally, some interpolation, using i
. Although it is very
convenient, I have some reservations about overusing this quasiquoter,
because it is engaging in “stringly typed” programming that just makes
use of anything that implements Show
and Typeable
. It is easy to
accidentally write code that compiles but does the wrong thing when
operating at this implicit level. Still, it’s useful:
it "makes interpolation prettier" $ do
let list = [1 :: Int, 2]
let num = 42 :: Int
[i|number: ${num}, stuff: ${map (+1) list}|] `shouldBe`
"number: 42, stuff: [2,3]"
interpolate
interpolate
is
another package that does the same sort of thing. You might like its
unindent
feature, which facilitates the copy/paste mode of embedding blocks of
text into your code.
file-embed
file-embed
is also
useful, because you can use it to embed contents of entire
directories. It also has a unique feature of injection into an
executable.
Conclusion
You don’t have to settle for the built-in syntax for creating strings
out of literals in Haskell. With libraries using Template Haskell, you
can use much prettier representations of strings. Check out here
,
interpolate
, and file-embed
.
All the code
All my code for my article series are at this GitHub repo.