Formlets meet Hamlet

June 4, 2010

GravatarBy Michael Snoyman

I just scratched a whole bunch of itches at once. I've been wanting to integrate formlets with Yesod, I've wanted Django-style generics, and somehow combining form generation with model definitions.

Well, the generics could be a little more generic, and I'm not sold 100% on formlets, but there's some definite progress. I also want to discuss some future directions for Hamlet.

Type-based form definitions

A major driving force behind the persistent package is having the types define a lot of characteristics. For example: in Django, you would define a string field and then specify that you want it to be non-empty (non-null? I don't remember their terminology). I believe the equivalent functionality for persistent should be through types. So, we would have a newtype NonEmptyString = NonEmptyString String. Then, the Persistable instance can do any validation that it wants.

By having all of these specifications in the type, we can easily extend this to deal with fields. Let's have a typeclass:

class Fieldable a where
    fieldable :: String -> Formlet (Hamlet url IO ()) m a

Then when we define the NonEmptyString instance, we have it verify that strings aren't empty. (If you're wondering, that first String argument is simply for defining the label of the field.) Then we'll have another class:

class Formable a where
    formable :: Formlet (Hamlet url IO ()) m a

And we can automatically generate Formable instances (via template haskell) for any datatype that is fully made up of Fieldable instances.

Taking it a step further: if we also have a Persist instance for this datatype, then we can automatically create, read, update and delete these types. By having a Fieldable instance, we can ask the user for input on the data. And voila! we have a generic CRUD system.

The code in question is available on github. I'd like to eventually turn CRUD into its own subsite, but I believe the subsite system will need a little more work to make this happen. Looks like a good project for Yesod 0.3.0.

Access Control Lists

In the project I'm contracting on right now, I'm going to need to implement permissions management. I'm considering a very simple extension to yesod to allow this:

data PermissionResult = Permitted | RequiredAuthentication | NotAuthorized String

-- in the Yesod typeclass
authenticateRoute :: y -> Maybe (Routes y)
authenticateRoute _ = Nothing
isPermitted :: Routes y -> Handler y PermissionResult
isPermitted _ = return Permitted

What's really cool about this approach is:

  • You can completely separate the authorization logic from the rest of the application.
  • You can't accidently forget to specify the settings for a route.
  • Before showing the user a link, you can call isPermitted yourself and decide if they should see it.

Hamlet modifications

The first one I'm considering is migrating away from the text package. I think it's massively slowing down hamlet (based on my bigtable benchmarks). Instead, I think Hamlet will work on UTF8-encoded bytestrings. Additionally, we can do a lot of the UTF8-encoding of strings at compile-time instead of runtime, hopefully speeding things up more. And if someone really wants a different encoding, they can re-encode the bytestrings.

I'm also considering removing interleaved IO support. There's three main reasons for this:

  • Most reasons for interleaved IO are not a good idea anyway. For example, it's probably best not to interleave database calls, as that will hog a database connection longer than is necessary.
  • It adds a lot of complexity to the system. Based on some profiling, I believe the enumerators in particular are giving serious slow-downs versus pure, lazy lists.
  • The Hamlet datatype could be greatly simplified from a Monad to... well, maybe all the way to (url -> String) -> Lazy.ByteString. I'd probably keep a newtype wrapper, and allow some instances like Monoid.

Please let me know if anyone is relying on any of the Hamlet features I'm considering removing.


comments powered by Disqus