This has been done by the OCsigen project, a web framework implemented in OCaml, that seeks to provide strong typing guarantee.
You can have a look at their Html5 interface on this documentation page. See for example the type of the img smart constructor (it's a mouthful!):
val img :
src:Xml.uri ->
alt:Html5_types.text ->
([< `Accesskey
| `Class
| `Contenteditable
| `Contextmenu
| `Dir
| `Draggable
| `Height
| `Hidden
| `Id
| `Ismap
| `OnAbort
| `OnBlur
| `OnCanPlay
| `OnCanPlayThrough
| `OnChange
| `OnClick
| `OnContextMenu
| `OnDblClick
| `OnDrag
| `OnDragEnd
| `OnDragEnter
| `OnDragLeave
| `OnDragOver
| `OnDragStart
| `OnDrop
| `OnDurationChange
| `OnEmptied
| `OnEnded
| `OnError
| `OnFocus
| `OnFormChange
| `OnFormInput
| `OnInput
| `OnInvalid
| `OnKeyDown
| `OnKeyPress
| `OnKeyUp
| `OnLoad
| `OnLoadStart
| `OnLoadedData
| `OnLoadedMetaData
| `OnMouseDown
| `OnMouseMove
| `OnMouseOut
| `OnMouseOver
| `OnMouseUp
| `OnMouseWheel
| `OnPause
| `OnPlay
| `OnPlaying
| `OnProgress
| `OnRateChange
| `OnReadyStateChange
| `OnScroll
| `OnSeeked
| `OnSeeking
| `OnSelect
| `OnShow
| `OnStalled
| `OnSubmit
| `OnSuspend
| `OnTimeUpdate
| `OnVolumeChange
| `OnWaiting
| `Spellcheck
| `Style_Attr
| `Tabindex
| `Title
| `User_data
| `Width
| `XML_lang
| `XMLns ],
[> `Img ])
nullary
(In OCaml's standard syntax, a type t
with three type parameters
a
, b
and c
is written (a,b,c) t
rather than t a b c
).
The fact that <img>
may have no child is encoded by the use of the
"nullary" type here. The rest of the static information encodes which
kinds of attributes may be used on this node.
The weird `Foo | `Bar | `Baz
stuff is a so-called "polymorphic
variant" (presented in eg. this article), a kind of extensible structural sum type using row
polymorphism that is more or less unique to OCaml (while they would be
useful to any programming language, as extensible records generalize
the usual nominal records of OCaml and Haskell). Here there are mostly used as a form of type-level lists.
Other than that, that is a relatively classic use of phantom types, only lead to extreme sizes because of the sheer number of cases you have in the HTML spec. Phantom types are the precursors of GADT to enforce additional type abstraction in the ML world. In Haskell98, you would probably try to encode the same kind of type-level information using type-classes rather than directly type abstractions.