Functional-first Web Applications in F#

This post is the first part in a short series on programming web-enabled, functional-friendly applications in F# and has kindly been written by Krzysztof CieĊ›lak. Thanks, Chris!

Introduction

For many developers, Functional Programming (FP) is not only more difficult to learn, but developers also often believe that it's difficult to find any real world applications using it. However, FP gives developers the benefits of terse code, precise expressiveness and a better type system, whilst FP also works particularly well with concurrent programming.

FP is gaining popularity in web applications, although it's still relatively unknown as a tool in this area. As it turns out though, FP is the ideal paradigm for web development: HTTP at its core is a stateless protocol (although state is sometimes simulated through the use of cookies, sessions or browsers' local storage). As a user interacts with an HTTP application, each call of the server can be viewed as a function that takes an HTTP request as an input and returns HTTP response as the output. In applications developed using FP, the code can exactly match this interpretation of the protocol. Look at the following HTTP request / response.

Now, compare it with the following example function using a simple set of F# types.

Why F#?

F# is a mature, open source, cross-platform, functional-first programming language. It empowers users and organizations to tackle complex computing problems with simple, maintainable and robust code.

F# is an FP language running on the .Net platform. It has a powerful type system combined with type inference and a lightweight, elegant syntax which makes it an amazing language. F# can be also used to target the Javascript ecosystem using Fable for client side programming (we'll be covering this in more detail later in this series).

F# has several important features that make it a great choice for web programming:

  • Functional-first - F# is functional-first language which means it has great support for the FP features, which in turn makes it a great fit for web programming and lets developers easily model their web server as set of functions that take an HTTP request and return an HTTP response
  • Powerful type system - thanks to constructs like records (lightweight, immutable classes) and discriminated unions, F# is a great fit for general purpose programming, including any domain modelling.
  • Focus on asynchronous programming - F#'s async workflows make it very easy to write asynchronous code, which simplifies writing scalable, reactive web applications
  • Agents and actors - F# has built-in constructs for creating agents - small, isolated parts of code that can be easily used to create distributed, scalable, and reactive applications
  • Interoperability - F# interoperates seamlessly with both C# (effectively giving it access to the whole .Net ecosystem) and JS (providing access to all JavaScript libraries and frameworks)
  • Open-source and cross-platform - F# can be run today on all major operating systems using the .Net Framework (Windows), .Net Core and Mono (Windows, Linux, MacOS) and NodeJS (Windows, Linux, MacOS)

Introducing Suave

Suave is a simple web development F# library providing a lightweight web server and a set of combinators to manipulate route flow and task composition

Suave is probably the most popular F# library for the web programming. It provides a lightweight, non-blocking web server, and a set of functions and combinators that let developers represent the logic of their web application as a set of functions that are composed together.

WebParts

Here's an example of "hello world" in Suave:

1: 
2: 
3: 
open Suave

startWebServer defaultConfig (Successful.OK "Hello World!")

The building block of all Suave applications is the WebPart. A WebPart is simply any F# function (no magic involved!) that aheres to the following signatureHttpContext -> Async<HttpContext option> - here's an example function that returns Hello World that happens to be a webpart. There's no need to implement an interface or inherit from a base class - simply matching this signature is enough to participate in the Suave engine.

As you can see, a WebPart is any function that takes an HttpContext (an object containing the HTTP request, response, and any other required data e.g. headers), and tries to asynchronously return a new HttpContext. It's important to notice that WebPart can also return None - which means that the operation failed. Here's a sample WebPart:

1: 
2: 
3: 
4: 
5: 
6: 
let sleep milliseconds message : WebPart =
  fun (context : HttpContext) ->
    async {
      do! Async.Sleep milliseconds
      return! OK message context
    }

Composing WebParts

Having a single WebPart is normally not enough for any reasonably sized application - no one wants to write their whole application as a single function! So, Suave also introduces the idea of combinators - functions that can combine multiple WebParts into a single WebPart - and this combination can be both vertical and horizontal.

The two most important combinators are:

  • >=> - Used to chain (or compose) multiple WebParts together. It runs the first WebPart, waits for it to finish, and then if it returned Some HttpContext, passes it to next WebPart as an input. If the first part failed and None was returned, the entire WebPart will immediately None without executing second part. If you're familiar with Scott Wlaschin's series on Railway Oriented Programming, this idea of "two track" programming should be immediately familiar to you.
  • choose - a function that takes a list of WebParts and combines them all into single WebPart. This new webpart executes each individual WebPart in turn, until if finds one of them that returns success, which becomes the overall result.

Here's a sample of using Suave's combinators:

 1: 
 2: 
 3: 
 4: 
 5: 
 6: 
 7: 
 8: 
 9: 
10: 
11: 
let greetings bag =
    bag ^^ "name"
    |> Option.ofChoice
    |> withDefault "World"
    |> sprintf "Hello, %s"

let sample : WebPart = 
    path "/hello" >=> choose [
      GET  >=> request (fun ctx -> OK (greetings ctx.query))
      POST >=> request (fun ctx -> OK (greetings ctx.form))
      RequestErrors.NOT_FOUND "Found no handlers" ]

We first write a simple function, greetings, which takes in some bag of key/value pairs. We then try to retrieve the name value from the bag, and if none is found, replace it with the default of "World". We then return that as a string e.g. "Hello, World" or "Hello, Chris".

The second function is the main WebPart which will represent our web server. It combines a moderately complex set of routes into an easy-to-read form:

  • If the route is "/hello" AND the request is GET, return a webpart that calls greetings using the query string as the bag.
  • If the route is "/hello" AND the request is POST, return a webpart that calls greetings using the form object as the bag.
  • If the route is "/hello" AND a different verb return a pre-defined web part that returns a NOT FOUND result.
  • Otherwise, return no response to Suave i.e. an error.

Observe how closely this flow diagram fits with the actual code:

This model offers an excellent way to write simple code using basic F# functions, and then compose them together using Suave's powerful combinators in a stateless manner into complex applications - a great use of F# and FP principles. No interfaces, no inheritance, and no heavyweight libraries or frameworks needed.

Alternative F# server-side Web Stacks

Suave is not only F# library for the server-side web programming (although it's arguably the most popular, with the biggest community support). There also exist other options for creating web applications with F#:

  • Giraffe - an F# micro web framework for building rich web applications. It has been heavily inspired and is similar to Suave, but has been specifically designed with ASP.NET Core in mind and can be plugged into the ASP.NET Core pipeline via middleware. Giraffe applications are composed of so called HttpHandler functions which can be thought of a mixture of Suave's WebParts and ASP.NET Core's middleware.

  • Freya - a functional web stack built on top of OWIN. At its core, Freya wraps the OWIN environment dictionary with a computation expression and provides access to that dictionary with lenses. Additional layers of the stack provide types based on the HTTP (and related) RFCs, a pipeline abstraction for connecting Freya computations, a router, and an implementation of the HTTP finite state machine

  • WebSharper - allows end-to-end web applications with both client and server developed in F#. It includes TypeScript interoperability, mobile web apps, getting started material, templates and much more.

  • Azure Functions - an event driven, compute-on-demand experience that extends the existing Azure application platform with capabilities to implement code triggered by events occurring in Azure or third party service as well as on-premises systems. Developers can leverage Azure Functions to build HTTP-based API endpoints accessible by a wide range of applications

Summary

In this post, I've tried showing how FP and F# in particular can be a great, natural fit for web programming. I've described several properties of FP that makes it easy to create web applications, and shown that any web server can be treated as a simple function taking HTTP request and returning an HTTP response. I've also shown how F# fits into this picture and what language features make it a great choice for building web applications. Finally, I've shown Suave - a native F# library that uses a unique programming model that lets developers create complex web server applications by combining simple, reusable functions.

val sleep : milliseconds:int -> message:'a -> context:'b -> Async<'c>

Full name: safeweb.sleep
val milliseconds : int
val message : 'a
val context : 'a
val async : AsyncBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
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 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.Sleep : millisecondsDueTime:int -> Async<unit>
val greetings : bag:obj -> string

Full name: safeweb.greetings
val bag : obj
module Option

from Microsoft.FSharp.Core
val sprintf : format:Printf.StringFormat<'T> -> 'T

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.sprintf
val sample : 'a

Full name: safeweb.sample
val query : Linq.QueryBuilder

Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.query