[Haskell-cafe] Re: I'm stuck in my thought experiment
afalloon at synopsys.COM
Fri Aug 17 09:17:16 EDT 2007
Levi Stephen wrote:
> Apologies for a long post that may not be totally clear. I was thinking
> a problem and how the data might be represented in Haskell. I'm now
> stuck and
> frustrated. Now, I'm not even sure whether I'm on the right track (I might
> still be thinking too OO). Suggestions/ideas would be much appreciated.
> I was imagining a drag and drop web page designer. There are a bunch of
> Widgets (e.g., BlogWidget, TextWidget, MenuWidget, etc) that the user can
> place on the page.
> Along with these are layout/container widgets (e.g., ColumnLayoutWidget)
> that can contain
> other widgets.
> I'm looking at a data structure that would allow this to be represented
> in Haskell,
> so I'm keeping in mind that these won't be written in code, but generated
> on the fly somehow (e.g., from a database or file).
Maybe I am misunderstanding your requirements, but it seems to me that
the simplest solution would be best in this case:
data Widget = BlogWidget [Article]
| TextWidget String
| MenuWiget Menu
| Rows Spacing [Widget]
| Columns Spacing [Widget]
You can also add a type parameter if you want to be able to carry around
extra metadata about pages, or you could even parameterize the Article
and Menu types if you want to be able to extend them separately or if
you want to ensure your layout algorithms don't depend on widget
contents by keeping their type abstract.
> So, my thoughts were along the lines of something like:
> class Widget a where
> render :: a -> Html
> -- A page has a title and a Widget.
> -- I know this isn't valid Haskell, but I'm not sure how to specify what I
> -- want here. (existential types?)
> data Page = Page String Widget
> data TextWidget = TextWidget String
> instance Widget TextWidget ....
> -- An example layout widget
> data ColumnLayoutWidget = ColumnLayoutWidget [Widget]
> instance Widget ColumnLayoutWidget ...
> So, entire pages might be represented something like:
> Page "Main" (ColumnLayoutWidget [MenuWidget, TextWidget mainPageText])
> Page "About" (ColumnLayoutWidget [MenuWidget, TextWidget aboutPageText])
This code seems to indicate that you want to be able to extend the
widget types without changing this source file. This is a good goal, but
it may not be worth the extra complexity.
Also, this looks a lot like the Composite pattern from OO. A rule of
thumb that I use is: "if I would do this with inheritance in OO, I
probably want a variant in FP". Since Composite depends on the
inheritance of the composite object type, I would probably look to use a
single data type with multiple constructors for the different
compisites like the Widget type above.
If I wanted to develop the widgets themselves separately from the
layout, I would probably do something like this:
class Widget a where
render :: a -> Html
bbox :: a -> Size
type Layout = forall a. Widget a => Widget a
| Rows Spacing [Layout]
| Columns Spacing [Layout]
| Grid Spacing [[Layout]]
type Page = Page String Layout
renderLayout :: Layout -> Html
renderPage :: Page -> Html
> Where I get stuck, is I want to extract layout information into a parent
> This would allow global changes such as adding a header image to the
> above pages
> to be done once only.
By making the layout type separate from the widgets themselves, it
allows you to examine the layout and do any transformations you want
without having to know anything about the widgets.
> So I want to be able to have something like:
> layout = Page "Main" (ColumnLayoutWidget [MenuWidget, ??? ])
> mainPage = ChildPage layout [TextWidget mainPageText]
> aboutPage = ChildPage layout [TextWidget aboutPageText]
> So, each page is it's layout/parent page, with additional widgets
So you want some sort of wildcard element that can be substituted in
later? Maybe I am misunderstanding your requirement, but if thats the
behavior you want, you should check out the term-level evaluators for
lambda calculus for inspiration on substitution, but I expect your
requirement may be simpler than that.
> The issue becomes, given a parent page and the customized content for
> the child page,
> what is the best way to insert the customized content at the right point?
> Might a tree like structure be useful? But, how do you work out where in
> the tree
> child content gets added? Store a traversal with each sub tree of child
> page content
> that basically says 'insert here'?
This is probably a good use for a zipper (a kind of functional
iterator). http://en.wikibooks.org/wiki/Haskell/Zippers that way you can
pass around a value that means "right here", and its clear where the
substitution will happen.
Another good zipper war story is from xmonad:
> It might be simple to have a PlaceHolderWidget. Then insertions of the
> child page
> content happens at each of those widgets.
> This just gets trickier if I start considering multiple extension points
> for child
> pages and what happens when the layout/parent page changes. This is why I'm
> thinking I may be going along a bad path here.
Exactly. With multiple substitutions you get into issues of naming, so
thats why looking at lambda calculus evaluators would be the right
inspiration, but I think it may be more complicated than you need. The
zipper approach might be easier.
More information about the Haskell-Cafe