Elmish's program.mkSimple versus program.mkProgram

Over a year ago I started working with F#, Microsoft's functional alternative to C#, for the first time. Like most OOP developers jumping into a functional language, I really struggled with the language in the beginning. Not only was the tooling sort of subpar compared to C#, but just approaching problems in a functional mindset was a hard thing to wrap my head around. Coming from C# and TypeScript, things like immutability, pipes, discriminated unions, "railway-oriented programming", and (strangely the one I had the most trouble with) no parentheses, were real mental blocks for me.

Once I got over those blocks, though, and started approaching my F# code in a functional manner, everything became crystal clear. I had found a language that I absolutely love working with, and I'm lucky enough that, being a freelancer, I have the freedom to choose it in almost every new project or engagement. The only other language that I enjoy using as much as F# is Dart, and even then there are things from F# that I'd like to see implemented in that language.

(By the way you can hire me to build your Shopify apps. Just shoot me an email at joshua@nozzlegear.com 😉)

To add the cherry on top of the F# sundae, you can use the language to build full-stack F# web applications using Fable. This tool is downright magical, letting you use familiar frameworks like Facebook's React to build your frontend application, but letting you throw out JavaScript and use the strongly-typed F# instead. Using the same language on the frontend and the server means you get extra niceties like shared code, and my favorite, shared types:

module Domain.Requests 

// The frontend sends this type to the server, and the server receives it. 
// Both parts of the application agree on what the data looks like because they both use this type.
type LoginRequest = {
    username: string 
    password: string
}

But, digressing, since evangalizing my favorite language isn't the point of this post, the Fable community has largely centered itself around a framework called Elmish for building web apps. As it describes itself, Elmish is a set of abstractions around the "model view update" architecture, an architecture that was popularized by the Elm language and frameworks like Redux. This architecture fits in very well with React, which Elmish provides further abstractions for with Elmish.React.

While I had tried to embrace as many functional programming paradigms as quickly as possible when I started learning F# and then diving into Fable, I have avoided Elmish until very recently. Instead I was content with the Mobx bindings I had created, though I did give Elmish a shot several times since it's every in the F# community. But whenver I tried my hand at a sample Elmish project, I would end up abandoning it quickly afterwards.

The problem: I didn't understand Elmish's Program.mkProgram, and couldn't see why anybody would opt to use that instead of Program.mkSimple. After all, "simple" was, well, simpler! I didn't need to return "commands" from my init and update functions, so why would I use that at all?

If you're not familiar with Elmish, here's a super quick overview of how it works:

  1. You define an application-wide model type, and then initialize it in an init() function.
  2. You define a discriminated union of "messages" that your app will dispatch throughout normal usage.
  3. You define an update function that receives both your model type and one of the messages, and this function then outputs the updated model.
  4. You define a view function that receives your model and another dispatch function that you use to dispatch your messages.

It's a super simple and, more importantly, very easy-to-reason-about architecture. If you're an experienced frontend developer, it probably sounds very similar to things like Redux or Mobx's state tree: render the view, dispatch an action, update the model, render the view, etc.

However, experienced Elmish developers have probably already noted, this is the "simple" version of Elmish. If you use the "program" version, instead of returning just the updated model from the update function, you must return a tuple of the model and something that Elmish calls a "command". Commands are sent back to Elmish after the update function runs, and Elmish looks at them and either reruns the update using the new commands, or runs the execute function if there was no command to run.

These commands are, among other things, a method for dispatching or even automating further changes to your model without any user input. Here's a very small version of Elmish's program, one that takes a number and continuously increments it and decrements it between 1 and 3, then logs the value to the console:

module App.Example.Program
open Elmish

type Model = {
    counter: int 
}

type Message =
    | Increment 
    | Decrement 

let init () = 
    // Initialize the model. This is a full elmish "program", so we must also return a command. In this case, we begin by dispatching an Increment message.
    { counter = 0 }, Cmd.ofMsg Increment

let update message model = 
    // Act on the Message that was dispatched and update the model
    match message with 
    | Increment -> 
        let updatedModel = { model with counter = model.counter + 1 }

        // Decide which command to dispatch next: increment if the model is less than 3, else decrement
        let nextCommand = 
            if model.counter < 3 
            then Cmd.ofMsg Increment 
            else Cmd.ofMsg Decrement

        // This is a full elmish "program", so we must return a tuple of the updated model and the next command
        updatedModel, nextCommand

    | Decrement -> 
        let updatedModel = { model with counter = model.counter + 1 }

        // Again, decide which command to dispatch next.
        let nextCommand = 
            if model.counter > 0 
            then Cmd.ofMsg Decrement 
            else Cmd.ofMsg Increment

        updatedModel, nextCommand

let view model dispatch = printfn "Counter is %i" model.counter

// Create and start the program
Program.mkProgram init update view 
|> Program.run

If you were to compile and run this in your browser, you'd see a stream of "Counter is 1", "Counter is 2", "Counter is 3", "Counter is 2", and so on in your browser's console.

Again, it's very small "app", but it showcases a kind of "event loop" (if you want to call it such) that Elmish enables you to build using its commands. And speaking of loops, this program is actually stuck in one, endlessly incrementing and decrementing the value. At no point does the update function return an empty command (Cmd.none), meaning the program will never stop running the update function.

However, if you were to build this same program using Elmish's Program.mkSimple you... wouldn't be able to do it at all because Program.mkSimple only returns the model from its update function, not any commands. Not only that, but your init function can't return a command either, and therefore can't even start the loop (you'd need to wait for user interaction and call the dispatch function in your view).

It turns out that Elmish's program.mkSimple is based off of something similar from the Elm language (which has actually been renamed to beginnerProgram to make its intent more clear). Like Elm, Elmish's simple program is really only for beginners to help ease them into the framework by hiding any concept of commands. In fact, if you look at the source code, Elmish's Program.mkSimple just wraps your update function and tacks on Cmd.none for every single run.

Let's look at a more practical example, though. I've built a lot of login pages lately, so I've got it down to a near science. For a typical login page that takes a username plus password and sends them to the server for validation, there's about five events you need to dispatch:

  1. Changing the username value.
  2. Changing the password value.
  3. Beginning the login process (user presses the "login" button).
  4. Successfully logging in.
  5. Failure to login and receiving an error message.

Let's use the Program.mkSimple version of Elmish to build this login form, and then we'll look at how it could be implemented with the full "program":

module App.Example.Login.Simple

type Model = { 
    username: string 
    password: string 
    loading: bool
    error: string option 
}

type Message = 
    | UpdateUsername of string 
    | UpdatePassword of string 
    | BeginLoading 
    | LoginSuccess 
    | LoginFailure of string 

let init() = 
    // Initialize the default model state
    { username = ""
      password = ""
      loading = false 
      error = None }

let update message model = 
    // Act on the Message that was dispatched and update the model
    match message with 
    | UpdateUsername username -> 
        { model with username = username }
    
    | UpdatePassword password -> 
        { model with password = password }

    | BeginLoading -> 
        { model with loading = true; error = None }

    | LoginSuccess -> 
        { model with loading = false; error = None }

    | LoginFailure error -> 
        { model with loading = false; error = Some error }

open Fable.PowerPack
open Fable.Import

let someFunctionThatSendsTheLoginDataToTheServer (_:string) (_:string) = 
    Promise.create(fun resolve reject -> 
        Fable.Import.Browser.window.setTimeout(resolve, 3000, [])
        |> ignore
    )

let login model dispatch (event: React.MouseEvent) = 
    // Prevent submitting the form and refreshing the page
    event.preventDefault()

    if model.loading then 
        ()
    else 
        // Dispatch the loading event to show visual feedback to the user 
        dispatch BeginLoading

        // Begin the login attempt. This function (not shown) returns a promise.
        someFunctionThatSendsTheLoginDataToTheServer model.username model.password
        |> Promise.result
        |> Promise.iter(fun result ->
            match result with 
            | Ok _ -> 
                // Successfully logged in. Dispatch the success event. This is where you'd route the user to a dashboard or something.
                dispatch LoginSuccess
            | Error (exn: System.Exception) -> 
                // Error was thrown. Dispatch the login failure event, passing the error message to be displayed in the form. 
                LoginFailure exn.Message 
                |> dispatch)

open Fable.Core.JsInterop
open Elmish
module R = Fable.Helpers.React 
module P = R.Props 

let getEventValue (event: React.FormEvent) = 
    event.currentTarget?value
    |> unbox<string>    

let view model dispatch = 
    R.form [] [
        R.div [P.ClassName "control-group"] [
            R.label [P.HtmlFor "username"] [
                R.str "Username"
            ]
            R.input [
                P.Name "username" 
                P.Type "text" 
                P.Value model.username 
                P.OnChange (getEventValue >> UpdateUsername >> dispatch)
            ]
        ]
        R.div [P.ClassName "control-group"] [
            R.label [P.HtmlFor "password"] [
                R.str "Password"
            ]
            R.input [
                P.Name "password"
                P.Type "password" 
                P.Value model.password 
                P.OnChange (getEventValue >> UpdatePassword >> dispatch)
            ]
        ]

        model.error 
        |> Option.map (fun e -> R.p [] [R.str e])   
        |> R.opt 

        (match model.loading with 
        | true -> Some (R.progress [] [])
        | false -> None
        |> Option.map (fun child -> R.div [] [child])
        |> R.opt)

        // Note that the login function must receive both the view's model and the view's dispatch function so it can dispatch different events.
        // This is not the case with the full "program", it's only the case with the "simple" version of Elmish.
        R.button [
            P.ClassName "btn btn-primary" 
            P.Type "submit" 
            P.OnClick (login model dispatch >> ignore)] [
            R.str (if model.loading then "Logging in" else "Login")
        ]
    ]

open Elmish.React

Program.mkSimple init update view 
|> Program.withReact "elmish-app"
|> Program.run

This "app" uses Fable's React to create a small form with username and password boxes, a submit button, plus an error message when there is an error message.

Elmish's program.mkSimple

This login form is more complicated than our infinite counter example, but, as with all things Elmish, it should be pretty easy to understand. One of my favorite things about Elmish is the update function. Looking at this example's version of that function, it's very clear how the model is changing with each message dispatched.

However, one of the big differences between the beginner version and the "program" version of Elmish apps is displayed in this example's login function. Because the update function of a simple Elmish app can't dispatch further commands, the login function has to handle:

  1. Checking if the app is already attempting to login.
  2. Dispatching the LoginSuccess message.
  3. Dispatching the LoginFailure message.
  4. Actually executing the login attempt.

To do that, the function needs to be passed not only the username and password, but it also needs model.loading and the dispatch function itself. That's not that big of a deal in a small app like this, but the dispatching and props passing can get very bad when you have a large app with nested components and "action" functions like login spread throughout. This sort of trickle-down proponomics is one of the biggest reasons people switch to using tools like Redux and Mobx. It just becomes too much to manage.

Luckily for beginner Elmish users, there's a light at the end of this tunnel. Let's take a look at the same login form, but built with program.mkProgram; the kind that dispatches additional commands from the update function.

type Model = { 
    username: string 
    password: string 
    loading: bool
    error: string option 
}

type Message = 
    | UpdateUsername of string 
    | UpdatePassword of string 
    | BeginLoading 
    | LoginSuccess 
    | LoginFailure of string 

open Elmish

let init() = 
    // Initialize the default model state, and don't dispatch any further commands after initialization.
    { username = ""
      password = ""
      loading = false 
      error = None }, Cmd.none

open Fable.PowerPack
open Fable.Import

let someFunctionThatSendsTheLoginDataToTheServer (_:string) (_:string) = 
    Promise.create(fun resolve reject -> 
        Fable.Import.Browser.window.setTimeout(resolve, 3000, [])
        |> ignore
    )

let login username password (): JS.Promise<unit> = 
    // The login function no longer needs to worry about checking if we're already loading, and doesn't need to dispatch anything.
    // That's all handled by the update function now.
    someFunctionThatSendsTheLoginDataToTheServer username password

let update message model = 
    // Act on the Message that was dispatched and update the model
    match message with 
    | UpdateUsername username -> 
        // No further command, so just update the model
        { model with username = username }, Cmd.none
    
    | UpdatePassword password -> 
        { model with password = password }, Cmd.none

    | LoginSuccess -> 
        // This is where you would route the user to a dashboard page or something like that, but in this example we won't do anything further
        { model with loading = false; error = None }, Cmd.none

    | LoginFailure error -> 
        // Display the error to the user, and wait until the press the login button again
        { model with loading = false; error = Some error }, Cmd.none

    | BeginLoading -> 
        // Check if the user is already attempting a login before running the login function 
        if model.loading then 
            // Just return the model without any changes and no further commands
            model, Cmd.none
        else         
            // Update the model
            let updatedModel = { model with loading = true; error = None }

            // Prepare the login promise. Elmish will pass the final () arg to start the promise.
            let loginPromise =  login model.username model.password 

            // Called by Elmish when the promise succeeds. Dispatches the LoginSuccess message. 
            let loggedIn () = LoginSuccess

            // Called by Elmish when the promise fails. Dispatches the LoginFailure message.
            let loginFailed (exn: System.Exception) = LoginFailure exn.Message

            // Return the updated model, *and a command promise that executes and waits for the login function to finish.*
            updatedModel, Cmd.ofPromise loginPromise () loggedIn loginFailed

open Fable.Core.JsInterop
module R = Fable.Helpers.React 
module P = R.Props 

let getEventValue (event: React.FormEvent) = 
    event.currentTarget?value
    |> unbox<string>    

let view model dispatch = 
    R.form [] [
        R.div [P.ClassName "form-group"] [
            R.label [P.HtmlFor "username"] [
                R.str "Username"
            ]
            R.input [
                P.ClassName "form-control"
                P.Name "username" 
                P.Type "text" 
                P.Value model.username 
                P.OnChange (getEventValue >> UpdateUsername >> dispatch)
            ]
        ]
        R.div [P.ClassName "form-group"] [
            R.label [P.HtmlFor "password"] [
                R.str "Password"
            ]
            R.input [
                P.ClassName "form-control"
                P.Name "password"
                P.Type "password" 
                P.Value model.password 
                P.OnChange (getEventValue >> UpdatePassword >> dispatch)
            ]
        ]

        model.error 
        |> Option.map (fun e -> R.p [] [R.str e])   
        |> R.opt 

        (match model.loading with 
        | true -> Some (R.progress [] [])
        | false -> None
        |> Option.map (fun child -> R.div [] [child])
        |> R.opt)

        // The login function is no longer called directly by the view. Instead we just dispatch the BeginLoading message and let the update function handle it.
        R.button [
            P.ClassName "btn btn-primary" 
            P.Type "submit" 
            P.OnClick (fun e -> e.preventDefault(); dispatch BeginLoading)] [
            R.str (if model.loading then "Logging in" else "Login")
        ]
    ]

open Elmish.React
open Elmish.Debug
open Elmish.HMR

Program.mkProgram init update view 
#if DEBUG
|> Program.withDebugger
|> Program.withHMR
#endif
|> Program.withReact "elmish-app"
|> Program.run

There we go, the same login page, but this time executing the login function is no longer done directly by the view; instead the view just dispatches the BeginLoading message and lets the update function run it.

Now this itself looks a little bit more complicated, with the update and init functions needing to return commands, but keep in mind that it's perfectly reasonable to return Cmd.none a majority of the time. Remember, the source code for Elmish's program.mkSimple just wraps all of your update runs and returns your new model with Cmd.none.

Taking full advantage of commands in Elmish enables things like:

  1. Better organization of code, where "action" functions like login only deal with their actions.
  2. Functions that are more focused on what they need to do instead of checking that they should do it in the first place.
  3. Less prop/dispatch passing overall.

And yes, you absolutely can still call the login function directly from the view, and you can still pass loading state and dispatch to it in the same way we did before.


Learn how to build rock solid Shopify apps with C# and ASP.NET!

Did you enjoy this article? I wrote a premium course for C# and ASP.NET developers, and it's all about building rock-solid Shopify apps from day one.

Enter your email here and I'll send you a free sample from The Shopify Development Handbook. It'll help you get started with integrating your users' Shopify stores and charging them with the Shopify billing API.

We won't send you spam. Unsubscribe at any time.