compile-time vs. run-time cost of Hamlet templates

257 views Asked by At

For the Hamlet template mechanism, I understand that the template is parsed at compile time, resulting in source code that contains calls to blaze-html combinators (and possibly other expressions because of interpolation). So the points (subtrees) where interpolation happens are known at compile-time.

At run-time, we need to compute the interpolated value (of course), and "plug it" in the tree, that is, apply the html combinators. All of them? Actually some of these applications could be evaluated at compile-time (those that have no interpolation below them). Does this happen?

3

There are 3 answers

0
Michael Snoyman On BEST ANSWER

It's been a while since I worked on the code, so don't take this as authoratative (like Daniel mentioned, -ddump-simpl is a good call here). But I don't believe we're using the blaze-html combinators, just the data type. Hamlet itself concatenates strings as much as possible at compile time to avoid that cost at runtime. I know when I last benchmarked (which granted was years ago), that optimization did pay off pretty well.

1
Daniel Wagner On

Probably not: what you are asking for sounds a lot like partial evaluation (not to be confused with partial application), which is a bit of a compiler-performance minefield, hence often avoided. But you can check for yourself; use -ddump-simpl with your preferred optimization level to see the core that GHC generates.

0
d8d0d65b3f7cf42 On

as Michael says "Hamlet itself concatenates strings as much as possible at compile time to avoid that cost at runtime."

For the example from the book,

main = putStrLn $ renderHtml [shamlet|
<p>Hello, my name is #{name person} and I am #{show $ age person}.
<p>
    Let's do some funny stuff with my name: #
    <b>#{sort $ map toLower (name person)}
<p>Oh, and in 5 years I'll be #{show ((+) 5 (age person))} years old.
|]
  where
    person = Person "Michael" 26

-ddump-simpl contains this:

               (>>
                  @ Text.Blaze.Internal.MarkupM
                  Text.Blaze.Internal.$fMonadMarkupM
                  @ ()
                  @ ()
                  (id
                     @ (Text.Blaze.Internal.MarkupM ())
                     (. @ Data.Text.Internal.Text
                        @ Text.Blaze.Internal.Markup
                        @ String
                        Text.Blaze.Internal.preEscapedText
                        Data.Text.pack
                        (GHC.CString.unpackCString#
                           ".</p>\n\
                           \<p>Let's do some funny stuff with my name: <b>"#)))

Indeed, that's not the syntax tree of the HTML (last line - the string contains a closing tag and the next opening tag).

This Hamlet feature should be advertised more!