Skip to content

Latest commit

 

History

History
127 lines (77 loc) · 11.7 KB

mvc.md

File metadata and controls

127 lines (77 loc) · 11.7 KB

The Model-View-Controller Pattern and Functional Reactive Programming

by Heinrich Apfelmus, December 2015. CC-BY-SA

Table of Contents

Introduction

A GUI library like threepenny-gui library provides you with all the basic UI elements, but the real challenge is to combine them in new ways to create a simple and pleasant user experience.

For example, a basic text input widget allows the user to input arbitrary text into your program. But often, we want the user to adhere to a certain format. For instance, he may only enter a valid email address and should be provided with visual feedback in case he enters an invalid email address. Such a "validating input widget" would be very useful in many contexts, and deserves to be cast into a library module.

This guide sets forth a few principles for widget implementation, that is how to program widgets (as opposed to their visual design). In particular, we will look at the the Model-View Controller pattern and how to combine it with functional reactive programming (FRP)

Concepts

In this section, we look at the general ideas and how they combine.

Model-View-Controller

The traditional paradigm for writing GUI applications is the Model-View-Controller (MVC) pattern, and this pattern continues to be very useful when combined with functional reactive programming (FRP). However, we will focus on its conceptual aspects, whereas the original description of MVC also describes implementation details that are no longer relevant to us.

Here is how the MVC paradigm works: The whole purpose of a user interface is to allow a user to manipulate data on a computer. This data is the model. For instance, the data in question may be the address of a friend that the user wants to remember. To be able to inspect the data, the user has to see a representation of the data on the computer screen; this is the view. In our example, the computer draws glyphs corresponding to the friend's address on the screen. Finally, the user not only wants to passively view the data, but also to actively change it. He does so by operating a controller. For example, he can press keys on his keyboard to change the address. The following figure illustrates these concepts:

Note that controller and view are often closely linked together; the most pleasant user experiences are those where the user can interact directly with the visual representation, where he can almost "touch" it. Widgets embody this combination of controller and view. Of course, there are widgets that consist only of view (for example a status text) or only of controller (for example a clickable button), but these should be used in supporting roles only, because they lead to a separation between "thing I see" and "thing I touch", which results in a clumsy user experience.

From an implementation point of view, the concepts of model, controller and view are relative notions, they depend on your level of abstraction. For instance, a city map with scrollbars will be perceived mainly as a view by the user, but it actually consists of a model (rectangle displayed), a controller (scrollbars) and a view (showing the partial map). In turn, the scrollbar itself consists of a model (position), a controller (responds to mous drags) and a view (position indicator). In other words, the MVC concepts can be nested arbitrarily. Moving up and down the abstraction ladder, we can easily create rich widgets by combining simple ones. The figure illustrates nesting:

Functional Reactive Programming

Having established the concepts of model, view and controller, we can now ask which programming constructs we should use to represent them.

The main point is that the data in the model may change over time, usually on request by a controller, and the view needs to be updated to reflect the new data. A traditional paradigm for updating data and dealing with such changes is the so-called event-driven programming, where you register callback functions to be called whenever a change happens. Unfortunately, it turns out that this style is rather clumsy when it comes to ensuring that updates happen in the right order and yield consistent results.

But since we are using Haskell, the programming language that is most powerful when it comes to creating new abstractions, we can look for other paradigms to represent MVC concepts. In particular, we will make use of functional reactive programming (FRP).

The core idea of FRP is to use abstract data types that already include a notion of time. There are several different variations on this theme; in Threepenny, we will use the FRP variant laid out in the reactive-banana library. It consists of two core types, the Behavior and the Event.

A Behavior is simply a value that varies in time. You can think of it as a function that maps each moment in time to the corresponding value.

type Behavior a = Time -> a

An Event is a sequence of event occurrences. Think of it as an (infinite) list of occurrences, which are pairs: the first component indicates when the occurrences happens and the second component is a value tagged to this occurrence.

type Event    a = [(Time,a)]

The following figures illustrate the meaning of these types.

In words, a Behavior is like a continuously varying value, say the position of a ball that is being thrown by a basketball player, while an Event is a sequence of occurrences, for instance keeping track of whenever said ball falls through the basket.

Several combinators allow us to make new Behaviors and Events from old ones, thus giving us a new way to program with time-dependent data. Unfortunately, a detailed explanation of FRP is out of scope for this document, see elsewhere for a more thorough introduction.

FRP in an imperative language?

The important point about Behavior and Event is that their implementations perform update notification internally. However, even if you were using a programming language that does not support FRP as actual data types, it is still very useful to use FRP as a concept for thinking about your code. For instance, in an imperative language, a Behavior roughly corresponds to a read-only variable (with built-in change notification), while an Event is an object where you can register event listeners.

In fact, using FRP is completely optional when programming with the Threepenny library. You can view the type Event as a means to register event handlers and Behavior as a read-only variable.

Whether you use FRP as actual data types or as a concept only, the key advantage of FRP is the syntactic style it encourages:

Programming in the style of functional reactive programming means to specify the dynamic behavior of a value completely at the time of declaration.

For instance, take the example of a counter: you have two buttons labelled “Up” and “Down” which can be used to increment or decrement the counter. Imperatively, you would first specify an initial value and then change it whenever a button is pressed; something like this:

counter := 0                               -- initial value
on buttonUp   = (counter := counter + 1)   -- change it later
on buttonDown = (counter := counter - 1)

The point is that at the time of declaration, only the initial value for the counter is specified; the dynamic behavior of counter is implicit in the rest of the program text. A remote part of the program may change the counter, and this "action at a distance" is not visible at the point where the counter is declared.

In contrast, FRP specifies the whole dynamic behavior at the time of declaration, like this:

counter :: Behavior Int
counter = accumulate ($) 0
            (fmap (+1) eventUp
             `union` fmap (subtract 1) eventDown)

Now, it is no longer possible for a remote part of the program to change the counter; the only way to influence the counter is to do so at declaration time. This is the key property of FRP that helps you simplify your code, and it is worth keeping in mind whenever you program with FRP.

Three Principles for Representing MVC Concepts with FRP

Having learned about FRP, we are now in a position to give a representation of the concepts of model, view and controller in terms of actual programming constructs. However, we have seen that MVC can be nested, so there cannot be a single direct mapping between concepts and data types. But we can formulate three principles which serve as a guideline for structuring your code.

Here are the three principles:

  1. The Model is represented by a continuous time-varying value, i.e. a Behavior. The point is that while Behaviors do change notification under the hood, this is transparent to the programmer. The contents of a view should only depend on the present (and past) values of the model, and not on how often the model has "changed internally". This eliminates a large source of potential errors and helps you keep your code clean.

  2. Only the user may trigger controller Events. In other words, a programmatic change to the model never leads to the widget emitting an event. On the other hand, user input will usually lead to events being triggered. In fact, it is recommended, though not strictly enforced, that user input is represented in terms of Events only. This is in line with the first principle and helps you to cleanly separate between updates that the model still needs to incorporate, and updates that the model already has incorporated.

  3. Often, there is the issue of feedback cycles: the user makes a change to the widget, which is propagated to the model, which in turn changes the view contents of the widget and may heavily interfere with the user's editing operations. The solution is to use a temporary copy: as long as the user manipulates the widget, the view contents may diverge from the model, so he is essentially manipulating a copy of the data represented by the widget, instead of the data taken from the model.

    For instance, the hand of a clock will stop following time while the user adjusts it, and will only resume ticking when the user releases the hand. The connection between the current time (model) and the clocks hands (view) is suspended while the user is manipulating the clock.

These principles can be summarized in the following illustration: