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 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 x :: Html (a,b).
If you have an element
x :: Html m () then you can freely vary its model type using
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
b and monads
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
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
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
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
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
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.