With RebindableSyntax, HTML in Haskell can be more readable than HTML itself :big_smile: :
example::HTMLexample=htmldo#headdo#title"This is a title"#bodydo"Hello World!"#h1"Heading level 1"#h2"Heading level 2"#h3"Heading level 3"#h4"Heading level 4"#h5"Heading level 5"#h6"Heading level 6"#p"Paragraph"#ado#href=:"https://www.wikipedia.org/""A link to Wikipedia!"#input$#type=:"text"#input$#type=:"file"#input$#type=:"checkbox"comment"This is a comment"#abbrdo#id=:"anId"#class=:"jargon"#style=:"color:purple;"#title=:"Hypertext Markup Language""HTML"
@Joel McCracken problem is that you have to define every possible tag and attribute in library - with labels, user can create arbitrary one, while we can still possibly check that it's a valid tag (consisting of alphanumeric characters) at compile-time
@TheMatten Separate library, I'd think? A couple of things to note: a) not sure Chris would be open to changing the syntax of the lucid library in this manner, b) we could extend this syntax to CSS too, which means html-do could contain css-do ... as well as non-html xml documents like svg-do or rss-do.
Rib can easily switch to it, as long as this HTML monad will support including raw html somewhere inside.
Syntactically it looks good to me, however semantically it would be better to have this be a map than a list. What happens when there are duplicate attributes? [#class "one", #class "two"]?
(Okay, not sure about css-do -- because that's not even XML (and the clay library does a good job for now) -- but other formats like svg, and rss could definitely make use of this monadic syntax; in fact, I've been looking for one to generate RSS feeds on my website).
Ok - we will need to build our own pretty printer then (with sanitization and stuff)?
a) I don't expect him to - I choose to not use Monad for HTML builder and instead expose (>>) for custom behaviour through RebindableSyntax
b) Not sure if CSS is as good fit as HTML for something like this - but if we can sort out syntax of selectors, it could be useful
-- | 'HTMLBuilder's can be sequenced by appending more children.instance(builder~HTMLBuilder,builder'~HTMLBuilder)=>Sequencebuilderbuilder'where(>>)=(<>)-- | We fall back to 'Monad' if type follows shape of @m [email protected]instance{-# incoherent #-}(Monadm,ma~ma)=>Sequencema(mb)where(>>)=(P.>>)
If you have some State, you probe it in some outer do - in same way as there's not syntax for binding inside of list literal - it doesn't make much sense
Well, let's say you use that import. Can you define main :: IO () in that same module using do block? Wouldn't that use your Sequence's >> (instead of Monad's >>)?
toHtmlRaw is needed when you get raw HTML from some source beyond your control (such as Pandoc's renderer, or the zulip API returning message raw HTML), and would like to incorporate it in your HTML syntactic monad.
Just to make sure we understand each other - HTMLBuilder isn't a Monad, it's a Monoid and Sequence allows us to use do notation for appending it's values, getting this nice syntax above as a result. This allows for easy HTML construction - parsing HTML is orthogonal problem, but it could be done by lib too, possibly using some mature dependency.
I guess the only reason for using Monad for HTML building is that it does not require RebindableSyntax
If RebindableSyntax is a problem, it could be changed - but to me it feels like a roundabout solution, basically being Writer for no good reason
We can use whatever works well I guess :big_smile:
Okay, let's use blaze-html then - last question is if you're okay with reasoning behind Sequence or if HTMLBuilder should be "useless monad" too to avoid RebindableSyntax:slight_smile:
I don't really know about the last question - I guess it depends on how it would work in practice. So far, all HTML construction I've done have been in pure functions, and that includes the use of State, which I needed to build some state while building the rows of a table. But because lucid used HtmlT monad, I ended up using StateT.
It seems to me that as an user of your library, it shouldn't matter - unless a real monad is needed when constructing HTML? Is that even a possible use case? (I certainly don't imagine someone doing liftIO in the midst of creating HTML tree)
Can it be dependent on HTML in any way?
You can use do syntax as normally - custom HTMLBuilder instance is only selected when you use custom combinators
Basically it builds a table, and for every 7th row if a particular food item had been consumed every day non-stop, puts the string "7" in that table cell.
Done - I'm now pretty happy with it's interface (though threading Sem through was not feasible - let's just bind effects upfront before using html syntax): https://gitlab.com/thematten/html-do
Added few more small changes
Haha, support for html-do in rib may actually end up being reason for me to use it in the future :big_smile:
I may later find some time to do css-do too if it turns out to work nicely :slight_smile:
We can't use labels, because they can't contain - - this looks a little bit stringy, plus I guess in CSS there's not that much interest in filling values dynamically
The CSS stuff should disallow invalid combinations. Like display: 2px. Clay does this. In Clay you have to use strings for selectors like "div.menu-bar", but Clay also allows plain display function.
I guess with CSS being more rigid and "typed", approach with labels does not make that much sense - you have to define all the properties in library anyway
Are there some pain points in clay that could be solved with new interface, or is it good enough to stay with it?
Rib might need a small refactoring to avoid assuming HTML builder, but to begin with https://github.com/srid/rib-sample/blob/7ce0bd6/src/Main.hs#L93-L119 can be replaced with your HTML-do stuff while the Rib.writeHtml above is replaced with Rib.writeFileCached . yourRenderFunction.
(In case if you are interested in poking around; otherwise I will get to it later)
So I forgot about this after all :joy: - but QualifiedDowas accepted and may come in 8.12, so we won't need ugly tricks with RebindableSyntax to use do blocks
The proposal has been accepted; the following discussion is mostly of historic interest.
This proposal introduces a mechanism to override the meaning of a do-notation block (a bit like with -XRebi...
At the same time, I was thinking a little bit about possible solutions and I think I like the idea of special syntax for this purpose, maybe similar to HSX, that could be checked for correctness at compile-time and would interact well with dynamic client-side functionality
Basically, I would like to see something like JSX/Angular done well, possibly paired with some FRP-like or Shpadoinkle-like framework
HSX can be written pretty much like normal HTML. You can write an HSX expression inside your Haskell code by wrapping it with [hsx|YOUR HSX CODE|]. HSX expressions are just a syntax for blaze HTML and thus are automatically escaped as described in the blaze documentation.
🔥 The fastest way to build type safe web apps. IHP is a new batteries-included web framework optimized for longterm productivity and programmer happiness - digitallyinduced/ihp
With
RebindableSyntax
, HTML in Haskell can be more readable than HTML itself :big_smile: :Looks much better, actually. Any reason this can't go in the lucid library (that rib and zulip-archive use)?
:thinking: wonder if this can be interpreted from a data structure.
very neat! :smiley:
I will have to fiddle with this a little bit more if I want monadic
do
to still function properly :sweat_smile:There is also CSS via the
clay
library.I'd love to get a syntax like this into
rib
:-)Do attributes have to be inside the element body though? What's the rational for it?
I mean, how about the following?
Wow that's really cool, didn't realize Haskell had something like that
@Sridhar Ratnakumar I found it to be nicer - but it doesn't have to be that way necessarily
@Vance Palacio See
RebindableSyntax
,OverloadedLabels
,OverloadedStrings
andDataKinds
:slight_smile:(And then some other usual stuff from "GHC Haskell")
personally i think
[href "theurl", title "some tooltip"]
is plenty readable@Joel McCracken problem is that you have to define every possible tag and attribute in library - with labels, user can create arbitrary one, while we can still possibly check that it's a valid tag (consisting of alphanumeric characters) at compile-time
@Sridhar Ratnakumar should I try to integrate this with
lucid
?element by itself in
lucid
:vs
html-do
Without some sort of "html monad" that doesn't really make sense in context of building HTML declaratively
I've removed
(=:)
in favour ofIsLabel
instance:Now, question is - should I integrate this interface with existing library (
lucid
), or should it be self-contained?@TheMatten Separate library, I'd think? A couple of things to note: a) not sure Chris would be open to changing the syntax of the lucid library in this manner, b) we could extend this syntax to CSS too, which means
html-do
could containcss-do
... as well as non-html xml documents likesvg-do
orrss-do
.Rib can easily switch to it, as long as this HTML monad will support including raw html somewhere inside.
About the attributes syntax,
Syntactically it looks good to me, however semantically it would be better to have this be a map than a list. What happens when there are duplicate attributes?
[#class "one", #class "two"]
?(Okay, not sure about
css-do
-- because that's not even XML (and theclay
library does a good job for now) -- but other formats like svg, and rss could definitely make use of this monadic syntax; in fact, I've been looking for one to generate RSS feeds on my website).Ok - we will need to build our own pretty printer then (with sanitization and stuff)?
a) I don't expect him to - I choose to not use
Monad
for HTML builder and instead expose(>>)
for custom behaviour throughRebindableSyntax
b) Not sure if CSS is as good fit as HTML for something like this - but if we can sort out syntax of selectors, it could be useful
It is not a monad? :thinking:
If we want multiple formats other than HTML, this would be relaxed to:
dropping
incoherent
I can use
let
bindings and things in thedo
block?You can use
let
- it desugars tolet .. in ..
Also, interested in seeing how
State
can be used when constructing HTML. Looking forward to playing with the library!Hmm, because of rebinding of core operators like
>>
, the HTML thing should be in its own module, right? Eg, this entirerenderPage
function (and anything else that constructs HTML) should be on its own module: https://github.com/srid/zulip-archive/blob/641a6fa/src/Main.hs#L111-L193Even the clay Css monad cannot be used in the same module because of the rebinding? Is this a necessary limitation?
It doesn't have to be - you just change imports a little bit:
If you have some
State
, you probe it in some outerdo
- in same way as there's not syntax for binding inside of list literal - it doesn't make much senseWell, let's say you use that import. Can you define
main :: IO ()
in that same module usingdo
block? Wouldn't that use your Sequence's>>
(instead ofMonad
's>>
)?No - that's the trick :slight_smile:
I actually test this to make sure it always works - https://gitlab.com/thematten/html-do
Normally, type of do notation is at least
m a
- that never unifies withHTMLBuilder
, and so we can make use of itI wonder how that works. IIUC, the compiler desugars
do
blocks using>>
(and>>=
), butMonad
's>>
is not in scope.Ah, because you defined an instance for it.
In order to switch to your library, I would be replacing (among others) lucid's
toHtmlRaw :: String -> Html ()
with the corresponding function from html-do: https://github.com/srid/rib/blob/0351061/src/Rib/Parser/Pandoc.hs#L69Question is - is all you need one-directional HTML emitting? Because then
HTMLBuilder
could possibly just build finalText
directlyBidirectional. There is also
Lucid.renderText
that needs replacing: https://github.com/srid/rib/blob/251577b/src/Rib/Shake.hs#L138(It ultimately goes to a file on disk, so doesn't have to be pretty, but ideally should be small and valid)
So you're reading some HTML too?
toHtmlRaw
is needed when you get raw HTML from some source beyond your control (such as Pandoc's renderer, or the zulip API returning message raw HTML), and would like to incorporate it in your HTML syntactic monad.Just to make sure we understand each other -
HTMLBuilder
isn't aMonad
, it's aMonoid
andSequence
allows us to usedo
notation for appending it's values, getting this nice syntax above as a result. This allows for easy HTML construction - parsing HTML is orthogonal problem, but it could be done by lib too, possibly using some mature dependency.Gotcha. It is just monadic syntax (without being a monad). Or maybe we should call it do-syntax.
Can Blaze be used for the parsing HTML problem? Like lucid already does: https://github.com/chrisdone/lucid/blob/45a01bb14b491f376f498cd5eba8c3944a129681/src/Lucid/Base.hs#L247-L251
I mean,
blaze-html
: https://hackage.haskell.org/package/blaze-html-0.9.1.2/docs/Text-Blaze-Html.htmlThat is, use Blaze's HTML datastructure (but not its monad) for representing the underlying HTML?
I guess the only reason for using
Monad
for HTML building is that it does not requireRebindableSyntax
If
RebindableSyntax
is a problem, it could be changed - but to me it feels like a roundabout solution, basically beingWriter
for no good reasonWe can use whatever works well I guess :big_smile:
Okay, let's use
blaze-html
then - last question is if you're okay with reasoning behindSequence
or ifHTMLBuilder
should be "useless monad" too to avoidRebindableSyntax
:slight_smile:(though the blaze datastructure is not general enough to incorporate any xml document)
I don't really know about the last question - I guess it depends on how it would work in practice. So far, all HTML construction I've done have been in pure functions, and that includes the use of
State
, which I needed to build some state while building the rows of a table. But because lucid usedHtmlT
monad, I ended up usingStateT
.It seems to me that as an user of your library, it shouldn't matter - unless a real monad is needed when constructing HTML? Is that even a possible use case? (I certainly don't imagine someone doing
liftIO
in the midst of creating HTML tree)I imagine that if one constructs HTML in some context, they build expression in same way as any other normal value:
Can't think of what
b
andd
would be, aside from State'sget
andput
.If
b
andd
are computed independent of the HTML, they may as well be passed as (pure) arguments to the UI function.Wait, can you even use
State
with your Sequence-based do syntax?Can it be dependent on HTML in any way?
You can use do syntax as normally - custom
HTMLBuilder
instance is only selected when you use custom combinatorsSee this example where I use
State
in the midst of HTML construction:Basically it builds a table, and for every 7th row if a particular food item had been consumed every day non-stop, puts the string "7" in that table cell.
Screenshot: image.png
Oh, and notice the use of
>>=
. So I guess monad is useful after all ...That function is defined as
renderFactor :: Monad m => DMap Tracking Identity -> Factor -> HtmlT m (Maybe Bool)
(Idle curiosity: how would this all look if we were to use polysemy instead?)
Ah, so monadic builder makes sense for making effectful combinators without having to use
<$>
/<*>
I guessIn
polysemy
it would berenderBirdsEyeView :: Member HTML r => Calendar (DMap Tracking Identity) -> Sem r ()
with code roughly the same I guessOkay - so at the end, let's say we build interface that allows something like:
That looks good.
What would be the type of the second argument to
runState
here?Sem (State (Map ..) : r) ()
Now there's version using
blaze-markup
in https://gitlab.com/thematten/html-do/-/tree/blaze-html - seeHTML.DoSpec
for examplesYou can use
Text.Blaze.Renderer.Pretty.renderMarkup
to print themOh, I need to add
raw
And I'll probably add
Prelude.HTML.Do
that selects right(>>)
for youNice. Once there is both 'raw2html' and 'html2raw' - we can try integrating it with rib ...
You want
raw2html
for embedding stringly pieces of HTML or for actual parsing?Just to embed it in the HTML do thingy. Think of putting the raw Zulip message HTML coming from their API somewhere in your existing HTML do syntax.
(No need to actually parse it; but ultimately the raw HTML should appear as expected in the final 'rendered' HTML text)
Done - I'm now pretty happy with it's interface (though threading
Sem
through was not feasible - let's just bind effects upfront before using html syntax):https://gitlab.com/thematten/html-do
Added few more small changes
Haha, support for
html-do
inrib
may actually end up being reason for me to use it in the future :big_smile:I may later find some time to do
css-do
too if it turns out to work nicely :slight_smile:When it comes to CSS, would something like:
be useful in any way?
We can't use labels, because they can't contain
-
- this looks a little bit stringy, plus I guess in CSS there's not that much interest in filling values dynamically@Sridhar Ratnakumar :up:
The CSS stuff should disallow invalid combinations. Like
display: 2px
. Clay does this. In Clay you have to use strings for selectors like "div.menu-bar", but Clay also allows plaindisplay
function.Example: https://github.com/srid/website/blob/a0e8db4/src/Website/View/CSS.hs
I guess with CSS being more rigid and "typed", approach with labels does not make that much sense - you have to define all the properties in library anyway
Are there some pain points in
clay
that could be solved with new interface, or is it good enough to stay with it?I think Clay is good enough.
Rib might need a small refactoring to avoid assuming HTML builder, but to begin with https://github.com/srid/rib-sample/blob/7ce0bd6/src/Main.hs#L93-L119 can be replaced with your HTML-do stuff while the
Rib.writeHtml
above is replaced withRib.writeFileCached . yourRenderFunction
.(In case if you are interested in poking around; otherwise I will get to it later)
(I also need to get rid of , or improve, the
Rib.{load,write}Target
ugliness using this opportunity)Hacking on this, if anybody want to pair (tmate)
@TheMatten Think I've found a problem. You can't use a monad as argument to those label thingies? For eg.,
gives:
To reproduce, follow the README in the
html-do
branch of this repo: https://github.com/srid/rib-sample/tree/html-doHmmmm, let's just put this thing on top of Lucid - I won't be able to avoid something like
HtmlT
if we want monadic HTML(BTW, I didn't forget about this - once I resolve some inference problems I will push lucid version out :slight_smile: )
So I forgot about this after all :joy: - but
QualifiedDo
was accepted and may come in 8.12, so we won't need ugly tricks withRebindableSyntax
to usedo
blocks@TheMatten Any news on your HTML DSL attempts?
(btw, this topic comes up in 1st page of google when searching for
haskell html dsl
:grinning:)So we have
QualifiedDo
now and thus we could do something like:At the same time, I was thinking a little bit about possible solutions and I think I like the idea of special syntax for this purpose, maybe similar to HSX, that could be checked for correctness at compile-time and would interact well with dynamic client-side functionality
Basically, I would like to see something like JSX/Angular done well, possibly paired with some FRP-like or Shpadoinkle-like framework
Just came across https://marketplace.visualstudio.com/items?itemName=s0kil.vscode-hsx
image.png
https://github.com/digitallyinduced/ihp/blob/master/IHP/HtmlSupport/Parser.hs