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
holeprimitive to mark the occurrences of formlets. - a
plugrun 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.
