val x : int
Full name: index.x
Multiple items
val int : value:'T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
--------------------
type int = int32
Full name: Microsoft.FSharp.Core.int
--------------------
type int<'Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
val inferredX : int
Full name: index.inferredX
val helloWorld : name:string -> string
Full name: index.helloWorld
val name : string
val sprintf : format:Printf.StringFormat<'T> -> 'T
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val text : string
Full name: index.text
val person : string * int
Full name: index.person
val personShortHand : string * int
Full name: index.personShortHand
val name : string
Full name: index.name
val age : int
Full name: index.age
type Person =
{Name: string;
Age: int;}
Full name: index.Person
Person.Name: string
Multiple items
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
Person.Age: int
val me : Person
Full name: index.me
val printfn : format:Printf.TextWriterFormat<'T> -> 'T
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.printfn
val a : int
Full name: index.a
val mutable y : int
Full name: index.y
val youngerme : Person
Full name: index.youngerme
val set : elements:seq<'T> -> Set<'T> (requires comparison)
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.set
namespace Microsoft.FSharp
namespace Microsoft.FSharp.Data
namespace Microsoft.FSharp.Data.UnitSystems
namespace Microsoft.FSharp.Data.UnitSystems.SI
namespace Microsoft.FSharp.Data.UnitSystems.SI.UnitSymbols
type Direction =
| North
| South
| East
| West
Full name: index.Direction
union case Direction.North: Direction
union case Direction.South: Direction
union case Direction.East: Direction
union case Direction.West: Direction
type Weather =
| Cold of temperature: float<C>
| Sunny
| Wet
| Windy of Direction * windspeed: float<m/s>
Full name: index.Weather
union case Weather.Cold: temperature: float<C> -> Weather
Multiple items
val float : value:'T -> float (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.float
--------------------
type float = System.Double
Full name: Microsoft.FSharp.Core.float
--------------------
type float<'Measure> = float
Full name: Microsoft.FSharp.Core.float<_>
[<Measure>]
type C = Data.UnitSystems.SI.UnitNames.coulomb
Full name: Microsoft.FSharp.Data.UnitSystems.SI.UnitSymbols.C
union case Weather.Sunny: Weather
union case Weather.Wet: Weather
union case Weather.Windy: Direction * windspeed: float<m/s> -> Weather
[<Measure>]
type m = Data.UnitSystems.SI.UnitNames.metre
Full name: Microsoft.FSharp.Data.UnitSystems.SI.UnitSymbols.m
[<Measure>]
type s = Data.UnitSystems.SI.UnitNames.second
Full name: Microsoft.FSharp.Data.UnitSystems.SI.UnitSymbols.s
val weather : Weather
Full name: index.weather
val speed : float<m/s>
val temp : float<C>
active recognizer High: float<m/s> -> Choice<unit,unit,unit>
Full name: index.( |Low|Medium|High| )
namespace System
namespace System.Net
val webPageSize : Async<int>
Full name: index.webPageSize
val async : AsyncBuilder
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
val wc : WebClient
Multiple items
type WebClient =
inherit Component
new : unit -> WebClient
member BaseAddress : string with get, set
member CachePolicy : RequestCachePolicy with get, set
member CancelAsync : unit -> unit
member Credentials : ICredentials with get, set
member DownloadData : address:string -> byte[] + 1 overload
member DownloadDataAsync : address:Uri -> unit + 1 overload
member DownloadFile : address:string * fileName:string -> unit + 1 overload
member DownloadFileAsync : address:Uri * fileName:string -> unit + 1 overload
member DownloadString : address:string -> string + 1 overload
...
Full name: System.Net.WebClient
--------------------
WebClient() : unit
val result : string
member WebClient.AsyncDownloadString : address:Uri -> Async<string>
Multiple items
type Uri =
new : uriString:string -> Uri + 5 overloads
member AbsolutePath : string
member AbsoluteUri : string
member Authority : string
member DnsSafeHost : string
member Equals : comparand:obj -> bool
member Fragment : string
member GetComponents : components:UriComponents * format:UriFormat -> string
member GetHashCode : unit -> int
member GetLeftPart : part:UriPartial -> string
...
Full name: System.Uri
--------------------
Uri(uriString: string) : unit
Uri(uriString: string, uriKind: UriKind) : unit
Uri(baseUri: Uri, relativeUri: string) : unit
Uri(baseUri: Uri, relativeUri: Uri) : unit
property String.Length: int
val prettyPrintTime : unit -> unit
Full name: index.prettyPrintTime
val time : DateTime
Multiple items
type DateTime =
struct
new : ticks:int64 -> DateTime + 10 overloads
member Add : value:TimeSpan -> DateTime
member AddDays : value:float -> DateTime
member AddHours : value:float -> DateTime
member AddMilliseconds : value:float -> DateTime
member AddMinutes : value:float -> DateTime
member AddMonths : months:int -> DateTime
member AddSeconds : value:float -> DateTime
member AddTicks : value:int64 -> DateTime
member AddYears : value:int -> DateTime
...
end
Full name: System.DateTime
--------------------
DateTime()
(+0 other overloads)
DateTime(ticks: int64) : unit
(+0 other overloads)
DateTime(ticks: int64, kind: DateTimeKind) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, calendar: Globalization.Calendar) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, kind: DateTimeKind) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, calendar: Globalization.Calendar) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int) : unit
(+0 other overloads)
DateTime(year: int, month: int, day: int, hour: int, minute: int, second: int, millisecond: int, kind: DateTimeKind) : unit
(+0 other overloads)
property DateTime.UtcNow: DateTime
property DateTime.Hour: int
property DateTime.Minute: int
type 'T list = List<'T>
Full name: Microsoft.FSharp.Collections.list<_>
Multiple items
module List
from Microsoft.FSharp.Collections
--------------------
type List<'T> =
| ( [] )
| ( :: ) of Head: 'T * Tail: 'T list
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<_>
val mapi : mapping:(int -> 'T -> 'U) -> list:'T list -> 'U list
Full name: Microsoft.FSharp.Collections.List.mapi
val id : x:'T -> 'T
Full name: Microsoft.FSharp.Core.Operators.id
module Seq
from Microsoft.FSharp.Collections
val iter : action:('T -> unit) -> source:seq<'T> -> unit
Full name: Microsoft.FSharp.Collections.Seq.iter
namespace Microsoft.FSharp.Core
Multiple items
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = String
Full name: Microsoft.FSharp.Core.string
type obj = Object
Full name: Microsoft.FSharp.Core.obj
type unit = Unit
Full name: Microsoft.FSharp.Core.unit
type 'T option = Option<'T>
Full name: Microsoft.FSharp.Core.option<_>
type Attribute =
member Equals : obj:obj -> bool
member GetHashCode : unit -> int
member IsDefaultAttribute : unit -> bool
member Match : obj:obj -> bool
member TypeId : obj
static member GetCustomAttribute : element:MemberInfo * attributeType:Type -> Attribute + 7 overloads
static member GetCustomAttributes : element:MemberInfo -> Attribute[] + 15 overloads
static member IsDefined : element:MemberInfo * attributeType:Type -> bool + 7 overloads
Full name: System.Attribute
F# |> DNN
Using F# for DNN development
(With a focus on Fable/Elmish)
Stefan Cullmann
Berlin, Germany
My Job
- Head of IT
- Very small team
-
Inhouse development only
- Training & qualification
- Certification
- Conferences and Seminars
- Focus on solving problems
- Exploring and analysing the business domain
- Adapting and improving business processes
-
Software development is a learning process,
working code is a side effect
- Focus on the core domain
- Keep the implementation as close as possible to your understanding
F# Basics
- General purpose programming language
- Functional-first
- Powerful type system
- Awesome data manipulation capabilities
- Leads to the "pit of success"
The Mountain of Doom
The Pit of Success
Mutable by default |
Immutable by default |
Side-effects + statements |
Expressions |
Classes |
Functions as values |
Inheritance |
Composition |
State |
Data + pure functions |
Polymorphism |
Algebraic Data Types |
What does it mean?
- It is easy to learn
- It is very typesafe
- It encorurages experimentation
- It can be very intuitive
- It shuns ceremony
- Easier to test
- Very friendly Community
- It will open your mind to possibilities
Source: Eight reasons to learn F#
F# Primer in < 5 minutes
Values
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
|
// bind 5 to x
let x:int = 5
// type inference
let inferredX = 5
// functions are just values, don't need a class
let helloWorld(name) = sprintf "Hello, %s" name
// type inference again
let text = helloWorld "DNN-Summit"
|
Types
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
|
// Tuples are first class citizens in F#
let person = ("Stefan", 50)
let personShortHand = "Stefan", 50 // string * int
let name, age = personShortHand // decompose the tuple
// Declaring a record
type Person = { Name : string; Age : int }
// Create an instance
let me = { Name = "Stefan"; Age = 50 }
printfn "%s is %d years old" me.Name me.Age
|
Immutable by default
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
|
let a = 10
//a <- 20 // not allowed
let mutable y = 10 // need an extra keyword!
y <- 20 // ok
let me = { Name = "Stefan"; Age = 50 }
//me.Age <- 20 // not allowed
//creates a new record based on an existing one
let youngerme = {me with Age = 20}
|
Classes
1:
2:
3:
4:
5:
6:
7:
|
// Declaring a class
type Person (name:string, age:int) =
member val Name = name with get, set //auto properties
member val Age = age with get, set
let me = Person (Stefan, 50)
me.Age <- 20 // allowed
|
More Types
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
|
open FSharp.Data.UnitSystems.SI.UnitSymbols
type Direction = North | South | East | West
type Weather =
| Cold of temperature:float<C>
| Sunny
| Wet
| Windy of Direction * windspeed:float<m/s>
// Create a weather value
let weather = Windy(North, 10.2<m/s>)
let (|Low|Medium|High|) speed =
if speed > 10.<m/s> then High
elif speed > 5<m/s>. then Medium
else Low
|
Exhaustive pattern matching
1:
2:
3:
4:
5:
6:
7:
|
match weather with
| Cold temp when temp < 2.0<C> -> "Really cold!"
| Cold _ | Wet -> "Miserable weather!"
| Sunny -> "Nice weather"
| Windy (North, High) -> "High speed northernly wind!"
| Windy (South, _) -> "Blowing southwards"
| Windy _ -> "It's windy!"
|
Pipelines
Top ten most most popular counties for house sales
Asynchronous support
1:
2:
3:
4:
5:
6:
7:
|
open System
open System.Net
let webPageSize = async {
use wc = new WebClient()
let! result = wc.AsyncDownloadString(Uri "http://www.dnn-connect.org")
return result.Length }
|
No nulls!
Attention!
Whitespace sensitive
1:
2:
3:
4:
5:
|
open System
let prettyPrintTime() =
let time = DateTime.UtcNow
printfn "It is now %d:%d" time.Hour time.Minute
|
Equals is comparison!
1:
2:
|
let x = 5
x = 10 // false, COMPARISON!!!
|
REPL
- Read, Evaluate, Print Loop
- No console applications needed
- Scripts
- Explore domain quickly
- Converts quickly to full-blown assemblies
Fable
Fable
- Fable is an F# to JavaScript compiler powered by Babel
- REPL
Fable-Elmish
Build Fable apps following the Elm architecture (Model View Update)
Fable-Elmish
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
|
# Install template
dotnet new -i Fable.Template.Elmish.React
# Create project
dotnet new fable-elmish-react -n awesome
cd awesome
# Install npm dependencies
yarn install
cd src
# Install dotnet dependencies
dotnet restore
# Start Fable server and Webpack dev server
dotnet fable yarn-start
# In your browser, open: http://localhost:8080/
|
Model - View - Update
1:
2:
3:
4:
5:
6:
7:
8:
9:
|
// MODEL
type Model = int
type Msg =
| Increment
| Decrement
let init() : Model = 0
|
Model - View - Update
1:
2:
3:
4:
5:
6:
7:
|
// VIEW
let view model dispatch =
div []
[ button [ OnClick (fun _ -> dispatch Decrement) ] [ str "-" ]
div [] [ str (model.ToString()) ]
button [ OnClick (fun _ -> dispatch Increment) ] [ str "+" ] ]
|
Model - View - Update
1:
2:
3:
4:
5:
6:
|
// UPDATE
let update (msg:Msg) (model:Model) =
match msg with
| Increment -> model + 1
| Decrement -> model - 1
|
Model - View - Update
1:
2:
3:
4:
5:
6:
|
// wiring things up
Program.mkSimple init update view
|> Program.withConsoleTrace
|> Program.withReact "elmish-app"
|> Program.run
|
Sub-Components
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
|
// MODEL
type Model = {
Counters : Counter.Model list
}
type Msg =
| Insert
| Remove
| Modify of int * Counter.Msg
let init() : Model =
{ Counters = [] }
|
Sub-Components
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
|
// VIEW
let view model dispatch =
let counterDispatch i msg = dispatch (Modify (i, msg))
let counters =
model.Counters
|> List.mapi (fun i c -> Counter.view c (counterDispatch i))
div [] [
yield button [ OnClick (fun _ -> dispatch Remove) ] [ str "Remove" ]
yield button [ OnClick (fun _ -> dispatch Insert) ] [ str "Add" ]
yield! counters ]
|
Sub-Components
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
17:
18:
19:
|
// UPDATE
let update (msg:Msg) (model:Model) =
match msg with
| Insert ->
{ Counters = Counter.init() :: model.Counters }
| Remove ->
{ Counters =
match model.Counters with
| [] -> []
| x :: rest -> rest }
| Modify (id, counterMsg) ->
{ Counters =
model.Counters
|> List.mapi (fun i counterModel ->
if i = id then
Counter.update counterMsg counterModel
else
counterModel) }
|
And now within DNN
DNN and F#?
DNN and F#!
Simple DNN Spa module
1:
2:
3:
4:
5:
|
[AntiForgeryToken: {}]
[JavaScript:{
path: "http://localhost:8080/bundle.js",
provider:"DnnFormBottomProvider"}]
<div id="elmish-todo" data-moduleId = "[ModuleContext:ModuleId]"></div>
|
- During development, link to script on webpackageserver
SPA? Or multiple SPAs on one page?
1:
2:
3:
|
[AntiForgeryToken: {}]
[JavaScript:{path: "http://localhost:8080/bundle.js", provider:"DnnFormBottomProvider"}]
<div class="elmish-module" data-moduleId = "[ModuleContext:ModuleId]"></div>
|
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
|
let runApp elementId =
Program.mkSimple init update view
|> Program.withReact elementId
|> Program.run
let runApps className =
let elements = document.getElementsByClassName className
let runOnElement index =
let elementId = sprintf "%s-%i" className index
elements.[index].id <- elementId
runApp elementId
Seq.iter (runOnElement) [0 .. int elements.length - 1]
runApps "elmish-module"
|
Modify webpack.config.js for HMR
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
16:
|
module.exports = {
devtool: "source-map",
entry: resolve('./src/elmish.fsproj'),
output: {
filename: 'bundle.js',
path: resolve('./public'),
publicPath: 'http://localhost:8080/',
},
devServer: {
contentBase: resolve('./public'),
headers: { "Access-Control-Allow-Origin": "*" },
port: 8080,
hot: true,
inline: true
},
...
|
JS-Interop with Dnn ServicesFramework
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
|
open Fable.Core
type IServicesFramework =
abstract getServiceRoot: string -> string
abstract setModuleHeaders: obj -> unit
abstract getTabId : unit -> int option
abstract getModuleId : unit -> int option
abstract getAntiForgeryValue : unit -> string option
[<Emit("window['$'].ServicesFramework($0)")>]
let ServiceFramework (moduleid:int) : IServicesFramework = jsNative
|
Use Fable.JsonConverter within WebApi
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13:
14:
15:
|
type FableConfigAttribute () =
inherit System.Attribute()
interface IControllerConfiguration with
member __.Initialize ((controllerSettings:HttpControllerSettings), _) =
let fableFormatter =
JsonMediaTypeFormatter (
SerializerSettings = JsonSerializerSettings (
Converters = [|Fable.JsonConverter()|]))
controllerSettings.Formatters.Clear ()
controllerSettings.Formatters.Add fableFormatter
[<FableConfig>]
type FableController () =
inherit DnnApiController ()
|
Share Types and Functions
Either reference a .fs from the WebApi project also in your Elmish project
or use a NetStandard f# Class Lib and reference it from both projects.
Yes, that's Universal F#