"
```
## Controlflow
You can use Either and Maybe in your documents:
```haskell
>>> Div :> Just (Div :> "a")
>>> Div :> if True then Nothing else Just (Div :> "b")
>>> Div :> if True then Left (Div :> "a") else Right "b"
>>> Div :@ (if True then Right (IdA "a") else Left (ClassA "a")) :> "b"
b
```
If you use Either, both possible outcomes are typechecked if they are valid children.
You can combine Either, Maybe and Lists in any way you want. The
types will get jolly complicated, but if you don't write the types,
you'll be good.
## Performance
`type-of-html` is a lot faster than `blaze-html` or than `lucid`.
Look at the following benchmarks:
Remember this benchmark from `blaze-html`?
![blaze](https://jaspervdj.be/blaze/images/benchmarks-bigtable.png)
This is comparing blaze with `type-of-html`:
![type-of-html](https://user-images.githubusercontent.com/5609565/98452828-48a97780-2153-11eb-8e6c-2981c69e7d0d.png)
To look at the exact code of this benchmark look into the repo. The
big table benchmark here is only a 50x10 table. Using a 1000x10 table
like on the blaze homepage yields even better relative performance,
but would make the other benchmarks unreadable. The synthetic page is
just a small page with a lot of elements and a lot of attributes but
with minimal text. The hackage upload benchmark is a rendering of
https://hackage.haskell.org/upload which contains a lot of text.
Note how the `compactHTML` rendering is about 150 times faster than
blaze for the real world case. Look below for the explanation of the
difference between `type-of-html` and `compactHTML`.
How is this possible? We supercompile lots of parts of the generation
process. This is possible thanks to the new features of GHC 8.2:
AppendSymbol. We represent tags and attributes as kinds and map these
to (a :: [Symbol]) and then fold all neighbouring Symbols with
AppendSymbol. Afterwards we reify the Symbols with symbolVal which
will be embedded in the executable as Addr#. All this happens at
compile time. At runtime we do only generate the content and append
Builders. Because all functions from this library get inlined, we'll
automatically profit from future optimizations in ByteString.Builder.
For example, if you write:
```haskell
renderText $ Tr :> Td :> "test"
```
The compiler does optimize it to the following (well, unpackCString#
doesn't exist for Builder, so it's slightly more complicated):
```haskell
decodeUtf8 $ toLazyByteString
( Data.ByteString.Builder.unpackCString# ""#
<> builderCString# "test"#
<> Data.ByteString.Builder.unpackCString# " |
"#
)
```
Note that the compiler automatically sees that your string literal
doesn't need utf8 and converts directly the `"test"# :: Addr#` to an
escaped Builder without any intermediate structure, not even an
allocated bytestring.
```haskell
renderByteString $ Tr :> Td :> "teſt"
```
Results in
```haskell
toLazyByteString
( Data.ByteString.Builder.unpackCString# ""#
<> encodeUtf8BuilderEscaped prim (Data.Text.unpackCString# "te\\197\\191t"#)
<> Data.ByteString.Builder.unpackCString# " |
"#
)
```
If you write
```haskell
renderBuilder $ Div :> Div
```
The compiler does optimize it to the following:
```haskell
Data.ByteString.Builder.unpackCString# ""#
```
This sort of compiletime optimization isn't for free, it'll increase
compilation times.
Note that compiling with `-O2` results in a ~25% faster binary than
with `-O` and compiling with `-O0` compiles about 15 times faster, so
be sure that you develop with `-O0` and benchmark or deploy with
`-O2`. Be aware, that cabal compiles only with `-O` if you don't
specify explicitly otherwise.
### Even faster
Need for speed? Consider following advise, which is sorted in
ascending order of perf gains:
If you've got attributes or contents of length 1, use a Char.
This allows for a more efficient conversion to builder, because we
know the length at compile time.
```haskell
Div :> 'a'
```
If you know for sure that you don't need escaping, use `Raw`.
This allows for a more efficient conversion to builder, because we
don't need to escape.
```haskell
Div :> Raw "abc"
```
If you've got numeric attributes or contents, don't convert it to a
string.
This allows for a more efficient conversion to builder, because we
don't need to escape and don't need to handle utf8.
```haskell
Div :> (42 :: Int)
```
If you know that an attribute or child is empty, use `()` or omit it altogether.
This allows for more compile time appending and avoids two runtime
appends.
```haskell
Div :> ()
```
If you know for sure a string at compile time which doesn't need
escaping, use a `Proxy Symbol`.
This allows for more compile time appending and avoids two runtime
appends, escaping and conversion to a builder.
```haskell
Div :> (Proxy @"hello")
```
These techniques can have dramatic performance implications,
especially the last two. If you replace for example in the `big page
with attributes` benchmark every string with a Proxy Symbol, it'll run
in 10 ns which is 500 times faster than `blaze-html`. Looking at core
shows that this is equivalent of directly embedding the entire
resulting html as bytestring in the binary and is therefore the
fastest possible output.
### CompactHTML
You can use an even more performant representation if you can
sacrifice some flexibility. If every variable of your html document
is just some type which will be converted into a Builder, you can
render your entire html document as a list of ByteString which will be
cached by ghc and interspersed with variables. This will be in a lot
of cases orders of magnitude faster. This is especially the case with
larger pages. For very small pages (let's say upto 5 elements) this
technique is slower.
```haskell
{-# LANGUAGE TypeApplications #-}
{-# LANGUAGE DataKinds #-}
import Html
test x y = Div :> ("Hello, my name is: " # x)
# Div :> ("I'm of age: " # y)
-- It is important that your CompactHTML is a top-level value.
myDoc :: CompactHTML ["name", "age"]
myDoc = compactHTML $ test (V @"name") (V @"age")
main :: IO ()
main = putStrLn $ renderCompactString myDoc (Put "Bob") (Put (42 :: Int))
```
You can easily investigate your CompactHTML in ghci thanks to a colorful prettified show instance:
```haskell
>>> myDoc
"Hello, my name is: "
name
"
I'm of age: "
age
"
"
```
## Custom Attributes and Elements
You can define your own attributes, for example data-* or htmx. These
custom attributes reside as well 100% at the type level and don't
incur any performance penalty. Beware that it is up to you to choose a
valid attribute name.
```haskell
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds #-}
module Main where
import Html
newtype instance Attribute
"data-name" -- name for rendering
'True -- global attribute
v -- value
= DataNameA v
data instance Attribute
"person-vegan" -- name for rendering
'True -- global attribute
() -- boolean attribute
= PersonVeganA
main :: IO ()
main = print $ Div :@ (DataNameA "foo" # PersonVeganA) :> "bar"
```
Please note that you have to use `newtype instance` for attributes with
values and `data instance` for boolean attribute. Otherwise you'll get
a compiler error about roles.
And you can define your custom elements:
```haskell
{-# LANGUAGE TypeFamilies #-}
{-# LANGUAGE DataKinds #-}
module Main where
import Html
data instance Element
"banana" -- name for rendering
'[Flow, Phrasing] -- content categories
Flow -- content model
'["async", "for"] -- allowed attributes besides global attributes
= Banana
main :: IO ()
main = print $ Banana :@ AsyncA "foo" :> "bar"
```
## Notation
If you want a bit cleaner notation which resembles blaze, you can use
the language pragma rebindable syntax:
```haskell
{-# OPTIONS_GHC -fno-warn-missing-signatures -fno-warn-unused-do-bind -fno-warn-name-shadowing #-}
{-# LANGUAGE NoMonomorphismRestriction #-}
{-# LANGUAGE RebindableSyntax #-}
import Prelude
import Html
main :: IO ()
main = print $
Html :> do
Body :> do
H1 :@ IdA "a" :> do
Img
Strong :@ ClassA "b" :> (0 :: Int)
Div :> do
Div :@ IdA "c" :> (1 :: Int)
Div :> do
Form :@ ClassA "d" :> do
Fieldset :> do
Div :@ IdA "e" :> do
Div :> do
Label :@ ClassA "f" :> "a"
Select :> do
Option :@ IdA "g" :> "b"
Option :> "c"
Div :@ ClassA "h" :> "d"
I :> "a"
Button :@ IdA "i" :> do
I :> "e"
where
(>>) = (#)
```
If you are on GHC 9.0 or newer, you can also use the [`QualifiedDo`](https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/exts/qualified_do.html#extension-QualifiedDo) language extension via
```haskell
{-# OPTIONS_GHC -fno-warn-unused-do-bind #-}
{-# LANGUAGE QualifiedDo #-}
import Prelude
import Html
import qualified Html.QualifiedDo as H
main :: IO ()
main = print $
Html :> H.do
Body :> H.do
H1 :@ IdA "a" :> H.do
Img
Strong :@ ClassA "b" :> (0 :: Int)
Div :> H.do
Div :@ IdA "c" :> (1 :: Int)
Div :> H.do
Form :@ ClassA "d" :> H.do
Fieldset :> H.do
Div :@ IdA "e" :> H.do
Div :> H.do
Label :@ ClassA "f" :> "a"
Select :> H.do
Option :@ IdA "g" :> "b"
Option :> "c"
Div :@ ClassA "h" :> "d"
I :> "a"
Button :@ IdA "i" :> H.do
I :> "e"
```
## Comparision to lucid and blaze-html
Advantages of `type-of-html`:
- more or less 10 times faster on a medium sized page
- a lot higher type safety: nearly no invalid document is inhabited
- fewer dependencies
Disadvantages of 'type-of-html':
- a bit noisy syntax (don't write types!)
- sometimes unusual type error messages
- compile times (30sec for a medium sized page, with `-O0` only ~2sec)
- needs at least ghc 8.2
- you may need to specify {-# OPTIONS_GHC -freduction-depth=0 #-}
I'd generally recommend that you put your documents into an extra
module to avoid frequent recompilations. Additionally you can use
`type-of-html` within an `blaze-html` document and vice versa. This
allows you to gradually migrate, or only write the hotpath in a more
efficient representation.
## Example usage
If you'd like to see a full fledged example, go to following module
from the benchmarks of this library:
[Example](https://github.com/knupfer/type-of-html/tree/master/bench/ExampleTypeOfHtml.hs)
It is a verbatim implementation of the source of
http://hackage.haskell.org/upload
If you wish, you can compare the clarity of the code with an
implementation in blaze:
[Example](https://github.com/knupfer/type-of-html/tree/master/bench/ExampleBlaze.hs)
I'd argue that the source code in `type-of-html` has some visual advantages:
- qualified imports aren't needed
- it's more consistent, e.g. no spurious underscores (e.g. `rel_` vs `ref`)
- clearer distinction between data and functions, in `type-of-html`
elements and attributes are capitalized
Note that the type of `hackageUpload` is quite big (about 1kLOC of type signature), so don't even bother to ask ghci :).
## FAQ
### Why don't you provide a pretty printer?
It's sadly not possible to pretty print html source in a semantic preserving way.
If you add before every tag a newline and indentation, the following happens:
```html
ab
```
```html
a
b
```
This would add a space when rendering. So perhaps we'll avoid all
these text modifying tags, but now look at ``. Every whitespace
and newline within is semantically important. And even if we avoid
modifying stuff within ``, there is the nasty css property
`white-space: pre;`. With this, all bets are off to modify any
indenting, because we can't know which css will apply to the document:
- It may include a link, so it would be dynamic.
- There might be meddling javascript.
- The user of the browser is free to activate a custom css.
If we go creative, there is still one option:
```html
```
This would be semantically correct, but would this be pretty?
I recommend, that if you want to debug html, use mozilla fire bug, so
you can as well fold trees and look at the rendering.
### Why don't you keep track of appended tag lengths at compile time?
Well, it's true that we could improve performance a miniscule by that:
by knowing the length of the string, we could just use internal
bytestring functions to copy over the addr#. This would avoid a ccall
to determine the length of the addr# more or less per tag.
At the other hand, luckily ghc moves all these bytestrings to the
toplevel in core, so we have this cost only the first time we use this
bytestring. Considering that most webpages have a lot more than one
visitor, this dwarfs the cost (about 3 nanoseconds) a lot.
The negative side of doing this would be a severe complication for the
typechecker, which is already quite stressed with this library.
Besides, there is a ghc ticket about replacing compile time strings
(addr#) with bytearray#, which contains it's length. So in some
future, we'll have this for free.
### Wouldn't it be more efficient to append all compile time stuff to one huge Symbol and use '[(Nat,Nat)] to slice it?
Cool idea, but no. This would reduce memory fragmentation and avoid a
terminating null more or less per tag at the cost of the lost of
sharing. Being unable to inspect a Symbol, we would end up with a lot
of repetitions, which are at the moment shared. Even the bytestring,
which is used for the Symbol, is shared of equal Symbols.
### Isn't there any performance tweak left?
As far as I know: no. If you disagree, please file an issue.