Fable and Fable-Elmish Step-by-Step: Creating a Calculator

Introduction

So you have heard about Fable, an awesome F# to Javascript compiler with emphasis on interoperability with anything in the Javascript ecosystem starting from libraries on the client (browser, for example) or server (node) to module loaders and bundlers (like webpack).

Learning by Doing

In this tutorial we are going to learn how we use Fable by creating a simple calculator application running in the browser. This tutorial is aimed at beginners and is intended to be as self-contained as possible. I assume you are comfortable with F#.

Fable-Elmish

Fable-Elmish: A library for building user interfaces, heavily inspired by Elm and it’s clean architecture. First, I will focus on boilerplate code and the setup of a Fable-Elmish project. Then we will discuss the Elm architecture and from there we start with a simple implemention of a counter app to illustrate the concepts of Elm. After that the actual calculator app follows.

Project Structure

Our application will have the following project structure. This structure should be re-usable for similar small projects.

elmish-calc (root folder)
|
| -- build
| -- app.js (generated after compilation)
 | -- public
| -- index.html
| -- bundle.js (generated after bundling)
 | -- src (here the F# source files)
|
| -- app.fsx (our main F# script file)

| -- package.json
| -- webpack.config.js
| -- build.js

Start by creating a root folder and create a similar structure with empty files , excluding app.js and bundle.js because these will be generated.

Inside the src directory is where our F# code will be. For this application it is one fsharp script (app.fsx) but you could put multiple files there or even a fsharp project (.fsproj). For now we will keep it simple and just stick with one script file.

The build directory is where the compiled javascript will be, the output of Fable. However, compilation is not enough to run the application in the browser because the default ouput is in ES6. We let Webpack take that ouput and bundle it to a single file the browser understands: the bundle.js inside the public directory.

You might ask, “Can we change the default output of Fable so that the browser understands it directly without using Webpack?”. Yes we can, but I thought it is a good idea to let Webpack do that for us. Webpack is used frequently with Fable projects so it is worthwhile getting familiar with it. It is a very powerful tool and bundling is just one of the many things it can do. Please refere to their website to learn more.

The work flow goes like this: F# source files -> Fable -> Js source files -> Webpack -> Js bundle file . As you have already noticed, you will need quite some dependencies to get the process going. You define these dependencies inside package.json.

package.json

When working with Fable. We don’t use Nuget for our libraries and dependencies. Instead, we use Npm: a package manager for javascript. You get npm when you install Node. I assume you have both installed and ready to be used from command line. When you run npm install inside a directory that has package.json npm will download all libraries and developement dependencies such as compilers and bundlers inside a directory called node_modules . Lets have a look at our package.json

As you can see, there are two main sections inside package.json, the dependencies and devDependencies . Developement dependencies (devDependencies) are a mixture of libraries, compilers and loaders. While dependencies are javascript libraries. Now, you can run npm install and see a new directory called node_modules created where all these libraries, compilers and loaders will be.

Note that we are pulling fable-compiler version 0.7.48, so this tutorial is best applicable to that version.

webpack.config.js

This is the settings file for the bundling process. It might look quite intimidating so for now I will ask you not to think about it too much.

The important part to understand now is this:

It tells webpack to look at the file called app.js (will be generated by Fable) inside the build directory and bundle that into a directory called public with filename bundle.js .

build.js

This file is a javascript file that will call Fable compiler with pre-defined settings.

When build.js is run by node (using node build.js from the command line), it will clean the build directory. Then tell Fable to look for src/app.fsx and compile that to the build directory, effectively generating app.js which will be handed to webpack for bundling.

This part:

is where you put the fable configuration.

index.html

This is the html page that will embed the app, the div with id elmish-calc is the placeholder where the app will be mounted.

As you can see, you need no other external dependecies to pull from CDN or other services because everything we needed was inside node_modules and webpack made sure it all goes to that one bundle.js .

app.fsx

Here is where we implement the application. First we reference the dependencies:

Counter App

Before we dive in with our calculator app, I think we should start with something simple like a counter app. This will help demonstrate the ideas and concepts of the Elm Architecture.

The Elm Architecture

Fable-Elmish follows the elm achitecture for writing the apps. There are many good guides on this subject like the official one here and it is highly recommended that you take a look there. But I intended this tutorial to be self-contained so I will try to explain it myself. Here we go!

An Elm program has four main parts:

  • Model
  • Messages
  • Update function
  • View function

Model

The Model is the data you want to keep track of while the app is running. It captures the state of the app at any point in time during execution. In a counter app we would like to keep track of an integer we call Count so we define our model as follows:

Messages

Messages are objects that contain data. These data are mostly sent (dispatched) by user interaction from the app. For example when a user clicks a button or enters text in a text field, a message is then sent to the update function along with with current model. In our counter example. We want messages that are sent when the user wants to increment or decrement the counter. We define our messages like this:

Update

When a message is dispachted, the update function recieves it along with the current model. Then it computes based on this information the next model or the next state of the app. We implement it as follows:

View

This is a function that takes the current model, a dispachter function, and returns a view. In our counter, it is as simple as this:

The reason we are returing ReactElement as output has to do with the implementation of Fable-Elmish. Fable-Elmish defines the idea of a Program that executes a dispatch loop based on the Elm architecture. However, the view part of this program is not tied to a specific user interface library. It has adapters to work with and leverage different user interface libraries. In our case we are using the React implementation but other implementations can be used. Currently React and ReactNative are supported. Snabbdom support is now being implemented as per the time of writing.

Now we have all the parts we need to run the app. We use the Program module:

app.fsx

The full app.fsx for the counter app

To build the app:

npm install 
npm run build
npm run bundle
  • npm install downloads the dependencies. (run only once)
  • npm run build calls fable compiler to compile F# code.
  • npm run bundle calls webpack to bundle the output of fable coompiler into one bundle.js file.

You will repeat npm run build and npm run bundle evey time you want to re-compile your app to see your changes. You might think it is too slow and you would be right. Usually we would enable what we call Hot Reloading to watch for our changes and re-compile automatically and fast. We will not use that in this tutorial.

After that go to your index.html and you should see a simple counter like the following, demo here.

Calculator

Now we begin with our calculator. Basically, it has the same structure as with our counter application but with diffirent implementations for Model , update and view .

Calculator: Model

First we want to define a model that captures the state of the application at any point while the app is running. Usually the user has to go through a series of clicks to calculate something. For example. to calculate 9 + 2. The sequence of clicks is:

[9; +; 2; =] becomes [11]

When the user reaches the last element of the sequence, i.e. clicks on = , we evaluate the [9; +; 2] to get [11] and show that the answer is 11 to the user. Let us first define what the user can click on:

The representation of our sample sequence becomes:

[9; +; 2] => [Const 9; Plus; Const 2]

What we want to keep track of is not one Input but rather a list of Input so this is going to be our model:

Calculator: Messages

For messages we will only have one type of messages. Namely, a message that captures what the user clicked. This is very convienient when implementing our update function. When the user clicks something, a message of the clicked button is dispatched. Then the update function gets the current model (the sequence of tracked clicks) and what the user clicked. Based on this, the update function will compute the next state of the app. Diffirent scenario’s can occur, for example:

// user clicks on digit 9 as his/her first message
update (Const 9) [] => [Const 9]
// user clicks on operation as his/her first message, then do    // nothing and return the same model because you have to have a  // Const x as your first click.
update (Operation x) [] => []
// user clicks op operation after he/she had clicked on digit
update (Operation x) [Const y] => [Const y; x]
// user clicks on two digits after another, we "concat" the      // digits i.e. concatInts 2 4 => 24 
update (Const 3) [Const 4] => [Const 34]

The list goes on but hopefully now you have an idea of how the update function will be implemented. We check the message we got (what the user clicked) and what we already have (the current model) and compute what the next model will be. Our Messages type will be:

We will also have some helper functions like concatInts and solve , and a partial active pattern Operation to easily match with an operation like Plus , Minus , Div and Times .

Calculator: Update

Just as I illustrated with the scenario’s, we implement the update function by matching on what message we got and the model we already had and return a new model:

The function above contains all the logic for the calculator! It is the beauty of F# that lets us describe this logic the same way we think about it.

If you look carefully, you will see that there are missing patterns for inputting negative numbers i.e. [Minus; Const x] => [Const (-x)]. The implementation for this pattern is left as an exercise for reader.

Calculator: View

Just like with the counter, the view function for the calculator will have the signature:

view :(model: Model) (dispatcher: Messages -> unit) : ReactElement

But we will break this function in smaller helper functions and use these small functions to construct the main view function. We want to re-use the function that makes a digit and the the function the makes an operation button. These are implemented as follows

Notice that when a helper view function wants to dispach an action, it takes the dispatcher : Messages -> unit as argument. The main view function passes the dispatcher down to it’s child view functions.

The code for the digitStyle, opButtonStyle and opString is omitted here because it is not of great importance, you can examine the code when we take a look at the whole file.

Now let’s take a look at the main view function, it should look straightforward.

and the last part to run app is not changed:

Full app.fsx for calculator:

Now recompile with

npm run build
npm run bundle

Navigate to your index.html to see the calculator. There you go, you have now made a calculator with Fable and Fable-Elmish.

Click here for the demo.

Full source code can be found here on github.

Conclusion

Using Fable, we are bringing the power of F# to the browser. With Fable-Elmish we were able to think about and write our app cleanly according to the Elm architecture. We saw a glimpse of what you can do with Webpack. I enjoyed writing this article and I hope you learned something from it and also had fun along the way.