如何用TemplateHaskell实现编译期生成Narration结构?避免重复解析
Great question! The core issue here is that your current code validates the narration file at compile time but still generates code that parses it again at runtime. To fix this, we can directly embed the compiled-time parsed Narration value into the generated Haskell AST, so runtime uses it directly without re-parsing.
Here's how to do it step by step:
1. Ensure Your Narration Type Has a Lift Instance
Template Haskell needs a Lift instance to convert runtime values (in this case, your compiled-time Narration value) into Haskell expression ASTs. The easiest way is to derive it with the DeriveLift extension:
{-# LANGUAGE DeriveLift #-} -- Your existing Narration definition data Narration = Narration { ... } -- Replace with your actual constructors and fields deriving (Lift) -- Add this to derive the Lift instance automatically
If you can't derive it (e.g., for types without default Lift support), you can write a manual instance:
import Language.Haskell.TH.Lift instance Lift Narration where lift (YourNarrationCtor str num nestedItems) = [| YourNarrationCtor $(lift str) $(lift num) $(lift nestedItems) |] -- Repeat this pattern for all your Narration constructors
2. Rewrite embedNarration to Embed the Parsed Value Directly
Instead of generating code that calls parseNarration at runtime, we'll parse the file at compile time, validate it, then convert the resulting Narration value directly into an AST expression:
import Language.Haskell.TH import Language.Haskell.TH.Lift import System.IO (readFile) embedNarration :: String -> Q Exp embedNarration file = do -- Use runIO instead of unsafePerformIO: it's the safe, idiomatic way for IO in TH text <- runIO $ readFile file case parseNarration text of Left err -> throw $ ErrorCall $ "Compile-time parsing failed for " ++ file ++ ": " ++ err Right parsedNarration -> lift parsedNarration -- Convert the validated Narration value to an AST expression
How This Works
Compile Time: When you use
$(embedNarration "path/to/narration.txt")in your code, Template Haskell will:- Read the target file and parse it into a
Narrationvalue. - If parsing fails, throw a clear compile-time error (matching your original validation behavior).
- If successful, convert the
Narrationvalue into a concrete Haskell expression (e.g.,Narration "Chapter 1" [Paragraph "Welcome to the story"]).
- Read the target file and parse it into a
Runtime: The generated code will directly use this pre-parsed
Narrationvalue—no more calls toparseNarration, no duplicated work, and zero runtime parsing overhead.
Why This Beats Your Original Code
Your original code generated something like:
(fromRight undefined . parseNarration) "entire file content here"
Which forces redundant runtime parsing even though you already validated the file at compile time. The rewritten version skips runtime parsing entirely by embedding the final Narration structure directly into your code.
内容的提问来源于stack exchange,提问作者Eva

