0 to Production in Twelve Weeks with F# on the Web
namespace Microsoft
namespace System
namespace System.Collections
namespace System.Collections.Generic
namespace System.Net
namespace System.Net.Http
namespace System.Threading
namespace System.Threading.Tasks
namespace System.Web
Multiple items
val Failure : message:string -> exn

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

--------------------
active recognizer Failure: exn -> string option

Full name: Microsoft.FSharp.Core.Operators.( |Failure|_| )
union case Choice.Choice1Of2: 'T1 -> Choice<'T1,'T2>
val x : 'a
union case Choice.Choice2Of2: 'T2 -> Choice<'T1,'T2>
val x : 'b
active recognizer Failure: exn -> string option

Full name: Microsoft.FSharp.Core.Operators.( |Failure|_| )
Multiple items
type LiteralAttribute =
  inherit Attribute
  new : unit -> LiteralAttribute

Full name: Microsoft.FSharp.Core.LiteralAttribute

--------------------
new : unit -> LiteralAttribute
val connectionString : string

Full name: Document.connectionString
val key : string

Full name: Document.key
type AppDomain =
  inherit MarshalByRefObject
  member ActivationContext : ActivationContext
  member AppendPrivatePath : path:string -> unit
  member ApplicationIdentity : ApplicationIdentity
  member ApplicationTrust : ApplicationTrust
  member ApplyPolicy : assemblyName:string -> string
  member BaseDirectory : string
  member ClearPrivatePath : unit -> unit
  member ClearShadowCopyPath : unit -> unit
  member CreateComInstanceFrom : assemblyName:string * typeName:string -> ObjectHandle + 1 overload
  member CreateInstance : assemblyName:string * typeName:string -> ObjectHandle + 3 overloads
  ...

Full name: System.AppDomain
property AppDomain.CurrentDomain: AppDomain
AppDomain.SetData(name: string, data: obj) : unit
AppDomain.SetData(name: string, data: obj, permission: Security.IPermission) : unit
val connString : Lazy<obj>

Full name: Document.connString
AppDomain.GetData(name: string) : obj
Multiple items
namespace System.Data

--------------------
namespace Microsoft.FSharp.Data
type GetTodos = obj

Full name: Document.GetTodos
type GetTodo = obj

Full name: Document.GetTodo
val result : obj

Full name: Document.result
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
val cmd : GetTodo
val id : x:'T -> 'T

Full name: Microsoft.FSharp.Core.Operators.id
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<'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 FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken

Full name: Microsoft.FSharp.Control.Async

--------------------
type Async<'T>

Full name: Microsoft.FSharp.Control.Async<_>
static member Async.RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
val todo1 : obj

Full name: Document.todo1
val todo2 : obj

Full name: Document.todo2
type Req = HttpRequestMessage

Full name: Document.Req
Multiple items
type HttpRequestMessage =
  new : unit -> HttpRequestMessage + 2 overloads
  member Content : HttpContent with get, set
  member Dispose : unit -> unit
  member Headers : HttpRequestHeaders
  member Method : HttpMethod with get, set
  member Properties : IDictionary<string, obj>
  member RequestUri : Uri with get, set
  member ToString : unit -> string
  member Version : Version with get, set

Full name: System.Net.Http.HttpRequestMessage

--------------------
HttpRequestMessage() : unit
HttpRequestMessage(method: HttpMethod, requestUri: Uri) : unit
HttpRequestMessage(method: HttpMethod, requestUri: string) : unit
type Res = HttpResponseMessage

Full name: Document.Res
Multiple items
type HttpResponseMessage =
  new : unit -> HttpResponseMessage + 1 overload
  member Content : HttpContent with get, set
  member Dispose : unit -> unit
  member EnsureSuccessStatusCode : unit -> HttpResponseMessage
  member Headers : HttpResponseHeaders
  member IsSuccessStatusCode : bool
  member ReasonPhrase : string with get, set
  member RequestMessage : HttpRequestMessage with get, set
  member StatusCode : HttpStatusCode with get, set
  member ToString : unit -> string
  ...

Full name: System.Net.Http.HttpResponseMessage

--------------------
HttpResponseMessage() : unit
HttpResponseMessage(statusCode: HttpStatusCode) : unit
Multiple items
namespace System.Net.Http

--------------------
type Http = Req -> Res

Full name: Document.Http
type HttpWebApi = Req -> Task<Res>

Full name: Document.HttpWebApi
Multiple items
type Task =
  new : action:Action -> Task + 7 overloads
  member AsyncState : obj
  member ContinueWith : continuationAction:Action<Task> -> Task + 9 overloads
  member CreationOptions : TaskCreationOptions
  member Dispose : unit -> unit
  member Exception : AggregateException
  member Id : int
  member IsCanceled : bool
  member IsCompleted : bool
  member IsFaulted : bool
  ...

Full name: System.Threading.Tasks.Task

--------------------
type Task<'TResult> =
  inherit Task
  new : function:Func<'TResult> -> Task<'TResult> + 7 overloads
  member ContinueWith : continuationAction:Action<Task<'TResult>> -> Task + 9 overloads
  member Result : 'TResult with get, set
  static member Factory : TaskFactory<'TResult>

Full name: System.Threading.Tasks.Task<_>

--------------------
Task(action: Action) : unit
Task(action: Action, cancellationToken: CancellationToken) : unit
Task(action: Action, creationOptions: TaskCreationOptions) : unit
Task(action: Action<obj>, state: obj) : unit
Task(action: Action, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : unit
Task(action: Action<obj>, state: obj, cancellationToken: CancellationToken) : unit
Task(action: Action<obj>, state: obj, creationOptions: TaskCreationOptions) : unit
Task(action: Action<obj>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : unit

--------------------
Task(function: Func<'TResult>) : unit
Task(function: Func<'TResult>, cancellationToken: CancellationToken) : unit
Task(function: Func<'TResult>, creationOptions: TaskCreationOptions) : unit
Task(function: Func<obj,'TResult>, state: obj) : unit
Task(function: Func<'TResult>, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : unit
Task(function: Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken) : unit
Task(function: Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : unit
Task(function: Func<obj,'TResult>, state: obj, cancellationToken: CancellationToken, creationOptions: TaskCreationOptions) : unit
type HttpFSharp = Req -> Async<Res>

Full name: Document.HttpFSharp
type HiddenState = Req -> Async<Res>

Full name: Document.HiddenState
type ExplicitState<'T> = Req -> (Req -> Async<'T>) -> ('T -> 'T) -> Async<Res>

Full name: Document.ExplicitState<_>
Multiple items
type SealedAttribute =
  inherit Attribute
  new : unit -> SealedAttribute
  new : value:bool -> SealedAttribute
  member Value : bool

Full name: Microsoft.FSharp.Core.SealedAttribute

--------------------
new : unit -> SealedAttribute
new : value:bool -> SealedAttribute
Multiple items
type Startup =
  new : unit -> Startup
  member Configuration : builder:'a -> unit

Full name: Document.Startup

--------------------
new : unit -> Startup
Multiple items
member Startup.Configuration : builder:'a -> unit

Full name: Document.Startup.Configuration

--------------------
namespace System.Net.Configuration

--------------------
namespace System.Configuration
val builder : 'a
val config : obj
val ignore : value:'T -> unit

Full name: Microsoft.FSharp.Core.Operators.ignore
Multiple items
type TodosController =
  inherit obj
  new : unit -> TodosController
  member GetTodos : unit -> 'a

Full name: Document.TodosController

--------------------
new : unit -> TodosController
Multiple items
member TodosController.GetTodos : unit -> 'a

Full name: Document.TodosController.GetTodos

--------------------
type GetTodos = obj

Full name: Document.GetTodos
type Array =
  member Clone : unit -> obj
  member CopyTo : array:Array * index:int -> unit + 1 overload
  member GetEnumerator : unit -> IEnumerator
  member GetLength : dimension:int -> int
  member GetLongLength : dimension:int -> int64
  member GetLowerBound : dimension:int -> int
  member GetUpperBound : dimension:int -> int
  member GetValue : params indices:int[] -> obj + 7 overloads
  member Initialize : unit -> unit
  member IsFixedSize : bool
  ...

Full name: System.Array
val map : mapping:('T -> 'U) -> array:'T [] -> 'U []

Full name: Microsoft.FSharp.Collections.Array.map
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
static member Async.StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
val handle : request:Req -> Async<'a>

Full name: Document.handle
val request : Req
val todos : obj []
val todos' : obj []
val x : obj
val baseUri : string
property HttpRequestMessage.RequestUri: Uri
property Uri.AbsoluteUri: string
Multiple items
type TodoController =
  inherit obj
  new : unit -> TodoController
  member GetTodos : request:'a -> 'b

Full name: Document.TodoController

--------------------
new : unit -> TodoController
Multiple items
member TodoController.GetTodos : request:'a -> 'b

Full name: Document.TodoController.GetTodos

--------------------
type GetTodos = obj

Full name: Document.GetTodos
val getResource : request:Req -> Async<Uri * 'a>

Full name: Document.getResource
val todos : 'a
val mapValues : uri:Uri * todos:'a [] -> Uri * 'b []

Full name: Document.mapValues
val uri : Uri
val todos : 'a []
val todos' : 'b []
module Seq

from Microsoft.FSharp.Collections
val toArray : source:seq<'T> -> 'T []

Full name: Microsoft.FSharp.Collections.Seq.toArray
val makeHandler : request:Req -> requestF:(Req -> Async<'a>) -> domainF:('a -> 'b * 'c) -> Async<'d>

Full name: Document.makeHandler
val requestF : (Req -> Async<'a>)
val domainF : ('a -> 'b * 'c)
val res : 'a
val values : 'c
val handler : request:Req -> Async<'a>

Full name: Document.handler
val domainLogic : value:string -> string

Full name: Document.Domain.domainLogic
val value : string
Multiple items
val string : value:'T -> string

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

--------------------
type string = String

Full name: Microsoft.FSharp.Core.string
String.ToUpper() : string
String.ToUpper(culture: Globalization.CultureInfo) : string
val unwrapRequest : request:Req -> Async<string>

Full name: Document.Web.unwrapRequest
property HttpRequestMessage.Content: HttpContent
HttpContent.ReadAsStringAsync() : Task<string>
static member Async.AwaitTask : task:Task<'T> -> Async<'T>
val wrapResponse : value:'a -> request:Req -> 'b

Full name: Document.Web.wrapResponse
val value : 'a
val handle : request:Req -> Async<'a>

Full name: Document.App.handle
Multiple items
module Web

from Document

--------------------
namespace System.Web
val value' : string
module Domain

from Document
module Sample

from Document
val getHandler : request:Req -> Async<'a>

Full name: Document.Sample.getHandler
val postHandler : request:Req -> Async<'a>

Full name: Document.Sample.postHandler
type HttpStatusCode =
  | Continue = 100
  | SwitchingProtocols = 101
  | OK = 200
  | Created = 201
  | Accepted = 202
  | NonAuthoritativeInformation = 203
  | NoContent = 204
  | ResetContent = 205
  | PartialContent = 206
  | MultipleChoices = 300
  ...

Full name: System.Net.HttpStatusCode
field HttpStatusCode.Created = 201
val sampleResource : obj

Full name: Document.Sample.sampleResource
val registerSample : config:'a -> 'b

Full name: Document.Sample.registerSample
val config : 'a

F# on the Web

0 to Production in 12 Weeks

Ryan Riley

Tachyus logo

Agenda

  • Tachyus' success with F#
  • Build an F# web API
    • Getting started
    • Data access
    • Web APIs
    • Domain mapping
    • Simplify
  • Questions

Tachyus

Measure

Analyze

Produce

Est. 2014

Starting Point

  • No team
  • First client's existing systems:
    • PHP-based web app for reporting
    • Paper/pencil data collection from oil fields

Twelve Weeks Later ...

Team

  • 11 total
  • 5 Software Engineering Team
  • 3 Experienced F# Developers
  • 2 Visual F# MVPs

Modular SPA

  • Monitoring
  • Reporting
  • Data Correction
  • Administration

Mobile

  • Data collection
  • Synchronization

Web APIs

  • Mobile sync API
  • Calculation engines for reports and monitoring
  • Domain to Web API mapping <- our focus

Why F#?

F# Software Foundation

Initially Skeptical

Prototyping

Talent Pool Assessment

Why We Chose F#

People

  • Solved a talent problem
  • Attract really good people
  • Good fit for data analysts

Active, Strong Community

I like this community better than any community I've seen out there.
Dakin Sloss, Tachyus Founder & CEO

Why We Chose F#

Technical

  • Domain-focused programming
  • Math / science-focused solution
  • Full .NET compatibility
  • Cross-platform

Getting Started

Demo:

F# MVC 5 Template

  1. File -> New -> Project
  2. F# ASP.NET MVC 5 and Web API 2
  3. Choose empty Web API project

Project:

Todo Backend

http://todobackend.com/

F# and Data Access

LINQ to SQL or Entity Framework?

FSharp.Data.SqlClient

SQL is the best DSL for working with data

Quick Sample

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
open FSharp.Data

type GetTodos = SqlCommandProvider<"
    select Id, Title, Completed, [Order] from Todo", connectionString>
type GetTodo = SqlCommandProvider<"
    select Id, Title, Completed, [Order] from Todo where Id = @id
    ", connectionString, SingleRow = true>

let result =
    async {
        use cmd = new GetTodo()
        return! cmd.AsyncExecute(id = 1) }
    |> Async.RunSynchronously

let todo1 = GetTodo.Record(0, "New todo", false, 1)
let todo2 = GetTodo.Record(Id = 0, Title = "New todo",
                           Completed = false, Order = 2)

Project:

Implement Todo Data Access

F# on the Web

HTTP is Functional

1: 
2: 
3: 
4: 
type Req = HttpRequestMessage
type Res = HttpResponseMessage

type Http = Req -> Res

HTTP in ASP.NET Web API

1: 
type HttpWebApi = Req -> Task<Res>

HTTP in F#

1: 
type HttpFSharp = Req -> Async<Res>

What About Resource State?

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
type HiddenState = Req -> Async<Res>

type ExplicitState<'T> =
    Req ->
     // Retrieve resource state from the `Req`
     (Req -> Async<'T>) ->
     // Execute domain logic
     ('T -> 'T) ->    // or ('T -> Async<'T>)
     // Generate the response
     Async<Res>

Project:

Implement Todo Backend

Add the following NuGet packages:

  • Microsoft.AspNet.WebApi.Owin
  • Microsoft.Owin.Cors
  • Microsoft.Owin.Host.SystemWeb

Configure

Startup.fs

1: 
2: 
3: 
4: 
5: 
6: 
7: 
8: 
9: 
[<Sealed>]
type Startup() =
    member __.Configuration(builder: IAppBuilder) =
        let config = new HttpConfiguration()
        config.MapHttpAttributeRoutes()
        builder
            .UseCors(Cors.CorsOptions.AllowAll)
            .UseWebApi(config)
        |> ignore

HTTP in ASP.NET Web API

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
[<RoutePrefix("webapi")>]
[<Route("")>]
type TodosController() =
    inherit ApiController()

    member this.GetTodos() =
        async {
            let! todos = Sql.store.GetAll()
            let todos' =
                todos
                |> Array.map (fun x ->
                    let baseUri = this.Request.RequestUri.AbsoluteUri
                    { Url = Uri(baseUri + x.Id.ToString())
                      Title = x.Title
                      Completed = x.Completed
                      Order = x.Order })
            return this.Request.CreateResponse(todos') }
        |> Async.StartAsTask

Modularize for Composition

Extract the handler function

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
let handle (request: Req) = async {
    let! todos = Sql.store.GetAll()
    let todos' =
        todos
        |> Array.map (fun x ->
            let baseUri = request.RequestUri.AbsoluteUri
            { Url = Uri(baseUri + x.Id.ToString())
              Title = x.Title
              Completed = x.Completed
              Order = x.Order })
    return request.CreateResponse(todos') }

[<Route("webapi")>]
type TodoController() =
    inherit ApiController()
    member this.GetTodos(request) =
        handle request |> Async.StartAsTask

Why Extract the Handlers?

The web is a delivery mechanism.

Extract the Request Mapping Function

1: 
2: 
3: 
let getResource (request: Req) = async {
    let! todos = Sql.store.GetAll()
    return request.RequestUri, todos }

Extract the Domain Function

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
let mapValues (uri: Uri, todos: NewTodo[]) =
    let todos' =
        todos
        |> Array.map (fun x ->
            { Url = Uri(uri.AbsoluteUri + x.Id.ToString())
              Title = x.Title
              Completed = x.Completed
              Order = x.Order })
        |> Seq.toArray
    uri, todos'

Compose

1: 
2: 
3: 
4: 
5: 
6: 
let makeHandler (request: Req) requestF domainF = async {
    let! res = requestF request
    let (_, values) = domainF res
    return request.CreateResponse(values) }

let handler request = makeHandler request getResource mapValues

Map Domain onto the Web

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
module Domain =
    let domainLogic (value: string) = value.ToUpper()

module Web =
    let unwrapRequest (request: Req) = async {
        return! request.Content.ReadAsStringAsync() |> Async.AwaitTask }

    let wrapResponse value (request: Req) = request.CreateResponse(value)

module App =
    let handle request = async {
        let! value = Web.unwrapRequest request
        let value' = Domain.domainLogic value
        return Web.wrapResponse value' request }

Remaining Problems

I think you should be more explicit here in step two

Remaining Problems

  • The Web API controller cannot be nested under a module
  • Web API controller must be named with Controller as the suffix
  • Routing has implementation-related limitations

Skip Web API Altogether

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
12: 
13: 
14: 
15: 
16: 
17: 
18: 
open Frank
open System.Web.Http.HttpResource

module Sample =
    let getHandler (request: Req) = async {
        return request.CreateResponse() }

    let postHandler (request: Req) = async {
        let! value = request.Content.ReadAsStringAsync() |> Async.AwaitTask
        // Do something with value
        return request.CreateResponse(HttpStatusCode.Created, value) }

    let sampleResource =
        routeResource "/api/sample"
                    [ get getHandler
                      post postHandler ]
    
    let registerSample config = config |> register [sampleResource]

Demo:

Simplify

F# + Web Take Aways

  • Production-ready
  • Facilitates rapid development (and prototyping)
  • Shifts focus to domain
  • Provides simple composition

Resources

Questions?