F# Implementation of The Elm Architecture - Part 2

This is the second part of a prototype of The Elm Architecture in F#. The first post covered the logical UI and this covers using it with WPF and Xamarin.

Below is the WPF implementation of the INativeUI interface. The Xamarin implementation is very similar.

Threading proves to be simple with all the updates being performed on the UI thread in a single call. UIUpdate maps well to the operations required to locate and update the native UI elements.

module WPF = let ( root : ContentControl ) = let rec ui : UIElement = match ui with | Text text -> let c = Label ( Content = text ) upcast c | Input ( text , event ) -> let c = TextBox ( Text = text ) let event = ! event . TextChanged . Add ( fun _ -> let t = c . Text async { ! event t } |> Async . Start ) upcast c | Button ( text , event ) -> let c = Button ( Content = text ) let event = ! event . Click . Add ( fun _ -> async { ( ! event ) ( ) } |> Async . Start ) upcast c | Div ( layout , list ) -> let children = List . map list let c = StackPanel ( Orientation = match layout with | Vertical -> Orientation . Vertical | Horizontal -> Orientation . Horizontal ) List . iter ( . Children . Add > > ) children upcast c let rec loc : Panel = match loc with | [ ] -> root . Content :?> _ | i :: xs -> ( xs ) . Children . Item i :?> _ let u = match u with | InsertUI ( loc , ui ) -> match loc with | [ ] -> root . Content <- ui | i :: xs -> xs ) . Children . Insert ( i , ui ) | UpdateUI ( loc , ui ) -> let element = match loc with | [ ] -> root . Content :?> _ | i :: xs -> ( xs ) . Children . Item i match ui with | Text text -> ( element :?> Label ) . Content <- text | Input ( text , _ ) -> ( element :?> TextBox ) . Text <- text | Button ( text , _ ) -> ( element :?> Button ) . Content <- text | Div _ -> ( ) | ReplaceUI ( loc , ui ) -> match loc with | [ ] -> root . Content <- ui | i :: xs -> let c = ( xs ) . Children . RemoveAt i . Insert ( i , ui ) | RemoveUI loc -> match loc with | [ ] -> ( ) | i :: xs -> xs ) . Children . RemoveAt i | EventUI _ -> ( ) { new INativeUI with member __ . list = . Dispatcher . Invoke ( fun ( ) -> List . iter list ) }

The same CounterList UI application from the previous post has been used across a number of native UIs. The example source produces the following mobile and desktop UIs.

The Elm Architecture continues to look to be a very promising pattern.

The INativeUI implementation is a single place for native UI element creation and is much more DRY than other UI models. So far, styling has not been considered, but this single place should make it easier with both an Elm and a CSS model being possible.

The Elm Architecture moves the view and event logic away from the native UI. This has the benefit of making the UI more testable and at the same time making migration of the native UI easier.

These benefits, combined with the type safety and composability outlined in the previous post, make this pattern compelling.

UPDATED:

Multiple items

module List



from Microsoft.FSharp.Collections



--------------------

type List<'T> =

| ( [] )

| ( :: ) of Head: 'T * Tail: 'T list

interface IReadOnlyList<'T>

interface IReadOnlyCollection<'T>

interface IEnumerable

interface IEnumerable<'T>

member GetSlice : startIndex:int option * endIndex:int option -> 'T list

member Head : 'T

member IsEmpty : bool

member Item : index:int -> 'T with get

member Length : int

member Tail : 'T list

...

val remove : n:int -> l:'a list -> 'a list * 'a list

val n : int

val l : 'a list

val pop : (int -> 'b list -> 'b list -> 'b list * 'b list)

val l : 'b list

val p : 'b list

val x : 'b

val xs : 'b list

val add : p:'a list -> l:'a list -> 'a list

val p : 'a list

val x : 'a

val xs : 'a list

val removei : i:int -> l:'a list -> 'a list

val i : int

val t : 'a list

val tail : list:'T list -> 'T list

val replacei : i:int -> item:'a -> l:'a list -> 'a list

val item : 'a

val revMap : mapping:('a -> 'b) -> list:'a list -> 'b list

val mapping : ('a -> 'b)

Multiple items

val list : 'a list



--------------------

type 'T list = List<'T>

val work : ('a list -> 'b list -> 'b list)

val input : 'a list

val output : 'b list

val mapAt : i:int -> mapping:('a -> 'a) -> list:'a list -> 'a list

val mapping : ('a -> 'a)

val removed : 'a list

val tail : 'a list

val head : list:'T list -> 'T

Multiple items

module Event



from Microsoft.FSharp.Control



--------------------

type 'msg Event = ('msg -> unit) ref ref





Message event used on the primative UI components.



--------------------

type Event<'Delegate,'Args (requires delegate and 'Delegate :> Delegate)> =

new : unit -> Event<'Delegate,'Args>

member Trigger : sender:obj * args:'Args -> unit

member Publish : IEvent<'Delegate,'Args>



--------------------

new : unit -> Event<'Delegate,'Args>

type unit = Unit

Multiple items

val ref : value:'T -> 'T ref



--------------------

type 'T ref = Ref<'T>

type Layout =

| Horizontal

| Vertical





Layout for a section of UI components.

union case Layout.Horizontal: Layout

union case Layout.Vertical: Layout

type UI =

| Text of string

| Input of string * string Event

| Button of string * unit Event

| Div of Layout * UI list





Primative UI components.

union case UI.Text: string -> UI

Multiple items

val string : value:'T -> string



--------------------

type string = System.String

union case UI.Input: string * string Event -> UI

union case UI.Button: string * unit Event -> UI

union case UI.Div: Layout * UI list -> UI

type 'T list = List<'T>

type UIUpdate =

| InsertUI of int list * UI

| UpdateUI of int list * UI

| ReplaceUI of int list * UI

| RemoveUI of int list

| EventUI of (unit -> unit)





UI component update and event redirection.

union case UIUpdate.InsertUI: int list * UI -> UIUpdate

Multiple items

val int : value:'T -> int (requires member op_Explicit)



--------------------

type int = int32



--------------------

type int<'Measure> = int

union case UIUpdate.UpdateUI: int list * UI -> UIUpdate

union case UIUpdate.ReplaceUI: int list * UI -> UIUpdate

union case UIUpdate.RemoveUI: int list -> UIUpdate

union case UIUpdate.EventUI: (unit -> unit) -> UIUpdate

Multiple items

type UI =

| Text of string

| Input of string * string Event

| Button of string * unit Event

| Div of Layout * UI list





Primative UI components.



--------------------

type 'msg UI =

{UI: UI;

mutable Event: 'msg -> unit;}





UI component including a message event.

Multiple items

UI.UI: UI



--------------------

type UI =

| Text of string

| Input of string * string Event

| Button of string * unit Event

| Div of Layout * UI list





Primative UI components.



--------------------

type 'msg UI =

{UI: UI;

mutable Event: 'msg -> unit;}





UI component including a message event.

Multiple items

UI.Event: 'msg -> unit



--------------------

module Event



from Microsoft.FSharp.Control



--------------------

type 'msg Event = ('msg -> unit) ref ref





Message event used on the primative UI components.



--------------------

type Event<'Delegate,'Args (requires delegate and 'Delegate :> Delegate)> =

new : unit -> Event<'Delegate,'Args>

member Trigger : sender:obj * args:'Args -> unit

member Publish : IEvent<'Delegate,'Args>



--------------------

new : unit -> Event<'Delegate,'Args>

type App<'msg,'model> =

{Model: 'model;

Update: 'msg -> 'model -> 'model;

View: 'model -> 'msg UI;}





UI application.

App.Model: 'model

App.Update: 'msg -> 'model -> 'model

App.View: 'model -> 'msg UI

type INativeUI =

interface

abstract member Send : UIUpdate list -> unit

end





Native UI interface.

Multiple items

type CompilationRepresentationAttribute =

inherit Attribute

new : flags:CompilationRepresentationFlags -> CompilationRepresentationAttribute

member Flags : CompilationRepresentationFlags



--------------------

new : flags:CompilationRepresentationFlags -> CompilationRepresentationAttribute

type CompilationRepresentationFlags =

| None = 0

| Static = 1

| Instance = 2

| ModuleSuffix = 4

| UseNullAsTrueValue = 8

| Event = 16

CompilationRepresentationFlags.ModuleSuffix: CompilationRepresentationFlags = 4

val memoize<'model,'msg (requires reference type and reference type)> : (('model -> 'msg UI) -> 'model -> 'msg UI) (requires reference type and reference type)





Memoize view generation from model object references.

val not : value:bool -> bool

val d : System.Runtime.CompilerServices.ConditionalWeakTable<'model,'msg UI> (requires reference type and reference type)

namespace System

namespace System.Runtime

namespace System.Runtime.CompilerServices

Multiple items

type ConditionalWeakTable<'TKey,'TValue (requires reference type and reference type)> =

new : unit -> ConditionalWeakTable<'TKey, 'TValue>

member Add : key:'TKey * value:'TValue -> unit

member GetOrCreateValue : key:'TKey -> 'TValue

member GetValue : key:'TKey * createValueCallback:CreateValueCallback<'TKey, 'TValue> -> 'TValue

member Remove : key:'TKey -> bool

member TryGetValue : key:'TKey * value:'TValue -> bool

nested type CreateValueCallback



--------------------

System.Runtime.CompilerServices.ConditionalWeakTable() : System.Runtime.CompilerServices.ConditionalWeakTable<'TKey,'TValue>

val view : ('model -> 'msg UI) (requires reference type and reference type)

val model : 'model (requires reference type)

System.Runtime.CompilerServices.ConditionalWeakTable.TryGetValue(key: 'model, value: byref<'msg UI>) : bool

val ui : 'msg UI (requires reference type)

System.Runtime.CompilerServices.ConditionalWeakTable.Add(key: 'model, value: 'msg UI) : unit

val text : text:string -> 'a UI





Returns a Text display UI component.

val text : string

val ignore : value:'T -> unit

val input : text:string -> string UI





Returns a text Input UI component.

val ev : (string -> unit) ref ref

val ui : string UI

val raise : (string -> unit)

val a : string

UI.Event: string -> unit

val button : text:string -> msg:'a -> 'a UI





Returns a Button UI component.

val msg : 'a

val ev : (unit -> unit) ref ref

val ui : 'a UI

UI.Event: 'a -> unit

val div : layout:Layout -> list:'a UI list -> 'a UI





Returns a section of UI components given a layout.

The name div comes from HTML and represents a division (or section) of UI components.

val layout : Layout

Multiple items

val list : 'a UI list



--------------------

type 'T list = List<'T>

Multiple items

module List



from Main



--------------------

module List



from Microsoft.FSharp.Collections



--------------------

type List<'T> =

| ( [] )

| ( :: ) of Head: 'T * Tail: 'T list

interface IReadOnlyList<'T>

interface IReadOnlyCollection<'T>

interface IEnumerable

interface IEnumerable<'T>

member GetSlice : startIndex:int option * endIndex:int option -> 'T list

member Head : 'T

member IsEmpty : bool

member Item : index:int -> 'T with get

member Length : int

member Tail : 'T list

...

val map : mapping:('T -> 'U) -> list:'T list -> 'U list

UI.UI: UI

val raise : ('a -> unit)

val a : 'a

val iter : action:('T -> unit) -> list:'T list -> unit

val i : 'a UI

val map : f:('a -> 'b) -> ui:'a UI -> 'b UI





Returns a new UI component mapping the message event using the given function.

val f : ('a -> 'b)

val ui2 : 'b UI

val e : 'a

UI.Event: 'b -> unit

val diff : ui1:'a UI -> ui2:'b UI -> UIUpdate list





Returns a list of UI updates from two UI components.

val ui1 : 'a UI

val update : ('c ref ref -> 'c ref ref -> unit -> unit)

val e1 : 'c ref ref

val e2 : 'c ref ref

val ev : 'c ref

val diff : (UI -> UI -> int list -> int -> UIUpdate list -> UIUpdate list)

val ui1 : UI

val ui2 : UI

val path : int list

val index : int

val diffs : UIUpdate list

module LanguagePrimitives



from Microsoft.FSharp.Core

val PhysicalEquality : e1:'T -> e2:'T -> bool (requires reference type)

val t1 : string

val t2 : string

val e1 : unit Event

val e2 : unit Event

val e1 : string Event

val e2 : string Event

val l1 : Layout

val l2 : Layout

val l : UI list

val fold : folder:('State -> 'T -> 'State) -> state:'State -> list:'T list -> 'State

val ui : UI

val snd : tuple:('T1 * 'T2) -> 'T2

val l : Layout

val h1 : UI

val t1 : UI list

val h2 : UI

val t2 : UI list

val h3 : UI

val app : model:'a -> update:('b -> 'a -> 'a) -> view:('a -> 'b UI) -> App<'b,'a>





Returns a UI application from a UI model, update and view.

val model : 'a

val update : ('b -> 'a -> 'a)

val view : ('a -> 'b UI)

val run : nativeUI:INativeUI -> app:App<'a,'b> -> unit





Runs a UI application given a native UI.

val nativeUI : INativeUI

val app : App<'a,'b>

val handle : ('b -> 'a UI -> 'a -> unit)

val model : 'b

val newModel : 'b

App.Update: 'a -> 'b -> 'b

val newUI : 'a UI

App.View: 'b -> 'a UI

val diff : UIUpdate list

val f : (unit -> unit)

abstract member INativeUI.Send : UIUpdate list -> unit

App.Model: 'b

type Msg =

| Increment

| Decrement

union case Msg.Increment: Msg

union case Msg.Decrement: Msg

type Model = int

val init : i:Model -> Model

val i : Model

val update : msg:Msg -> model:int -> Model

val msg : Msg

val model : int

val view : model:Model -> Msg UI

val model : Model

Multiple items

module UI



from Main



--------------------

type UI =

| Text of string

| Input of string * string Event

| Button of string * unit Event

| Div of Layout * UI list





Primative UI components.



--------------------

type 'msg UI =

{UI: UI;

mutable Event: 'msg -> unit;}





UI component including a message event.

val app : i:Model -> App<Msg,Model>

type Msg =

| Reset

| Top of Msg

| Bottom of Msg

union case Msg.Reset: Msg

union case Msg.Top: Counter.Msg -> Msg

module Counter



from Main

union case Msg.Bottom: Counter.Msg -> Msg

type Model =

{Top: Model;

Bottom: Model;}

Model.Top: Counter.Model

Model.Bottom: Counter.Model

val init : top:Counter.Model -> bottom:Counter.Model -> Model

val top : Counter.Model

val bottom : Counter.Model

val init : i:Counter.Model -> Counter.Model

val update : msg:Msg -> model:Model -> Model

val msg : Counter.Msg

val update : msg:Counter.Msg -> model:int -> Counter.Model

val view : model:Counter.Model -> Counter.Msg UI

val app : top:Counter.Model -> bottom:Counter.Model -> App<Msg,Model>

type Msg =

| Insert

| Remove

| Modify of int * Msg

union case Msg.Insert: Msg

union case Msg.Remove: Msg

union case Msg.Modify: int * Counter.Msg -> Msg

type Model =

{Counters: Model list;}

Model.Counters: Counter.Model list

val init : Model

val xs : Counter.Model list

val mapi : mapping:(int -> 'T -> 'U) -> list:'T list -> 'U list

val c : Counter.Model

val v : Counter.Msg

val app : App<Msg,Model>

namespace System.Windows

namespace System.Windows.Controls

module WPF



from Main

val CreateNaiveUI : root:ContentControl -> INativeUI

val root : ContentControl

Multiple items

type ContentControl =

inherit Control

new : unit -> ContentControl

member Content : obj with get, set

member ContentStringFormat : string with get, set

member ContentTemplate : DataTemplate with get, set

member ContentTemplateSelector : DataTemplateSelector with get, set

member HasContent : bool

member ShouldSerializeContent : unit -> bool

static val ContentProperty : DependencyProperty

static val HasContentProperty : DependencyProperty

static val ContentTemplateProperty : DependencyProperty

...



--------------------

ContentControl() : ContentControl

val createUI : (UI -> UIElement)

Multiple items

type UIElement =

inherit Visual

new : unit -> UIElement

member AddHandler : routedEvent:RoutedEvent * handler:Delegate -> unit + 1 overload

member AddToEventRoute : route:EventRoute * e:RoutedEventArgs -> unit

member AllowDrop : bool with get, set

member ApplyAnimationClock : dp:DependencyProperty * clock:AnimationClock -> unit + 1 overload

member AreAnyTouchesCaptured : bool

member AreAnyTouchesCapturedWithin : bool

member AreAnyTouchesDirectlyOver : bool

member AreAnyTouchesOver : bool

member Arrange : finalRect:Rect -> unit

...



--------------------

UIElement() : UIElement

val c : Label

Multiple items

type Label =

inherit ContentControl

new : unit -> Label

member Target : UIElement with get, set

static val TargetProperty : DependencyProperty



--------------------

Label() : Label

Multiple items

union case UI.Input: string * string Event -> UI



--------------------

namespace System.Windows.Input

val event : string Event

val c : TextBox

Multiple items

type TextBox =

inherit TextBoxBase

new : unit -> TextBox

member CaretIndex : int with get, set

member CharacterCasing : CharacterCasing with get, set

member Clear : unit -> unit

member GetCharacterIndexFromLineIndex : lineIndex:int -> int

member GetCharacterIndexFromPoint : point:Point * snapToText:bool -> int

member GetFirstVisibleLineIndex : unit -> int

member GetLastVisibleLineIndex : unit -> int

member GetLineIndexFromCharacterIndex : charIndex:int -> int

member GetLineLength : lineIndex:int -> int

...



--------------------

TextBox() : TextBox

val event : (string -> unit) ref

event Primitives.TextBoxBase.TextChanged: IEvent<TextChangedEventHandler,TextChangedEventArgs>

member System.IObservable.Add : callback:('T -> unit) -> unit

val t : string

property TextBox.Text: string

val async : AsyncBuilder

Multiple items

type Async =

static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)

static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)

static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>

static member AwaitTask : task:Task -> Async<unit>

static member AwaitTask : task:Task<'T> -> Async<'T>

static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>

static member CancelDefaultToken : unit -> unit

static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>

static member Choice : computations:seq<Async<'T option>> -> Async<'T option>

static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>

...



--------------------

type Async<'T> =

static member Async.Start : computation:Async<unit> * ?cancellationToken:System.Threading.CancellationToken -> unit

Multiple items

type Button =

inherit ButtonBase

new : unit -> Button

member IsCancel : bool with get, set

member IsDefault : bool with get, set

member IsDefaulted : bool

static val IsDefaultProperty : DependencyProperty

static val IsCancelProperty : DependencyProperty

static val IsDefaultedProperty : DependencyProperty



--------------------

Button() : Button

val event : unit Event

val c : Button

val event : (unit -> unit) ref

event Primitives.ButtonBase.Click: IEvent<RoutedEventHandler,RoutedEventArgs>

Multiple items

val list : UI list



--------------------

type 'T list = List<'T>

val children : UIElement list

val c : StackPanel

Multiple items

type StackPanel =

inherit Panel

new : unit -> StackPanel

member CanHorizontallyScroll : bool with get, set

member CanVerticallyScroll : bool with get, set

member ExtentHeight : float

member ExtentWidth : float

member HorizontalOffset : float

member LineDown : unit -> unit

member LineLeft : unit -> unit

member LineRight : unit -> unit

member LineUp : unit -> unit

...



--------------------

StackPanel() : StackPanel

type Orientation =

| Horizontal = 0

| Vertical = 1

field Orientation.Vertical: Orientation = 1

field Orientation.Horizontal: Orientation = 0

property Panel.Children: UIElementCollection

UIElementCollection.Add(element: UIElement) : int

val locatePanel : (int list -> Panel)

val loc : int list

type Panel =

inherit FrameworkElement

member Background : Brush with get, set

member Children : UIElementCollection

member HasLogicalOrientationPublic : bool

member IsItemsHost : bool with get, set

member LogicalOrientationPublic : Orientation

member ShouldSerializeChildren : unit -> bool

static val BackgroundProperty : DependencyProperty

static val IsItemsHostProperty : DependencyProperty

static val ZIndexProperty : DependencyProperty

static member GetZIndex : element:UIElement -> int

...

property ContentControl.Content: obj

val xs : int list

val uiUpdate : (UIUpdate -> unit)

val u : UIUpdate

val element : UIElement

val c : UIElementCollection

UIElementCollection.RemoveAt(index: int) : unit

UIElementCollection.Insert(index: int, element: UIElement) : unit

Multiple items

val list : UIUpdate list



--------------------

type 'T list = List<'T>

property Threading.DispatcherObject.Dispatcher: Threading.Dispatcher

Threading.Dispatcher.Invoke<'TResult>(callback: System.Func<'TResult>) : 'TResult

(+0 other overloads)

Threading.Dispatcher.Invoke(callback: System.Action) : unit

(+0 other overloads)

Threading.Dispatcher.Invoke(method: System.Delegate, [<System.ParamArray>] args: obj []) : obj

(+0 other overloads)

Threading.Dispatcher.Invoke(priority: Threading.DispatcherPriority, method: System.Delegate) : obj

(+0 other overloads)

Threading.Dispatcher.Invoke<'TResult>(callback: System.Func<'TResult>, priority: Threading.DispatcherPriority) : 'TResult

(+0 other overloads)

Threading.Dispatcher.Invoke(callback: System.Action, priority: Threading.DispatcherPriority) : unit

(+0 other overloads)

Threading.Dispatcher.Invoke(method: System.Delegate, timeout: System.TimeSpan, [<System.ParamArray>] args: obj []) : obj

(+0 other overloads)

Threading.Dispatcher.Invoke(method: System.Delegate, priority: Threading.DispatcherPriority, [<System.ParamArray>] args: obj []) : obj

(+0 other overloads)

Threading.Dispatcher.Invoke(priority: Threading.DispatcherPriority, timeout: System.TimeSpan, method: System.Delegate) : obj

(+0 other overloads)

Threading.Dispatcher.Invoke(priority: Threading.DispatcherPriority, method: System.Delegate, arg: obj) : obj

(+0 other overloads)