Property based testing is an effective way to verify the input/output relation of pure functions, leveraging predicates (properties) to declaratively describe such relation.

Model based testing extends the scope to stateful systems, whose behavior is described in terms of an abstract model, leveraging state machines.

This is an introduction aimed at providing a basic understanding of the idea. It is yet another poor man's approach which may also work in practice but, since it is deliberately simplistic, you may also want to invest some time learning about the APIs provided by libraries.

A simple example

Our system under test is the Queue class from the .NET base library. We model its state as a list and we focus on two actions only: enqueue and dequeue.

1: 
2: 
3: 
4: 
5: 
6: 
7: 
    type Sut = System.Collections.Generic.Queue<int>

    type State = list<int>

    type Action =
        | Enqueue of int
        | Dequeue

The whole point of the approach is to perform an action both on the abstract model and on the actual system, and then check that the resulting state is the same.

The system and its model

The functions fromModel and toModel define the correspondance between the actual system and its abstract model. They are easy to implement in this example but may be challenging in more realistic scenarios. Such challenges may arise from the system hiding its internal state or persisting it in a distributed environment. Most libraries for model based testing in fact take a different route (as we will see later) but for now we naively assume that:

  • fromModel puts the system under test in the given state.
  • toModel retrieves the state of the system under test.
1: 
2: 
3: 
4: 
5: 
    let fromModel (state: State) =
        Sut(state)

    let toModel (sut: Sut) =
        List.ofSeq sut

State transitions

The essence of our model is captured by the following function defining state transitions:

1: 
2: 
3: 
4: 
    let nextState (action: Action) (state: State) =
        match action with
        | Enqueue x -> List.append state [x]
        | Dequeue -> state.Tail

The effect of enqueing an item corresponds (in the abstract model) to appending it to the list representing the state of the system.

The abstract effect of a dequeue is to remove the head of the list representing the state.

The run function is the actual counterpart to the nextState function above. This time the action is executed on the system under test.

1: 
2: 
3: 
4: 
5: 
    let run (action: Action) (sut: Sut) =
        match action with
        | Enqueue x -> sut.Enqueue x
        | Dequeue -> sut.Dequeue() |> ignore
        toModel sut

Invariant and Precondition

Next we define two predicates: an invariant (that should hold in every state) and a precondition (that should hold in order to perform an action):

1: 
2: 
3: 
4: 
5: 
6: 
    let invariant (_: State) = true

    let precondition (action: Action) (state: State) =
        match (action, state) with
        | Dequeue, [] -> false
        | _ -> true

In this case the invariant always holds, we made illegal states unrepresentable! (but it was an easy win, let me suggest a nice article about this topic).

With the precondition we express that a dequeue action may not be performed on an empty queue.

Running a test

The test function initializes the system under test with the given state, then runs the given action on it and finally checks that its resulting state conforms to the model.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
    let test (state, action) =
        let check error x = if not x then failwith error

        invariant state
        |> check "Input Invariant"

        precondition action state
        |> check "Precondition"

        let sut = fromModel state

        toModel sut = state
        |> check "Initial State"

        let expected = nextState action state

        invariant expected
        |> check "Expected Invariant"

        let actual = run action sut

        actual = expected
        |> check "Final State"

Let's examine the code in more detail.

First we check that the input satisfies the invariant and the precondition.

Then we initialize the system under test, establishing the given state (and we verify that the initialization succeded by checking the state).

Next we use the model to get the state expected after the execution of the action (and we verify that this next state produced by the model still satisfies the invariant).

Then we run the action on the actual system, and we verify that, after the execution, the state of the system under test is the same as the expected one according to the model.

Random input

To run test with many random inputs we need a generator of state-action pairs satisfying invariant and precondition:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
    #r "nuget: FsCheck"

    open FsCheck

    let actionGenerator (state: State) =
        let enq = Gen.map Enqueue Arb.generate<int>
        let deq = Gen.constant Dequeue
        if state.IsEmpty
        then enq
        else Gen.oneof [enq; deq]

    let arbitraryStep =
        let stepGenerator =
            gen {
                let! state = Gen.listOf Arb.generate<int>
                let! action = actionGenerator state
                return (state, action)
            }
        Arb.fromGen stepGenerator

    Prop.forAll arbitraryStep test
    |> Check.Quick
Ok, passed 100 tests.

FsCheck state machines

As anticipated, initializing a real system to an arbitrary state may be hard. But we can explore many states executing a sequence of actions starting from a state that's easy to establish.

FsCheck state machines allow this. It requires a bit of boilerplate, but we can reuse actionGenerator, nextState and run:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
    open FsCheck.Experimental

    let machine = {
        new Machine<Sut, State>() with
            member __.Setup =
                { new Setup<Sut, State>() with
                    member __.Actual() = Sut()
                    member __.Model() = [] }
                |> Gen.constant
                |> Arb.fromGen

            member __.Next state =
                actionGenerator state
                |> Gen.map (fun action -> {
                    new Operation<Sut, State>() with
                        member __.Run state = nextState action state

                        member __.Check (sut, state) =
                            let actual = run action sut
                            state = actual
                            |@ sprintf "model = %A, actual = %A" state actual

                        override __.ToString() = sprintf "%A" action })
    }

    StateMachine.toProperty machine
    |> Check.Quick
Ok, passed 100 tests.
95% long sequences (>6 commands).
5% short sequences (between 1-6 commands).

Among the benefits of using a real library, we get shrinking and useful error messages in case of test failures. Notice also the added flexibility given by the Check method: if the state is hard to retrieve, instead of calling run and checking the whole state, we are free to focus only on the relevant part of the system.

Model View Update

Instead of discussing how effective the approach is for verification, let me point out that modeling is valuable at least for documentation: the nextState function expresses our understanding of the system.

If you're familiar with the ELM Architecture, looking at nextState should ring a bell: just add a view function and you have the MVU pattern! And the nice thing is that F# allows to use the same code, both for verification with .NET and for visualization with JavaScript.

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
20: 
21: 
22: 
23: 
24: 
25: 
26: 
27: 
28: 
29: 
    #r "nuget: Fable.Elmish.React"

    open Elmish
    open Elmish.React
    open Fable.React.Props
    open Fable.React.Helpers
    open Fable.React.Standard

    let init () = []

    let rnd = System.Random()

    let colors = [| "DarkSlateGray"; "DarkSlateBlue"; "CadetBlue"; "DarkCyan"; "DarkSeaGreen"; "Olive"; "Pink" |]

    let item n =
        let color = colors.[n % colors.Length]
        td [Style [Color color; Border "solid" ]] [str (string n)]

    let view (state: State) (dispatch: Action -> unit) =
        div [] [
            h2 [] [str "What a wonderful queue!"]
            button [ OnClick (fun _ -> rnd.Next(100) |> Enqueue |> dispatch) ] [ str "Enqueue -> " ]
            state |> List.rev |> List.map item |> table []
            if not state.IsEmpty then button [ OnClick (fun _ -> Dequeue |> dispatch) ] [ str " -> Dequeue" ]
        ]

    Program.mkSimple init nextState view
    |> Program.withReactSynchronous "elmish-app"
    |> Program.run

I think this live documentation aspect is worth exploring.

Final remark

The provided example was not meant to precisely capture the essence of the queue concept. It may be improved and refined to this aim, but I just wanted to describe a small stateful system, with no attempt to define a proper abstraction.

I often praise abstract data types but, quoting Leslie Lamport (via Ron Pressler):

The lesson I learned from the specification work of the early ’80s is that axiomatic specifications don’t work. The idea of specifying a system by writing down all the properties it satisfies seems perfect. We just list what the system must and must not do, and we have a completely abstract specification. It sounds wonderful; it just doesn’t work in practice.

I have to admit we rarely encounter formal ADT specifications besides simple examples and stacks (a quip of Djikstra, according to Bertrand Meyer, is that the purpose of ADT theory is to define stacks).

So, if we leave aside philosophy, we can lower a bit our theoretical pretenses and understand our systems better, embracing the operational paradigm of state machines.

namespace System
namespace System.Collections
namespace System.Collections.Generic
Multiple items
type Queue<'T> = interface IEnumerable<'T> interface IEnumerable interface IReadOnlyCollection<'T> interface ICollection new : unit -> unit + 2 overloads member Clear : unit -> unit member Contains : item: 'T -> bool member CopyTo : array: 'T [] * arrayIndex: int -> unit member Dequeue : unit -> 'T member Enqueue : item: 'T -> unit ...
<summary>Represents a first-in, first-out collection of objects.</summary>
<typeparam name="T">Specifies the type of elements in the queue.</typeparam>


--------------------
System.Collections.Generic.Queue() : System.Collections.Generic.Queue<'T>
System.Collections.Generic.Queue(collection: System.Collections.Generic.IEnumerable<'T>) : System.Collections.Generic.Queue<'T>
System.Collections.Generic.Queue(capacity: int) : System.Collections.Generic.Queue<'T>
Multiple items
val int : value:'T -> int (requires member op_Explicit)
<summary>Converts the argument to signed 32-bit integer. This is a direct conversion for all primitive numeric types. For strings, the input is converted using <c>Int32.Parse()</c> with InvariantCulture settings. Otherwise the operation requires an appropriate static conversion method on the input type.</summary>
<param name="value">The input value.</param>
<returns>The converted int</returns>


--------------------
[<Struct>] type int = int32
<summary>An abbreviation for the CLI type <see cref="T:System.Int32" />.</summary>
<category>Basic Types</category>


--------------------
type int<'Measure> = int
<summary>The type of 32-bit signed integer numbers, annotated with a unit of measure. The unit of measure is erased in compiled code and when values of this type are analyzed using reflection. The type is representationally equivalent to <see cref="T:System.Int32" />.</summary>
<category>Basic Types with Units of Measure</category>
type State = int list
type 'T list = List<'T>
<summary>The type of immutable singly-linked lists. </summary>
<remarks>See the <see cref="T:Microsoft.FSharp.Collections.ListModule" /> module for further operations related to lists. Use the constructors <c>[]</c> and <c>::</c> (infix) to create values of this type, or the notation <c>[1; 2; 3]</c>. Use the values in the <c>List</c> module to manipulate values of this type, or pattern match against the values directly. See also <a href="https://docs.microsoft.com/dotnet/fsharp/language-reference/lists">F# Language Guide - Lists</a>. </remarks>
type Action = | Enqueue of int | Dequeue
union case Action.Enqueue: int -> Action
union case Action.Dequeue: Action
val fromModel : state:State -> Sut
val state : State
type Sut = System.Collections.Generic.Queue<int>
val toModel : sut:Sut -> int list
val sut : Sut
Multiple items
module List from Microsoft.FSharp.Collections
<summary>Contains operations for working with values of type <see cref="T:Microsoft.FSharp.Collections.list`1" />.</summary>
<namespacedoc><summary>Operations for collections such as lists, arrays, sets, maps and sequences. See also <a href="https://docs.microsoft.com/dotnet/fsharp/language-reference/fsharp-collection-types">F# Collection Types</a> in the F# Language Guide. </summary></namespacedoc>


--------------------
type List<'T> = | ( [] ) | ( :: ) of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex : rank:int * offset:int -> int member GetSlice : startIndex:int option * endIndex:int option -> 'T list static member Cons : head:'T * tail:'T list -> 'T list member Head : 'T member IsEmpty : bool member Item : index:int -> 'T with get ...
<summary>The type of immutable singly-linked lists.</summary>
<remarks>Use the constructors <c>[]</c> and <c>::</c> (infix) to create values of this type, or the notation <c>[1;2;3]</c>. Use the values in the <c>List</c> module to manipulate values of this type, or pattern match against the values directly. </remarks>
<exclude />
val ofSeq : source:seq<'T> -> 'T list
<summary>Builds a new list from the given enumerable object.</summary>
<param name="source">The input sequence.</param>
<returns>The list of elements from the sequence.</returns>
val nextState : action:Action -> state:State -> int list
val action : Action
val x : int
val append : list1:'T list -> list2:'T list -> 'T list
<summary>Returns a new list that contains the elements of the first list followed by elements of the second.</summary>
<param name="list1">The first input list.</param>
<param name="list2">The second input list.</param>
<returns>The resulting list.</returns>
property List.Tail: int list with get
<summary>Gets the tail of the list, which is a list containing all the elements of the list, excluding the first element </summary>
val run : action:Action -> sut:Sut -> int list
System.Collections.Generic.Queue.Enqueue(item: int) : unit
System.Collections.Generic.Queue.Dequeue() : int
val ignore : value:'T -> unit
<summary>Ignore the passed value. This is often used to throw away results of a computation.</summary>
<param name="value">The value to ignore.</param>
val invariant : State -> bool
val precondition : action:Action -> state:State -> bool
val test : state:State * action:Action -> unit
val check : (string -> bool -> unit)
val error : string
val x : bool
val not : value:bool -> bool
<summary>Negate a logical value. Not True equals False and not False equals True</summary>
<param name="value">The value to negate.</param>
<returns>The result of the negation.</returns>
val failwith : message:string -> 'T
<summary>Throw a <see cref="T:System.Exception" /> exception.</summary>
<param name="message">The exception message.</param>
<returns>The result value.</returns>
val expected : int list
val actual : int list
namespace FsCheck
val actionGenerator : state:State -> Gen<Action>
val enq : Gen<Action>
Multiple items
module Gen from FsCheck
<summary> Combinators to build custom random generators for any type. </summary>

--------------------
type Gen<'a> = private | Gen of (int -> StdGen -> 'a) interface IGen member private Map : f:('a -> 'b) -> Gen<'b> static member ( <!> ) : f:('a1 -> 'a2) * a:Gen<'a1> -> Gen<'a2> static member ( <*> ) : f:Gen<('a1 -> 'a2)> * a:Gen<'a1> -> Gen<'a2> static member ( >>= ) : m:Gen<'a1> * k:('a1 -> Gen<'a2>) -> Gen<'a2>
<summary> Generator of a random value, based on a size parameter and a randomly generated int. </summary>
val map : f:('b -> 'c) -> gen:Gen<'b> -> Gen<'c>
<summary> Apply the function f to the value in the generator, yielding a new generator. </summary>
module Arb from FsCheck
val generate<'Value> : Gen<'Value>
<summary> Returns a Gen&lt;'Value&gt; </summary>
val deq : Gen<Action>
val constant : v:'b -> Gen<'b>
<summary> Always generate the same instance v. See also fresh. </summary>
property List.IsEmpty: bool with get
<summary>Gets a value indicating if the list contains no entries</summary>
val oneof : gens:seq<Gen<'a>> -> Gen<'a>
<summary> Build a generator that generates a value from one of the generators in the given non-empty seq, with equal probability. </summary>
val arbitraryStep : Arbitrary<int list * Action>
val stepGenerator : Gen<int list * Action>
val gen : GenBuilder
<summary> The workflow function for generators, e.g. gen { ... } </summary>
val state : int list
val listOf : gn:Gen<'b> -> Gen<'b list>
<summary> Generates a list of random length. The maximum length depends on the size parameter. </summary>
val fromGen : gen:Gen<'Value> -> Arbitrary<'Value>
<summary> Construct an Arbitrary instance from a generator. Shrink is not supported for this type. </summary>
module Prop from FsCheck
<summary> Combinators to build properties, which define the property to be tested, with some convenience methods to investigate the generated arguments and any found counter-examples. </summary>
val forAll : arb:Arbitrary<'Value> -> body:('Value -> 'Testable) -> Property
<summary> Quantified property combinator. Provide a custom test data generator to a property. </summary>
type Check = static member All : config:Config * test:Type -> unit + 1 overload static member Method : config:Config * methodInfo:MethodInfo * ?target:obj -> unit static member One : config:Config * property:'Testable -> unit + 1 overload static member Quick : property:'Testable -> unit + 1 overload static member QuickAll : test:Type -> unit + 1 overload static member QuickThrowOnFailure : property:'Testable -> unit static member QuickThrowOnFailureAll : test:Type -> unit + 1 overload static member Verbose : property:'Testable -> unit + 1 overload static member VerboseAll : test:Type -> unit + 1 overload static member VerboseThrowOnFailure : property:'Testable -> unit ...
static member Check.Quick : property:'Testable -> unit
static member Check.Quick : name:string * property:'Testable -> unit
namespace FsCheck.Experimental
val machine : Machine<Sut,State>
Multiple items
type Machine<'Actual,'Model> = new : maxNumberOfCommands:int -> Machine<'Actual,'Model> + 1 overload abstract member Next : 'Model -> Gen<Operation<'Actual,'Model>> abstract member ShrinkOperations : Operation<'Actual,'Model> list -> seq<Operation<'Actual,'Model> list> + 1 overload member MaxNumberOfCommands : int abstract member Setup : Arbitrary<Setup<'Actual,'Model>> override TearDown : TearDown<'Actual> abstract member TearDown : TearDown<'Actual>
<summary> Defines the initial state for actual and model object, and allows to define the generator to use for the next state, based on the model. </summary>

--------------------
new : unit -> Machine<'Actual,'Model>
new : maxNumberOfCommands:int -> Machine<'Actual,'Model>
Multiple items
type Setup<'Actual,'Model> = new : unit -> Setup<'Actual,'Model> abstract member Actual : unit -> 'Actual abstract member Model : unit -> 'Model override ToString : unit -> string

--------------------
new : unit -> Setup<'Actual,'Model>
val __ : Machine<Sut,State>
val __ : Setup<Sut,State>
abstract member Setup.Model : unit -> 'Model
abstract member Machine.Next : 'Model -> Gen<Operation<'Actual,'Model>>
Multiple items
type Operation<'Actual,'Model> = interface IOperation new : unit -> Operation<'Actual,'Model> abstract member Check : 'Actual * 'Model -> Property abstract member Pre : 'Model -> bool + 1 overload abstract member Run : 'Model -> 'Model
<summary> An operation describes pre and post conditions and the model for a single operation under test. The post-conditions are the invariants that will be checked; when these do not hold the test fails. </summary>

--------------------
new : unit -> Operation<'Actual,'Model>
val __ : Operation<Sut,State>
abstract member Operation.Check : 'Actual * 'Model -> Property
val sprintf : format:Printf.StringFormat<'T> -> 'T
<summary>Print to a string using the given format.</summary>
<param name="format">The formatter.</param>
<returns>The formatted result.</returns>
System.Object.ToString() : string
module StateMachine from FsCheck.Experimental
val toProperty : spec:Machine<'Actual,'Model> -> Property
<summary> Turn a machine specification into a property. </summary>
namespace Elmish
namespace Elmish.React
namespace Fable
namespace Fable.React
module Props from Fable.React
module Helpers from Fable.React
module Standard from Fable.React
val init : unit -> 'a list
val rnd : System.Random
Multiple items
type Random = new : unit -> unit + 1 overload member Next : unit -> int + 2 overloads member NextBytes : buffer: byte [] -> unit + 1 overload member NextDouble : unit -> float member Sample : unit -> float
<summary>Represents a pseudo-random number generator, which is an algorithm that produces a sequence of numbers that meet certain statistical requirements for randomness.</summary>

--------------------
System.Random() : System.Random
System.Random(Seed: int) : System.Random
val colors : string []
val item : n:int -> Fable.React.ReactElement
val n : int
val color : string
property System.Array.Length: int with get
<summary>Gets the total number of elements in all the dimensions of the <see cref="T:System.Array" />.</summary>
<exception cref="T:System.OverflowException">The array is multidimensional and contains more than <see cref="F:System.Int32.MaxValue" /> elements.</exception>
<returns>The total number of elements in all the dimensions of the <see cref="T:System.Array" />; zero if there are no elements in the array.</returns>
val td : props:seq<IHTMLProp> -> children:seq<Fable.React.ReactElement> -> Fable.React.ReactElement
union case HTMLAttr.Style: CSSProp list -> HTMLAttr
union case CSSProp.Color: obj -> CSSProp
union case CSSProp.Border: obj -> CSSProp
val str : s:string -> Fable.React.ReactElement
<summary> Alias of `ofString` </summary>
Multiple items
val string : value:'T -> string
<summary>Converts the argument to a string using <c>ToString</c>.</summary>
<remarks>For standard integer and floating point values the and any type that implements <c>IFormattable</c><c>ToString</c> conversion uses <c>CultureInfo.InvariantCulture</c>. </remarks>
<param name="value">The input value.</param>
<returns>The converted string.</returns>


--------------------
type string = System.String
<summary>An abbreviation for the CLI type <see cref="T:System.String" />.</summary>
<category>Basic Types</category>
val view : state:State -> dispatch:(Action -> unit) -> Fable.React.ReactElement
val dispatch : (Action -> unit)
Multiple items
union case HTMLAttr.Action: string -> HTMLAttr

--------------------
type Action = | Enqueue of int | Dequeue
type unit = Unit
<summary>The type 'unit', which has only one value "()". This value is special and always uses the representation 'null'.</summary>
<category index="1">Basic Types</category>
val div : props:seq<IHTMLProp> -> children:seq<Fable.React.ReactElement> -> Fable.React.ReactElement
val h2 : props:seq<IHTMLProp> -> children:seq<Fable.React.ReactElement> -> Fable.React.ReactElement
val button : props:seq<IHTMLProp> -> children:seq<Fable.React.ReactElement> -> Fable.React.ReactElement
union case DOMAttr.OnClick: (Browser.Types.MouseEvent -> unit) -> DOMAttr
System.Random.Next() : int
System.Random.Next(maxValue: int) : int
System.Random.Next(minValue: int, maxValue: int) : int
Multiple items
union case HTMLAttr.List: string -> HTMLAttr

--------------------
module List from Microsoft.FSharp.Collections
<summary>Contains operations for working with values of type <see cref="T:Microsoft.FSharp.Collections.list`1" />.</summary>
<namespacedoc><summary>Operations for collections such as lists, arrays, sets, maps and sequences. See also <a href="https://docs.microsoft.com/dotnet/fsharp/language-reference/fsharp-collection-types">F# Collection Types</a> in the F# Language Guide. </summary></namespacedoc>


--------------------
type List<'T> = | ( [] ) | ( :: ) of Head: 'T * Tail: 'T list interface IReadOnlyList<'T> interface IReadOnlyCollection<'T> interface IEnumerable interface IEnumerable<'T> member GetReverseIndex : rank:int * offset:int -> int member GetSlice : startIndex:int option * endIndex:int option -> 'T list static member Cons : head:'T * tail:'T list -> 'T list member Head : 'T member IsEmpty : bool member Item : index:int -> 'T with get ...
<summary>The type of immutable singly-linked lists.</summary>
<remarks>Use the constructors <c>[]</c> and <c>::</c> (infix) to create values of this type, or the notation <c>[1;2;3]</c>. Use the values in the <c>List</c> module to manipulate values of this type, or pattern match against the values directly. </remarks>
<exclude />
val rev : list:'T list -> 'T list
<summary>Returns a new list with the elements in reverse order.</summary>
<param name="list">The input list.</param>
<returns>The reversed list.</returns>
val map : mapping:('T -> 'U) -> list:'T list -> 'U list
<summary>Builds a new collection whose elements are the results of applying the given function to each of the elements of the collection.</summary>
<param name="mapping">The function to transform elements from the input list.</param>
<param name="list">The input list.</param>
<returns>The list of transformed elements.</returns>
val table : props:seq<IHTMLProp> -> children:seq<Fable.React.ReactElement> -> Fable.React.ReactElement
Multiple items
module Program from Elmish.React

--------------------
module Program from Elmish
<summary> Program module - functions to manipulate program instances </summary>

--------------------
type Program<'arg,'model,'msg,'view> = private { init: 'arg -> 'model * Cmd<'msg> update: 'msg -> 'model -> 'model * Cmd<'msg> subscribe: 'model -> Cmd<'msg> view: 'model -> Dispatch<'msg> -> 'view setState: 'model -> Dispatch<'msg> -> unit onError: string * exn -> unit syncDispatch: Dispatch<'msg> -> Dispatch<'msg> }
<summary> Program type captures various aspects of program behavior </summary>
val mkSimple : init:('arg -> 'model) -> update:('msg -> 'model -> 'model) -> view:('model -> Dispatch<'msg> -> 'view) -> Program<'arg,'model,'msg,'view>
<summary> Simple program that produces only new state with `init` and `update`. </summary>
val withReactSynchronous : placeholderId:string -> program:Program<'a,'b,'c,Fable.React.ReactElement> -> Program<'a,'b,'c,Fable.React.ReactElement>
<summary> Renders React root component inside html element identified by placeholderId. New renders are triggered immediately after an update. </summary>
val run : program:Program<unit,'model,'msg,'view> -> unit
<summary> Start the dispatch loop with `unit` for the init() function. </summary>