Formlets are great but they conflate model and view. Existing approaches to separation using holes are suboptimal and untyped. Biapplicative functors allow to compose forms in a clean and type-safe fashion with full separation of model and view code.
I found myself writing GUIs in Haskell with threepenny-gui for my employer recently. This was a great opportunity to learn more about FRP and I stumbled upon an unexpected neat trick. The trick is best explained in the context of formlets.
Formlets
Formlets are a functional abstraction around HTML input forms, based on the idea of using Applicative functors there where Monads could not venture to compose the fragments, so called formlets, of a form.
In the original paper a formlet composition defines two things:
- a data validation and composition procedure, and
- a UI component.
This can result in overcrowded definitions, as seen on the date example from the paper (adapted and simplified):
data Date = Date {month, day :: Int} dateFormlet :: Formlet Date dateFormlet = tag "div" [] (Date <$> (text "Month:" *> input_int) <*> (text "Day:" *> input_int <* text "\n"))
Contexts
The paper did highlight this issue and proposed the use of “multi-holed contexts” to separate model and UI code, adding a “context” language for defining UIs with:
- a
hole
primitive to mark the occurrences of formlets. - a
plug
run function that takes a context expression and a formlet and fills in the holes with the formlet components to produce a UI.
The date example with contexts looks like follows, where XML nodes have the obvious Monoid
instance:
dateFormlet = plug -- UI (tag "div" [] (text "Month:" <> hole <> text "Day:" <> hole <> text "\n") -- model (Date <$> inputInt <*> inputInt))
The context language made use of a parameterized applicative functor to keep count of the number of holes, but the holes themselves were untyped, so the typechecker’s help was limited. Moreover, there was no binding between a hole and its formlet, so plug
would simply fill the holes in order. The only guarantee being that all the holes in the UI template get filled.
Digestive functors
Modern Haskell implementations of formlets like the digestive-functors package do provide this separation, but eschew the complex hole counting in favour of an untyped string-based addressing scheme.
The digestive-functors tutorial section on Views
may slightly disturb a seasoned Haskell programmer.
Biapplicative functors
Biapplicative functors are bifunctors which support applicative composition on both arguments. The bifunctors package contains the following Haskell encoding of this abstraction:
class Bifunctor p => Biapplicative p where bipure :: a -> b -> p a b (<<*>>) :: p (a -> b) (c -> d) -> p a c -> p b d
The practical difference w.r.t. standard applicative functors is that bipure
takes two arguments and performs a parallel composition. For a simple example, let’s use the Biapplicative
instance for tuples:
> bipure (++) (+) <<*>> ("123", 123) <<*>> ("456", 456) ("123456",579)
Biapplicative formlets
A Formlet is essentially a tuple of HTML nodes defining a Form, and a callback that produces a value. The original Formlet definition was parametric only on the return type of the callback, i.e. the type of the value produced after a successful interaction with the user. The key insight is to reveal the biapplicative structure by making the Formlet type parametric on the UI type as well.
data Formlet ui a data HTML instance Biapplicative Formlet -- ‘inputInt’ is a Formlet with an HTML UI that produces an Int value inputInt :: Formlet HTML Int
In order to compose biapplicative formlets, bipure
expects a function to compose the UI values and a function to compose the return values. We could simply use HTML primitives to compose the UI values, but another option is to use a datatype constructor to bind the UI components. Therefore we define a new datatype DateForm
and a function renderDateForm
:
-- | A form to edit values of type 'Date' data DateForm = DateForm {day, month :: Input Int} -- | The logic side dateFormlet :: Formlet DateForm Date dateFormlet = bipure DateForm Date <<*>> inputInt <<*>> inputInt -- | The UI side renderDateForm :: DateForm -> HTML renderDateForm DateForm{..} = tag "div" [] (text "Month:" <> month <> text "Day:" <> day <> text "\n")
If, as in this case, the form type has the same shape as the value type, we can define both in the same declaration with a little type level machinery:
{-# LANGUAGE TypeFamilies, DataKinds #-} class Editable a where type EditorWidget a editor :: Formlet (EditorWidget a) a instance Editable Int where type EditorWidget Int = HTML editor = inputInt data Purpose = Model | View type family Field (purpose :: Purpose) a where Field 'Model a = a Field 'View a = EditorWidget a
With this machinery in place we can redefine Date
as a dual purpose datatype, being used both for model and view purposes. The DateForm
boilerplate definition is gone and the final version of the Date example is simply:
data Date purpose = Date { day, month :: Field purpose Int} dateFormlet :: Formlet (Date View) (Date Model) dateFormlet = bipure Date Date <<*>> editor <<*>> editor
To wrap up, biapplicative functors provide a fully typed solution to separate model logic from UI, by binding the component widgets to function arguments. This is achieved without compromising type inference or requiring fancy type extensions. Turning a few of those on, we can reuse the datatype declarations and keep the boilerplate down to a minimum.
Threepenny-editors
These days Forms are giving way to fully interactive JavaScript UIs, but undoubtedly the lessons of Formlets still apply. Threepenny-gui provides a set of FRP and HTML primitives but not much guidance on how to compose them. Reading through the lines, a design pattern for a Form-like approach to editors emerges. An editor-let is a function from an input Behavior to a tuple of an HTML node and a composable event tiding:
newtype Editor in html out = Editor (Behavior in -> (html, Tidings out))
The in
variable is contravariant in Editor
, and both html
and out
are functorial and applicative. Which means of course that Editor
must be a biapplicative profunctor, right?
The code for composing dual purpose editors is completely mechanic and can be derived via generics leading to very little else than the datatype declaration:
data PersonF (purpose :: Purpose) = Person { education :: Field purpose Education , firstName, lastName :: Field purpose String , age :: Field purpose (Maybe Int) } deriving Generic type Person = PersonF Model type PersonEditor = PersonF View instance Editable Person where type EditorWidget Person = PersonEditor editor = editorGenericBi
The UI is defined separately as desired:
instance Widget PersonEditor where getElement Person{..} = ( ("First: " ||| firstName) === ("Last: " ||| lastName) === ("Status: " ||| status) ) ||| (("Age:" ||| age) === ("Brexiteer: " ||| brexiteer) === ("Education: " ||| education)) where (|||) = horizontal (===) = vertical
A fully worked development of this approach can be found in the threepenny-editors package. Feedback and contributions are always welcome.
Previous art
In the process of writing this post I googled for “biapplicative” and “biapplicative formlets”, and it turns out that someone else already figured this trick out! The reform package for the venerable Happstack framework already used Bifunctors, albeit under a different name and for a slightly different purpose, validation instead of UI composition.