Tuesday, December 11, 2018

Porting a simple toy app to Elmish

In the past I developed a toy app/kata (Animalquiz) for learning purposes. First version was Java, then I ported it to F#.

This post is about making the  F# version run under Elmish.

Roughly speaking, an Emish based application consists in a set of possible messages, a model to represent the current state of the application, and an update function that given a message and a model, returns a new model (and a list of other messages,  but I can skip this part and assume that this list will always be returned as empty, that is ok for my purposes).

My original AnimalQuiz was console based (also with a gtk# interface), and it was based on a sort of model-view-update patten, even if I wasn’t aware of it.

So I expect there will not be so much work to do to put it under Elmish.


First step is creating a project based on the Elmish-Fable template.
According to the documentation that you can find in https://devhub.io/repos/kunjee17-awesome-fable
to install the Fable-Elmish-React template you need the following command:
dotnet new -i "Fable.Template.Elmish.React::*"

To create a new project:
dotnet new fable-elmish-react -n awesome


The template created contains three sample (sub)apps: 
  • The “Home” app which shows us a simple textbook where the content will be coped in a text area (using “onchange” property).
  • The classical counter app called “Counter sample”. 
  • The “About” which just shows some static text (which is just static text).

I want to add the AnimalQuiz as a fourth application, so what I'll do is:
  1. make a clone of one of them, and call the cloned app “AnimalQuiz”, 
  2. make the cloned app accessible from the outer menu.
  3. modify the cloned app, to  behave as the AnimalQuiz app.

The app I clone is the “Home” app, so I just copy the three file of it in a new folder called AnimalQuiz. In each module declaration I'll substitute "AnimalQuiz" to "Home".

I have to add the following rows in the in the project file awesome.fsproj, in that order,  to make sure they will be compiled:
<Compile Include="AnimalQuiz/Types.fs" />
<Compile Include="AnimalQuiz/View.fs" />
<Compile Include="AnimalQuiz/State.fs" />



     
Now the step 2: I want to make extra app reachable by the outer menu, so as follows there are the files that I have to change at it, and how I have to change them:
module App.Types: the message and the model will reference the message and the models of the sub apps:



Module Global (file Global.fs): I extend the Page discriminated union, and the toHash function:




Then I need to change the App.State module (only the row with some animalQuiz string are mine):



Last thing to do is modifying App.View module in two parts.
One is the definition of the menu:


Another one is the definition of the pageHtml internal to the root definition:



This is enough to make the new app visible and selectable from the outer menu.

Now is time to take a look to the actual app, that, until now, is just a copy of Home, but I'm going to change everything there.

The model is the following:




Those data are useful to manage an interaction between the user and the engine, but I am not going to explain all of them, with the exception of the CurrentState, which is of the type State, that describe the different states of the interaction with the user:




What follows are the messages:



The model include also a history of messages. The history of messages is useful because I can do any "replay n steps from the beginning" by applying n times the messages of the history from the initial state using the fold function.

For instance there is an "undo" that applyes the list of the messages from the initial state a nuber of times given by the length of the history minus one:

| (Undo,_) -> 
     let msgHistoryTruncated = model.MessageHistory |> List.take (List.length model.MessageHistory - 1)
     msgHistoryTruncated |> List.fold (fun (acc,_) x -> update x  acc   )  (initState,[])

     
repo is here https://github.com/tonyx/animalquiz-elmish



No comments: