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
Hello Hello. My self Kunjan Dalal, also known as Kunjee. I will be skipping other parts of the intro; because we are having some time issues.
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!!!
This talk was 45 mins long when I submitted. It has been cut down to 20 minutes due to other nice talks in the same area. So, I will also be cutting down a few things you might already know from other talks at this conference.
Pop Quiz Time
How many of you know about Elm?
How many are using Elm in a production application? A whole application or may be part of it?
Here is an important question. How many of you feel that the nice demo application is shown in the conference never get converted to the full-blown application?
Demo
We will not see any demo today.
Instead will talk about the past, more about what is possible in contrast to what can be possible.
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#.
In this slides, all code samples will be in 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.
There are two different things in Fable Elmish. Fable and Fable-Elmish or Elmish generally speaking. Let's have a look at both.
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
Here is a simple F# code with its specific Unit of Measure feature. A tight guard against how you define units. It is very helpful in the industry like Finance, Biology, Research etc.
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 );
Have a look at this. A very readable JavaScript. Which then pass on to Bable to boil down to normal JavaScript which can be understood by the browser.
Elmish
Elmish - Types
1:
2:
3:
4:
5:
type Model =
{ Value : string }
type Msg =
| ChangeValue of string
Your routine model with a single property of type String. To change that we need to pass the message of value 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
Init is initializing with an empty value and then update will take care of every change happen in the application.
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 "!" ] ]
Here is a simple view that takes the model and shows it to a user. Also, it can dispatch a message to update method.
Elmish - Main
1:
2:
3:
4:
5:
// App
Program . mkProgram init update view
|> Program . withConsoleTrace
|> Program . withReact "elmish-app"
|> Program . run
And here is wire up a code. Not much different than your regular Elm code. It is following the elm architecture.
What is SPA ?
When they told us about Single Page Application, they promise us that you don't need to learn any other thing other than HTML, javascript, and CSS. They promise a triangle of power, but instead what we got?
Today I will be providing tips and tricks for your Elm / Elmish application. I wish you will take the advice as it is. Just like we used to take advise of a wise old man. Respecting time nothing else.
Model - View - Update
In MVU architecture I feel that the model is the most important thing. It is a message between update and view, and whoever controls the message controls the narrative.
So, my tips and tricks will start from that only.
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
An Authenticated user can access the application module and all users can access the Authenticate module. If you are starting your application and you are having user authentication in your application then you surely like to do this. If you are having 2 - 3 pages where a user needs to be authenticated then it is ok but not more than that; you seriously like to save your self from repeated user verification code.
Pro tip - Use JWT if you are still not using it. It is nice to have when you are developing a Stateless application.
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
There are two ways of writing page-model. But having page model as discriminated union type always helps. You are only loading data that is required for a page, nothing else.
Pro tip - You can group pages by Navigation. So, one less naming problem solve.
Page - Model
1:
2:
3:
4:
5:
type HomeModel = {
Component1 : Component . Types . Model
Component2 : Component . Types . Model
.. ..
}
Divide your page into multiple components. Even if your page is having a single big entry form, divide that into a logical group. It always helps to manage small chunks of code compared to a big chunk.
Even simple entry forms are doing many things, like validation on a client-side and validation on the server side as if a username is available or not.
Features, like autocomplete will, requires a server and client-side code.
Component Model
We almost reached halfway, so it is time for a little break.
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
}
Most dumb domain model example to give an idea. Here just to show that first name should not be empty I need to update model, then Person-Error, then Validate and then message. It is three level deep. One can always use lenses to update them, but if you need to use lenses then you are doing something wrong. As model represent the view, we need to keep it as flat as possible. Use of lenses will become more and more complicated as an application grows. Instead, divide model in multiple models in MVU fashions.
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
}
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
One command should be doing one and only one thing. Here I capitalizing everything coming into the update function. Just because why not? But you can do validation or and post data to a server.
As command processes, the data, pass it to Changed message. It is confirmation that everything is good to go.
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
This is a famous subscribe example. But it is way more powerful than this. Let's have another example where it can be used.
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
Be it Real-time or reactive application; Subscribe is pretty useful. Here you can subscribe to events those are coming from the server. Events will update the UI eventually.
1:
2:
3:
4:
let root model dispatch =
Container . container [ Container . IsFluid ][
documentTable model dispatch
]
A view should be Small & cute. It should be a representation of the whole page. So, if someone is reading root function, s/he will get the idea.
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" ] ] ]
It will make so much easier to write UIs with wrappers. Please use them.
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
Here is React Leaflet example in F#. No need to create graph library from bottom up.
Html to Elmish
Convert your html to elmish code.
Ts 2 Fable
And this is my favorite one.
Convert your typescript definitions to F#
Here is an awesome fable link where I am maintaining all awesome stuff happening in Fable world.
Safe-Stack is just like MEAN-Stack but everything in F#. Even if you are not interested in F# then also do check it out. It is featured in Thought works Tech Radar recently.
And I personally feel that every Functional Programming language should have similar kind of stack.
Then there is What the F# podcast, Where I and Alfonso Garcia Caro was talking about Elmish.
There is also a link for fulma
Balero is recently released, framework where you code in Elmish pattern and it will deploy your code in web-assembly
And the obvious fable link. Do try out Fable REPL, it is compiling whole F# code in the browser itself. So, your code will totally work in offline mode.
Fuzzy Cloud is consulting things I started recently. I am available for training and consulting for everything related but not limited to Functional Programming.
I am carrying my visiting card with me if anyone likes to have.
If you have enjoyed this talk please tag Fable Compiler and me (I am totally optional) with your good comments.
Please provide your criticism and inputs to me, I will surely try to improve for next talk.
Any questions? Else I am available here only for today and tomorrow. Do stay in touch.