namespace Microsoft
namespace Microsoft.FSharp
namespace Microsoft.FSharp.Data
namespace Microsoft.FSharp.Data.UnitSystems
namespace Microsoft.FSharp.Data.UnitSystems.SI
namespace Microsoft.FSharp.Data.UnitSystems.SI.UnitNames
Multiple items
type MeasureAttribute =
  inherit Attribute
  new : unit -> MeasureAttribute

Full name: Microsoft.FSharp.Core.MeasureAttribute

--------------------
new : unit -> MeasureAttribute
val printfn : format:Printf.TextWriterFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
Multiple items
val string : value:'T -> string

Full name: Microsoft.FSharp.Core.Operators.string

--------------------
type string = System.String

Full name: Microsoft.FSharp.Core.string
Multiple items
type ClassAttribute =
  inherit Attribute
  new : unit -> ClassAttribute

Full name: Microsoft.FSharp.Core.ClassAttribute

--------------------
new : unit -> ClassAttribute
type 'T option = Option<'T>

Full name: Microsoft.FSharp.Core.option<_>
type bool = System.Boolean

Full name: Microsoft.FSharp.Core.bool
Multiple items
module Event

from Microsoft.FSharp.Control

--------------------
type Event<'T> =
  new : unit -> Event<'T>
  member Trigger : arg:'T -> unit
  member Publish : IEvent<'T>

Full name: Microsoft.FSharp.Control.Event<_>

--------------------
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>

Full name: Microsoft.FSharp.Control.Event<_,_>

--------------------
new : unit -> Event<'T>

--------------------
new : unit -> Event<'Delegate,'Args>
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
Multiple items
module List

from Microsoft.FSharp.Collections

--------------------
type List<'T> =
  | ( [] )
  | ( :: ) of Head: 'T * Tail: 'T list
  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
  static member Cons : head:'T * tail:'T list -> 'T list
  static member Empty : 'T list

Full name: Microsoft.FSharp.Collections.List<_>
namespace Microsoft.FSharp.Control
Multiple items
type DefaultValueAttribute =
  inherit Attribute
  new : unit -> DefaultValueAttribute
  new : check:bool -> DefaultValueAttribute
  member Check : bool

Full name: Microsoft.FSharp.Core.DefaultValueAttribute

--------------------
new : unit -> DefaultValueAttribute
new : check:bool -> DefaultValueAttribute

Scaling Fable Elmish / Elm Application

An Experience Report By


Kunjan Dalal

@kunjee


Agenda for Today

  • Intro to me
  • Intro to Elm
  • Intro to Fable & Fable.Elmish
  • Basic Example
  • The Elm Architecture - TEA
  • Tips and Tricks for Scaling your Application
  • Why Elmish?
  • External JS library in Fable Elmish
  • External React library in Fable Elmish
  • SAFE stack
  • Thank You!!!

Pop Quiz Time

Demo

Why Code Samples will be in F#?

It is a general-purpose functional programming language. It is a very much mainstream language. It works for web and mobile. It is very much strongly type language. It is F#.

Intro to Fable & Fable Elmish

Fable is an F# to JavaScript compiler powered by Babel, designed to produce readable and standard code.


Elmish implements core abstractions that can be used to build Fable applications following the “model view update” style of architecture, as made famous by Elm.

Fable

F# unit of Measure code
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
    module Tour.UnitsOfMeasure

    // From https://docs.microsoft.com/en-us/dotnet/fsharp/tour
    open Microsoft.FSharp.Data.UnitSystems.SI.UnitNames

    let sampleValue1 = 1600.0<meter>

    [<Measure>]
    type mile =
        static member asMeter = 1609.34<meter/mile>

    let sampleValue2 = 500.0<mile>

    let sampleValue3 = sampleValue2 * mile.asMeter

    printfn "After a %f race I would walk %f miles
            which would be %f meters" sampleValue1 sampleValue2 sampleValue3
Converted Java Script Code
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
    import { toConsole, printf } from "fable-core/String";
    export const sampleValue1 = 1600;
    export function mile$$$get_asMeter() {
    return 1609.34;
    }
    export const sampleValue2 = 500;
    export const sampleValue3 = sampleValue2 * mile$$$get_asMeter();
    toConsole(
        printf("After a %f race I would walk %f miles which would be %f meters"))
            (sampleValue1)(sampleValue2)(sampleValue3);

Elmish

Elmish - Types
1: 
2: 
3: 
4: 
5: 
    type Model =
        { Value : string }

    type Msg =
        | ChangeValue of string
Elmish - State
1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
    let init () = { Value = "" }, Cmd.none

    // UPDATE

    let update (msg:Msg) (model:Model) =
        match msg with
        | ChangeValue newValue ->
            { Value = newValue }, Cmd.none
Elmish - View
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
    let view model dispatch =
        div [ Class "main-container" ]
            [ input [ Class "input"
                    Value model.Value
                    OnChange (fun ev -> ev.target?value |>
                                string |> ChangeValue |> dispatch) ]
            span [ ]
                [ str "Hello, "
                str model.Value
                str "!" ] ]
Elmish - Main
1: 
2: 
3: 
4: 
5: 
    // App
    Program.mkProgram init update view
    |> Program.withConsoleTrace
    |> Program.withReact "elmish-app"
    |> Program.run

What is SPA ?

Model - View - Update

Let's Talk about Model

Application & Authentication

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
type Msg =
    | ApplicationMsg of Application.Types.Msg
    | AuthenticationMsg of Authentication.Types.Msg

type PageModel =
    | ApplicationModel of Application.Types.Model
    | AuthenticationModel of Authentication.Types.Model

type Model = {
    UserToken : AuthToken option
    CurrentPage : Page
    PageModel : PageModel
}
 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
match page with
| AuthenticationPage a ->
    let (authentication, authenticationCmd) =
        Authentication.State.init(a)
    in
    {model with CurrentPage = AuthenticationPage a;
        PageModel = AuthenticationModel authentication},
            Cmd.map AuthenticationMsg authenticationCmd

| ApplicationPage a ->
    if model.UserToken.IsNone
    then model, Navigation.modifyUrl(toHash (AuthenticationPage Login))
    else
    let (application, applicationCmd) =
        Application.State.init(a,model.UserToken.Value)
    in
    {model with CurrentPage = ApplicationPage a;
        PageModel = ApplicationModel application},
            Cmd.map ApplicationMsg applicationCmd

Page Model

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
module Types =

type BadPageModel = {
    HomeModel : Home.Types.Model
    UsersModel : Users.Types.Model
    ProfileModel : Profile.Types.Model
}

type GoodPageModel =
    | HomeModel of Home.Types.Model
    | UsersModel of Users.Types.Model
    | ProfileModel of Profile.Types.Model

Page - Model

1: 
2: 
3: 
4: 
5: 
type HomeModel = {
    Component1 : Component.Types.Model
    Component2 : Component.Types.Model
    ....
}

Component Model

Domain - Driven - Design

Domain Model on Client Side

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
type Validate = {
    IsValid : bool
    Message : string
}

type Person = {
    FirstName : string
    LastName : string
}

type PersonError = {
    FirstName : Validate
    LastName : Validate
}

type Model = {
    Person : Person
    PersonError : PersonError
}

Flatten the model

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
type Validate = {
    IsValid : bool
    Message : string
}

type FirstName = {
    Value : string
    Valid : Validate
}

type LastName = {
    Value : string
    Valid : Validate
}

type Person = {
    FirstName : FirstName
    LastName : LastName
}
OR
1: 
2: 
3: 
4: 
5: 
6: 
type Person = {
    FirstName: string
    FirstNameErr : Validate
    LastName : string
    LastNameErr : Validate
}

Let's Talk about Command

Single Responsibly Principal

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
type Msg =
    | ChangeValue of string
    | Changed of string

let init () = { Value = "" }, Cmd.none

let update (msg:Msg) (model:Model) =
    match msg with
    | ChangeValue newValue ->
        let updateValue = newValue.ToUpper()
        model, Cmd.ofMsg (Changed updateValue)
    | Changed updatedValue ->
        { Value = updatedValue}, Cmd.none

Another way to Look at it

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
19: 
type Command = | ChangeValue of string
type Event = | Changed of string

type Msg =
    | Command of Command
    | Event of Event

let init () = { Value = "" }, Cmd.none

let processCommand (msg : Command) (model : Model) =
    match msg with
    | ChangeValue newValue ->
        let updateValue = newValue.ToUpper()
        model, Cmd.ofMsg (Event (Changed updateValue))

let processEvent (msg : Event) (model: Model) =
    match msg with
    | Changed value ->
        { Value = value}, Cmd.none
1: 
2: 
3: 
4: 
let update (msg:Msg) (model:Model) =
    match msg with
    | Command c -> processCommand c model
    | Event e -> processEvent e model

Subscribe is there to Use

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
// Types
type Model = CurrentTime of DateTime
type Messages = Tick of DateTime

// State
let initialState() =
    CurrentTime DateTime.Now, Cmd.none

let update (Tick next) (CurrentTime _time) =
    CurrentTime next, Cmd.none

let timer initial =
    let sub dispatch =
        Browser.window.setInterval(fun _ ->
            dispatch (Tick DateTime.Now)
        , 1000) |> ignore
    Cmd.ofSub sub

Real Time or Reactive Application

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
let subscribe =
        let socketSubscription dispatch =
            let eventSourceOptions = createEmpty<IEventSourceOptions>
            eventSourceOptions.handlers <- createObj [
                "onMessage" ==>
                    fun (msg: ServerEventMessage) ->
                        printfn "onMessage %A" msg.json
                "chat" ==>
                    fun (msg : OutputMessages) ->
                                msg |> (SSESuccessMessages >> dispatch)
            ]

            let channels = [|"home"; ""|]
            SSClient.ServerEventsClient.Create(baseUrl
            , new List<string>(channels)
            , eventSourceOptions
            ).start() |> ignore
        Cmd.ofSub socketSubscription

Let's Talk about View

1: 
2: 
3: 
4: 
let root model dispatch =
    Container.container[ Container.IsFluid ][
        documentTable model dispatch
    ]

Use CSS Wrappers like Fulma

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
form [ ]
        [ // Email field
            Field.div [ ]
                [ Label.label [ ]
                    [ str "Email" ]
                  Control.div [ Control.HasIconLeft
                                Control.HasIconRight ]
                    [ Input.email [ Input.Color IsDanger
                                    Input.DefaultValue "hello@" ]
                      Icon.faIcon [ Icon.Size IsSmall; Icon.IsLeft ]
                        [ Fa.icon Fa.I.Envelope ]
                      Icon.faIcon [ Icon.Size IsSmall; Icon.IsRight ]
                        [ Fa.icon Fa.I.Warning ] ]
                  Help.help [ Help.Color IsDanger ]
                    [ str "This email is invalid" ] ] ]

Why I choose Elmish?

  • One Language of for Server, Client (Web & Mobile), Data Science / ML & Data Processing etc.
  • Possible to leverage JavaScript libraries (Stateless one)
  • Possible to leverage React libraries (Stateless one)
  • Because of all these, I can leverage my experience, I acquired throughout all these years.

React Leaflet

Awesome Tool Chain

Html to Elmish

Ts 2 Fable