[Haskell-cafe] Templates as typeclasses?
Alberto G. Corona
agocorona at gmail.com
Fri May 9 10:39:54 UTC 2014
Hi Mike, all.
Haskell prefer composability over inheritance. Composition is on site,
while inheritance can be hidden to the programmer and it can be too rigid.
What happens if I create my framework around a class template with header
and footer and later I need to program web services with it? I need to
create more classes for each particular problem and so on in the framework.
It is possible that these classes don´t fit the need of every particular
problem.
In the other side, a basic set of combinators can fit any problem. So
inheritance can become bad magic. While composability gives freedom.
For those of you that may not know about it, MFlow [1] templates use
composition of formlets called widgets. A MFlow page has type:
View renderingFormat monad returnValue
It is made by the applicative and monadic composition of formets with the
same type.
I can have:
import MFlow.Wai.Blaze.Html.All
myTemplate body= html <<< myheader **> body <** myfooter
where
myheader = ....
myfoother = ...
with "html" a blaze-html combinator. "myHeader" and "myfooter" are widgets.
body is the parameter widget that I give to the template.
the operator **> ignores the result of the first term like *>
if whe define:
type Widget a= View Text.Blaze.Html IO a
then:
myTemplate:: Widget a -> Widget a
I can alternatively do it using classes. I define a class:
class Template where
header:: Widget ()
body :: Widget a
footer :: Widget ()
data MyTemp a = .....
instance Template (MyTemp a) where
...
..
myTemplate mytemp= html << header mytemp **> body mytemp <** footer mytemp
It seems cumbresome, rigid and redundant to me, like many OO code..
What "header" means in that class? it includes also the html <header> tag
or it is simply some common html for all pages at the beginning? It is
probable that this class definition is not appropriate for all pages.
neither the combinator version. but with an EDSL I can create page template
variants faster for my particular problem.
But some particular problem can justify the creation of class templates in
order to enforce some requirements and make use of the extra level of
abstraction that it brings. As shown above, It is possible to do it in
Haskell and MFlow.
But this is not IMHO the right base design assumption for a (Haskell) web
framework. It may be for some particular problem.
[1] http://mflowdemo.herokuapp.com
2014-05-09 10:15 GMT+02:00 Tobias Dammers <tdammers at gmail.com>:
> You can do similar things with Haskell EDSL-style template systems such
> as Blaze, except that Haskell is not an OOP language, so you will be
> using different abstractions instead.
>
> Still, the big picture with these is that template building blocks are
> language elements, and template code and regular code can be mixed
> freely. The `h1` function from Blaze is just another Haskell function;
> it takes a MarkdownM () value and returns a MarkdownM (), and because
> MarkdownM happens to be a Monad, you can use do notation to sequence
> HTML elements. MarkdownM () is also an instance of IsString, so if you
> enable -XOverloadedStrings, you can use string literals to produce HTML
> text nodes. This means that you can write, in plain Haskell:
>
> h1 $ do
> span "Hello, "
> span "world!"
>
> ...and it'll produce <h1><span>Hello, </span><span>world!</span></h1>.
>
> For the common case where you have a "master" template and an
> incarnation, you could write a master template that takes its inner
> blocks as arguments, e.g.:
>
> masterT :: Text -> Html -> Html -> Html
> masterT titleText headerExtra content = do
> html $ do
> header $ do
> title $ toHtml titleText
> headerExtra
> body $ do
> h1 $ toHtml titleText
> div ! class_ "content" $ content
> footer $ "This is my super footer!"
>
> If you want something that behaves more like inheritance, the best way
> IMO would be to define the available blocks as a record type, e.g.:
>
> data TemplateBlocks =
> TemplateBlocks
> { tbTitleText :: Text
> , tbHeaderExtra :: Html
> , tbContent :: Html
> }
> -- Then provide some sensible defaults:
> defTemplateBlocks :: TemplateBlocks
> defTemplateBlocks =
> TemplateBlocks
> { tbTitleText = "My super web app"
> , tbHeaderExtra = return ()
> , tbContent = return ()
> }
> -- and then your master template becomes:
> masterT :: TemplateBlocks -> Html
> masterT tb = do
> html $ do
> header $ do
> title $ toHtml $ tbTitleText tb
> tbHeaderExtra tb
> body $ do
> h1 $ toHtml $ tbTitleText tb
> div ! class_ "content" $ tbContent tb
> footer $ "This is my super footer!"
> -- And then you can call it with the defaults, overriding
> -- as needed:
> masterT $ defTemplateBlocks
> { tbTitleText = "Homepage"
> , tbContent = div "Hello, world!"
> }
>
> Now, if you want to use template blocks *inside* template blocks, you
> need to alter your blocks a tiny bit in order to read other blocks into
> them:
>
> data TemplateBlocks =
> TemplateBlocks
> { tbTitleText :: Text
> , tbHeaderExtra :: TemplateBlocks -> Html
> , tbContent :: TemplateBlocks -> Html
> }
> -- now you could do, for example:
> masterT $ defTemplateBlocks
> { tbTitleText = "Homepage"
> , tbContent tb =
> div $ do
> "Hello, world!"
> "You are here: "
> toHtml $ tbTitleText tb
> }
>
> Now if MarkupM were implemented as a monad transformer, we could even
> stack it on top of a MonadReader TemplateBlocks to avoid passing the tb
> parameter explicitly, and lensify the whole thing, but oh well. (BTW, is
> anyone aware of any efforts in making Blaze into a transformer?)
>
> On Fri, May 09, 2014 at 02:34:52AM -0500, Mike Meyer wrote:
> > On Fri, May 9, 2014 at 2:11 AM, Tobias Florek <haskell at ibotty.net>
> wrote:
> >
> > > hi,
> > >
> > > The objects generated by the templates don't do much of
> > >> anything to let the application author leverage the type system, which
> > >> is what makes Cheetah stand out from other web template systems.
> > >>
> > >
> > > can you elaborate on that point? i would really like to see an example
> on
> > > what you mean.
> > >
> > > for another datapoint, have a look at hastache's use of generics
> > > hackage.haskell.org/package/hastache/docs/Text-Hastache.html
> >
> >
> > I mentioned that briefly in the original message: A Cheetah template is a
> > Python class. It can inherit from Python classes, and Python classes can
> > inherit from it.
> >
> > A standard usage is to have a page template that sets up headers and
> > footers, and make your pages are all children of that class. While
> > header/footer support is a stock feature of any modern templating system,
> > Cheetah does it by leveraging it's integration into the Python class
> system
> > instead of providing a specific mechanism for it.
> >
> > If you need something to happen on the server when the page renders -
> that
> > doesn't leave any traces in the output - you can subclass the template
> with
> > a standard Python class and provide that functionality. You then use the
> > Python subclass just like a regular template. Again, the behavior isn't
> > special, but doing it takes no special machinery, just leveraging Cheetah
> > templates being classes.
> >
> > You can find simple examples of this in the Cheetah docs:
> >
> http://www.cheetahtemplate.org/docs/users_guide_html_multipage/howWorks.objoriented.html
>
> > _______________________________________________
> > Haskell-Cafe mailing list
> > Haskell-Cafe at haskell.org
> > http://www.haskell.org/mailman/listinfo/haskell-cafe
>
> _______________________________________________
> Haskell-Cafe mailing list
> Haskell-Cafe at haskell.org
> http://www.haskell.org/mailman/listinfo/haskell-cafe
>
--
Alberto.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://www.haskell.org/pipermail/haskell-cafe/attachments/20140509/6dec01b1/attachment-0001.html>
More information about the Haskell-Cafe
mailing list