Composing Heterogeneous Elements

Composing HTML elements is straightforward in Shpadoinkle when the elements all have the same model type; nesting them in each other works automatically.

For an element to be a child of another element, they must have the same model type. This means that all elements in the tree under an Html m Model (children, children of children, etc.) must have the type Html m Model. Therefore, before an element can become the child of a parent, it must be transformed to have a matching model type, if the model types do not already match.

Changing the model type of an HTML element can be accomplished using the MC functions which make use of the MapContinuations typeclass. This typeclass is instantiated by types where the values can be changed by functions which change continuations.

Continuations are a concept in Shpadoinkle’s event handling system. Each event handler causes a continuation to be run. That continuation may perform I/O actions and/or update the model. If a continuation updates the model, then it does so atomically, which means that it is safe to have multiple continuations running concurrently. To learn more about continuations, see the Shpadoinkle-continuations package.

First consider the case where you have an element x :: Html m a and you need to change its type to Html m (a,b) so that the event handlers act on the left side of the tuple. In this case you should use the leftMC function: leftMC x :: Html m (a,b).

Similarly, if you have an element x :: Html m b and you need to change its type Html m (a,b), then you should use the rightMC function: rightMC x :: Html (a,b).

If you have an element x :: Html m () then you can freely vary its model type using voidC: voidC x :: Html m a. The event handlers will have the same side effects and they will not touch the model.

You can change the model type from anything to anything using the forgetMC function, which will strip out all event handlers, replacing them with no-ops. For any types a, b and monads m, n, if x :: Html m a then forgetMC x :: Html n b.

Given a lens from a type b to a type a, consisting of a function set :: (a -> b -> b) and a function get :: b -> a, you can change an element x :: Html m a to an element liftMC set get x :: Html m b using the liftMC function.

You can change an element x :: Html m a to an element maybeMC x :: Html m (Maybe a), where the event handlers are lifted into the Maybe monad using fmap (so they will have the same effect when the model is Just and they will not affect the model when the model is Nothing).

Given an isomorphism, you can change the type of an element from one side of the isomorphism to the other side. An isomorphism from a type a to a type b consists of a function f :: a -> b and a function g :: b -> a such that f . g = id and g . f = id. You can change an element x :: Html m a in the following way: pimap (piiso f g) x :: Html m b.

There is one more way of changing the model type of an element which you are likely to need, which is lifting into coproducts. Let’s say that you have two views in your app, represented by models of types a and b respectively. Then you can have an overall model of type Either a b. How can you combine elements x :: Html m a and y :: Html m b into the same structure?

In this scenario it will make more sense to think of view functions as opposed to elements. By a view function, I mean a function which goes from the value of the model to an element. So let’s suppose we have view functions v :: a -> Html m a and u :: b -> Html m b. Then we can combine them as eitherH v u :: Either a b -> Html m (Either a b). This combined element will show the view v when the model is a Left and the view u when the model is a Right.

If you wish to have multiple views combined in this way but you do not want your model type to be an Either type, then you can combine this coproduct pattern with an isomorphism. Suppose that f :: Either a b -> Model and g :: Model -> Either a b constitute an isomorphism, and v and u are as above. Then: pimap (piiso f g) . eitherH v u . g :: Model -> Html m Model.

For examples of many of these patterns, see the Servant CRUD example.