Skip to content

Web Development: We Are All Doing It Wrong

July 29, 2011

Well, almost all of us. Two projects are shining beacons of hope: Luna and Opa.

I’ve spent the last couple of weeks trying to write code that does it in my own particular style of less-wrong. When I say less-wrong, what am I actually talking about? At the moment, we write a HTML template on the server, then we write javascript to twiddle bits of the dom generated by that template; thus we at best create tightly interdependent templates and code, and at worst duplicate the two. We struggle to make a good experience with javascript available that also works when javascript is disabled. We write controllers and views that are so tightly coupled that they are really one piece written in two different languages.

My vision is to get away from these issues by writing templates at a slightly different level of declarativeness (in the Luna presentation, he describes this as data binding on steroids), and instead of working in terms of routes and requests, write code in terms of what the user intends with a click (which doesn’t mean you can’t have cool URIs).

This, then, is where I’m at. With caveats: most of these names, even these concepts, are more than a little bit provisional. And this is just the barest of beginnings, it doesn’t even get close to delivering on the vision. This is a teenytiny application that presents a whiteboard for running retrospectives. Anyone can add entries in any of the three sections. That’s about it.

data Section = Good | Bad | Confusing
  deriving (Show, Read, Eq)
data Entry = Entry String
  deriving (Show, Eq)

data Entity = EntitySection Section | EntityEntry Entry
  deriving (Show, Eq)

data Hap = NewEntry Section String | ShowRetro

defaultHap = ShowRetro

actionsForHap ShowRetro = []
actionsForHap (NewEntry section text) = [AddPropertyOn (EntitySection section) (EntityEntry $ Entry text)]

entityLand =
        haveEntity "Section" $
        haveEntity "Entry" $
        haveEntity "Text" $
        "Section" `relates_to_multiple` "Entry" $
        "Entry" `relates_to_one` "Text" $
        emptyEntityLand

possibleSectionStrings = ["Good", "Bad", "Confusing"]

newEntryHapperSection = HapperField { fieldName = "section", validators = [EnsureIsOneOf possibleSectionStrings] }
newEntryHapperText = HapperField { fieldName = "text", validators = [EnsureIsNotEmpty] }
newEntryHapper = Happer "newEntry" [newEntryHapperSection, newEntryHapperText] (\getter -> NewEntry (read $ getter (newEntryHapperSection)) (
getter newEntryHapperText))

allHappers = [newEntryHapper]

retro_entry entry = Tag "span" [] $ ValueFuncCall showEntryText [entry]

retro_entries section = Tag "div" [] $ Values [
                                      (Tag "h2" [] $ ValueFuncCall showValueAsString [section]),
                                      (ValueFuncCall (FuncMap retro_entry) [ValueFuncCall ("Section" `follow_to` "Entry") [section]])]

retro = Tag "div" [] $ Values [retro_entries(ValueEntity $ EntitySection Good),
                            retro_entries(ValueEntity $ EntitySection Bad),
                            retro_entries(ValueEntity $ EntitySection Confusing)]

sample_data = build_data entityLand
                     [
                      add_entity (EntitySection Good) (EntityEntry $ Entry "It's Okay"),
                      add_entity (EntitySection Bad) (EntityEntry $ Entry "It's Ugly")
                      ]

retro_page = Values [(Tag "h1" [] $ Text "Retro"),
                     retro,
                     HapperReceiver newEntryHapper [
                       HapperInput newEntryHapperSection (Dropdown possibleSectionStrings),
                       HapperInput newEntryHapperText Textfield
                     ]]

wrap_in_html body = Tag "html" [] $ Values [
                                    (Tag "head" [] $ Tag "title" [] $ Text "Waltz App"),
                                    (Tag "body" [] body)]

What’s interesting about this?

The definition of the markup for the page (retro_entries is a particularly fine example) makes it clear when a portion of the markup is a determined by some changing data in the system. This means that (a future version of) the framework can perform ajax-y incremental updates on the client side, without explicit dom-twiddling code being written for the application. When the set of entries for a section grows, the framework can take care of shoving that new node in there, so you get swish updates without having to maintain the synchronisation of JS and the markup.

Future work: make a more succinct language, probably based on haml or hamlet, that has the same properties.

A “Hap” (is in, what’s the haps?) is a user’s interest in doing something. A Happer is how a web request makes a Hap. They may ultimately turn out to be the same thing, but for now they’re distinct. Happers have a strong but lightweight link from their definition to their use, are related to URLs, and can be nested.

When you are requesting user input, you’re doing so to create a hap. So, you specify what Happer it is that you want. This is a strong link, so you don’t change one character and find that the application still runs but it’s not hooked together anymore (rails, ahem). But it’s a lightweight link, too: you’re just specifying what it is that you want to make, then laying out fields to do so.

A Happer is related to URLs. If you make a newEntry happer, than if that’s the only happer to be submitted by a form, the framework could indicate which happer is in use by POSTing to /entries. If there’s two happers to be submitted by a form, but both of them are related to updating an entry, then it might use a PUT to /entry/$id with hidden fields to indicate which inputs go with which happer.

Future work: Make the happers use URLs. Allow happers to be arbitrarily complicated (in order to allow the inevitable tables of inputs). Make it possible to use multiple happers in a single page. Work out if happers and haps are actually any different.

Once you’ve gone from a happer to a hap, you can get the actions for the hap. These actions are where any side-effects (writes to the database, etc) happen, and there’s two motivations for this; one practical and one philosophical. The practical motivation is that these side-effects are the things that are going to cause DOM modifications; this structure allows you to run happers -> haps -> actions both on the client, to update the dom, and on the server, to update the database and other users. The philosophical angle is that all existing Haskell web frameworks that I know of throw you into the IO monad in every request, so that any request can do any thing. This structure of haps creating actions which cause side effects should increase the amount of your application that lives outside the IO monad and therefore has pure code’s ease of reasoning.

Future work for actions are to make more sophisticated actions, to make actions that hit persistent databases, action processing to update the client ajaxily, and actions that cause other actions.

Feedback? There’s actual working code at https://github.com/imccoy/waltz-incremental. I haven’t done any of the really ambitious stuff yet, but it’s still seeming possible.

(Other ways of thinking about this:

  • how would we be building web apps if OOP and Relational Databases had never happened.
  • the twisted love-child of event sourcing, MVC and FRP
  • what would a web framework that really optimizes for pure functions look like?

or, perhaps, the delusions of a madman)

From → Uncategorized

4 Comments
  1. Look at Seaside, the Smalltalk framework. Web apps with no http abstractions. There’s an introductory book in the office. You say things like:

    aButton onClick: [ do this ]

  2. I think it’s an interesting approach – and I’ve been meaning to play with it for ages – but I don’t like the amount of server-side state it involves. I’m hoping to wind up in a place that is abstracted a little more from HTTP, but has the same shared-nothing thang going on.

    But yeah – I should definitely do some seaside tire-kicking.

  3. I had a look at Opa, the language you mentioned, after it was released on Slashdot. It does indeed seem promising, but one thing seems to be a complete deal-breaker: they’re licensing it under the AGPL — the compiler and its runtime.

    In their own words (http://blog.opalang.org/2011/08/opa-license-contributions.html) “‘if I’m using AGPL Opa to develop an app does it need to be AGPL, too? … yes, it does.” But they also offer a paid-for license to allow you to write non-AGPL apps in Opa.

    This to me is suicide: not even Richard Stallman was conceited to try something like that (GCC is GPL, but glibc is LGPL, so you can write proprietary or non-GPL apps in it). Who ever heard of a programming language that forces you to use a specific license for your own code unless you pay for it?

Trackbacks & Pingbacks

  1. Avi Bryant’s Presentations « Redtexture

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.