4

For several projects I need to embed long strings into Haskell source code.

The obvious way to do this is to unlines a list of lines. However, reading and maintaining this is cumbersome.

cCode :: String
cCode = unlines [
          "int main(int argc*, char** argv)",
          "  doStuff();",
          "}"]

Is there any way you can embed strings without any overhead (like the list as shown above) or even files? Is TemplateHaskell/Quasi-quotation the way to go here?

Note: This question was answered in Q&A form. Therefore it does not show any research effort.

Uli Köhler
  • 13,012
  • 16
  • 70
  • 120

2 Answers2

5

It is possible using QuasiQuotation as described in this blogpost written by me.

Step 1: Create a module (we'll call it StringEmbed.hs that contains the required functions

module StringEmbed(embedStr, embedStrFile) where

import Language.Haskell.TH
import Language.Haskell.TH.Quote

embedStr :: QuasiQuoter 
embedStr = QuasiQuoter { quoteExp = stringE,
                    quotePat = undefined,
                    quoteDec = undefined,
                    quoteType = undefined }

embedStrFile :: QuasiQuoter
embedStrFile = quoteFile embedStr

Note because of TH peculiarities it is not possible to just copy those functions into the module where you use them.

Step 2a: In your module, embed your strings:

{-# LANGUAGE QuasiQuotes #-}

import StringEmbed

cCode :: String
cCode = [embedStr|
int main(int argc, char** argv) {
    doStuff();
}
|]

Note that you only have to add the QuasiQuotes LANGUAGE pragma. TemplateHaskell is not required for this technique.

Because QuasiQuotes are delimited using |], you can't use that character sequence anywhere in the quasi-quoted string.

Step 2b: You can just as easily embed a file. Let's assume the file code.c contains the string you intend to embed.

{-# LANGUAGE QuasiQuotes #-}

import StringEmbed

cFooter :: String
cFooter = [embedStrFile|code.c|]

Alternatively you can use one of the many haskell libraries instead of StringEmbed.hs, for example heredoc (thanks Ørjan Johansen for the tip!)

{-# LANGUAGE QuasiQuotes #-}
import Text.Heredoc


cCode :: String
cCode = [here|
int main(int argc, char** argv) {
    doStuff();
}
|]
Uli Köhler
  • 13,012
  • 16
  • 70
  • 120
  • 1
    There are several packages on Hackage implementing similar quasiquotes, e.g. [heredoc](http://hackage.haskell.org/package/heredoc) has yours as `here` and `there`, respectively. Some also allow variable interpolation. Like all quasiquotes, they have the limitation that you cannot have the character sequence `|]` internally. – Ørjan Johansen Aug 05 '14 at 22:18
  • @ØrjanJohansen Thanks for the tip! I adde that to my answer. The purpose of doing it manually was to understand how it was done and to avoid dependencies for simple tasks like this, but you're correct when saying that users might want to do it using libraries. – Uli Köhler Aug 05 '14 at 22:36
3

You can use multi-line strings with gaps. It might not be as smooth as the quasiquoters, but it is standard Haskell.

cCode :: String
cCode = "\
      \int main(int argc*, char** argv)\n\
      \  doStuff();\n\
      \}\n\
      \"
augustss
  • 22,884
  • 5
  • 56
  • 93