Flare is a special-purpose UI library for Purescript. The idea is to define the user interface and the logic of the program at the same time. On the one hand this approach somewhat limits the number of use cases, but on the other hand this allows for a remarkable expressiveness.

A first example

Imagine you want to build a really simple web application that calculates the product a × b for two numbers a and b which can be entered by the user. Using Flare, we define the user interface and the program logic in a single expression:

26 (int "a" 6) * (int "b" 7)

This code generates the following reactive web-interface1:

How does this work? Flare defines the function int that takes two arguments: a label and an initial value2. An expression like int "a" 6 serves two purposes. It creates the input field with the label "a" and initial value 6, and at the same time it serves as a placeholder for its current value in the surrounding expression. If the user changes one of the input fields, the whole expression is re-evaluated.

Pure functions

In functional programming languages, programs are built from small, composable pieces that can be indivually tested. Suppose we have a pure function greet, that takes a string (a name) and returns a string (a greeting with a fancy version of the name):

13 greet :: String -> String
14 greet name = "Hello, " ++ fancy name ++ "!"
15   where fancy = toUpper >>> split "" >>> joinWith "."

To turn this into a web interface where users can enter their name, we first need an appropriate Flare component that contains a string. The function string creates a simple text field from a given label and default value. Then, all we have to do is to apply our function to the value inside the Flare component. We can do so by mapping the function greet over the component:

29 map greet (string "Your name:" "Nemo")

The result is the following small web application:

The usage of map here is similar to mapping functions over lists, except that the function is not applied to every element of a list, but rather to the stream of values inside the text field (technically, this means that a Flare component is a functor).

Multiple arguments

If our pure function takes more than one argument, we can not use map to apply it to the values inside several Flare components. This is where applicative functors come into play: they allow us to lift functions of several arguments into the realm of the functor (the Flare component). As an example, consider the following function that takes two parameters, hue and radius, and returns a representation of a small colored circle:

17 coloredCircle :: Number -> Number -> Drawing
18 coloredCircle hue radius =
19   filled (fillColor (hsl hue 0.8 0.4)) (circle 50.0 50.0 radius)

Note that this is a pure function without any side effects. Again, we can easily turn this into a web interface by creating two Flare components of the appropriate type for the two parameters. Here, we will use numberSlider label min max step default. Finally, the higher order function lift2 takes our two-argument function and applies it to the current values inside the two components:

32 lift2 coloredCircle (numberSlider "Hue"    0.0 360.0 1.0 140.0)
33                     (numberSlider "Radius" 2.0  45.0 0.1  25.0)

This results in the following interface:

It should be noted that the first example in this tutorial is actually equivalent to

28 lift2 (*) (int "a" 6) (int "b" 7)

where the multiplication operator (*) is nothing but a two-argument function that is lifted to work on the values inside the input fields.

Integration with other signals

The Flare library is built on top of Purescripts Signal library which is inspired by the corresponding module in Elm. In fact, a Flare is just a Signal with a collection of input fields. In this section, we demonstrate how Flare works together with other types of Signals. As a showcase, we want to integrate time to create a small animation:

Again, we start with a pure function. It takes three parameters, the number of leaves, a boolean flag which enables or disables the shadow, and a rotation angle:

28 plot :: Int -> Boolean -> Number -> Drawing
plot nLeaves shadow phi0 = ...

Similar to before, we create a slider for the first argument. Note that we have replaced numberSlider by intSlider (type safety first). For the second argument, we use the function boolean which creates a checkbox that returns either true or false.
Finally, for the third argument we want to have a time-dependent rotation angle. We will use the animationFrame Signal which yields the current time on every redraw of the frame in the browser. We can use the Signal in our expression for the Flare UI, by using the function lift:

41 lift3 plot (intSlider "Leaves" 2 10 6)
42            (boolean "Shadow" false)
43            (lift animationFrame)

With three lines of code, we have turned our static plot function into an interactive animation.

Stateful interfaces

So far, our examples did not include any notion of state. The output was simply given as a function of the current input values. To incorporate state, Flare uses the function foldp that works analogous to the corresponding function for signals. The p in foldp stands for past and indicates that the output can now depend on past values of the input fields:

51 foldp (+) 0 (button "Increment" 0 1)

Here, button is a simple Flare component that returns the first value 0 in its default state and the second value 1 when it is pressed. This allows us to build a simple counter:

Notice how foldp uses the internal accumulator to sum the time-varying stream of zeros and ones.

HTML output

We have seen simple text outputs and canvas graphics, but often we will have HTML as a target output. Flare provides an interface to Purescripts Smolder library that offers a nice way to create HTML markup:

82 table :: Int -> Int -> H.Markup
83 table h w = H.table $ foldMap row (0 .. h)
84   where row i = H.tr $ foldMap (cell i) (0 .. w)
85         cell i j = H.td (H.text (show i ++ "," ++ show j))

We turn this pure function into a Flare UI in the usual way...

60 lift2 table (intSlider "Height" 0 9 5)
            (intSlider "Width"  0 9 5)

... to create our last example:

Read on

Additional reading material:


1 The layout can be easily changed via CSS. We have also left out some "boilerplate" code that is always the same throughout this article: a few lines of Purescript and two lines of HTML.
2 In Purescript (or Haskell), function application f(x, y) is simply written as f x y.