Question

I am writing a simple HashString class, which is just a string and its hash:

data HashString = HashString Int    -- ^ hash
                             T.Text -- ^ string!

Now I'm trying to generate these at compile time with something like:

$(hString "hello, world") :: HashString

I want the hash, and the text packing to happen at compile time. How do I do this?

Here's what I've tried so far, but I'm not sure if its right, nor am I sure it does everything at compile time:

hString :: String -> Q Exp
hString s = [| HashString (hash $ T.pack s) (T.pack s) |]
Was it helpful?

Solution

The way you've written your code, no evaluation will happen at compile-time. When you quote a Haskell expression with [| ... |], the quoted code/AST is inserted where you apply it without any evaluation, so writing:

$(hString "hello, world")

is exactly the same as writing:

let s = "hello, world" in HashString (hash $ T.pack s) (T.pack s)

But think about it like this: you use [| ... |] to quote an expression to be inserted later, and you generate code at compile-time with $(...). So, if you include some code $(foo) in a quoted expression bla = [| bar $(foo) |], doing $(bla) will generate the code bar $(foo), which in turn will evaluate foo at compile time. Also, to take a value that you generate at compile time and generate an expression from it, you use the lift function. So, what you want to do is this:

import Data.String (fromString)
import Language.Haskell.TH.Syntax

hString s = [| HashString $(lift . hash . T.pack $ s) (fromString s) |]

This evaluates the hash function at compile time, since the inner splice is resolved after the outer splice was resolved. By the way, using fromString from Data.String is the generic way of constructing some OverloadedString data type from a String.

Also, you should consider making a quasi-quoter for your HashString interface. Using quasi-quoters is more natural than manually calling splice functions (And you've already used them; the nameless [| ... |] quoter quotes Haskell expressions).

You would create a quasiquoter like this:

import Language.Haskell.TH.Quote

hstr =
  QuasiQuoter
  { quoteExp = hString -- Convenient: You already have this function
  , quotePat = undefined
  , quoteType = undefined
  , quoteDec = undefined
  }

This would let you write HashStrings with this syntax:

{-# LANGUAGE QuasiQuotes #-}
myHashString = [hstr|hello, world|]
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top