I am trying to parse some markdown at compile time and hold on to the Html instance it generates.
Normally I would do something like this using a derived Language.Haskell.TH.Lift.Lift
instance:
-- Lib.hs
module Lib where
import Language.Haskell.TH
import Language.Haskell.TH.Lift
data MyNiceType = MyNiceType { f0 :: Int } deriving (Lift, Show)
preloadNiceType :: Q Exp
preloadNiceType = do
-- do some important work at compile time
let x = MyNiceType 0
[| x |]
However, when I try this pattern with a type that contains a Blaze.Html field:
( I am using the extensions TemplateHaskell
DeriveLift
DeriveGeneric
, and the packages template-haskell
th-lift
and blaze-html
)
data MyBadType = MyBadType { f1 :: Html } deriving (Lift)
I get this error:
• No instance for (Lift Html)
arising from the first field of ‘MyBadType’ (type ‘Html’)
Possible fix:
use a standalone 'deriving instance' declaration,
so you can specify the instance context yourself
• When deriving the instance for (Lift MyBadType)
Now, it is pretty clear from this error what GHC wants me to do. But I would really avoid having to instantiate Lift (or Data) myself for the Html type.
Is there a way I can avoid it? Or a different approach I am missing here? Or is implementing the instances trivial by some trick I am not aware of?
I am aware that I could just store the markdown source as a Text during compile time and render it at runtime, but I would like to know if there is an alternative.
You can try defining manual instances as in the following proof-of-concept. However, I'd suggest doing some objective benchmarking before assuming that this "pre-compiled" markup will perform better than just doing the rendering at runtime.
A general
Lift (String -> String)
instance would be "challenging" to define, but we can lift aStaticString
like so, by getting its string value and then using theIsString
instance to construct one afresh:Once that's defined, a
ChoiceString
instance is tedious but straightforward, except for theByteString
. You could consider using theLift ByteString
instance fromth-lift-instances
instead, or maybe there's an even better one that I don't know about.That leaves
HTML = MarkupM ()
. TheAppend
constructor forMarkupM
poses a problem, since it introduces a newMarkupM b
type quantified over anyb
. This means that an instance:won't work, because we'll never be able to guarantee the needed
Lift b
forAppend
. We can cheat by writing an illegalLift
instance that only works forMarkupM ()
. Note here that any values of typea
in constructors are ignored and assumed to be() :: ()
.This appears to work for the following example: