Search code examples
haskelltemplate-haskell

Embedding long strings inline without using lists


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.


Solution

  • 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();
    }
    |]