Composing bigger programs: combinators
Defining the entire logic of your program in a single giant function called app would clearly be impossible. Functional programming is all about composing functions from several smaller functions, and both F# and Suave offer various tools to make this easy.
In functional programming parlance, a “combinator” either combines several things of the same type into another thing of the same type, or otherwise takes a value and returns a new, modified version of that value. In mathematics it has a slightly different meaning, but we need not worry about this. In the case of Suave, there are two types of combinator:
- Combinators which combine multiple
WebPart
into a singleWebPart
. - Combinators that produce
WebPart
from more primitive values. Recall thatWebPart
has the typeHttpContext -> Async<HttpContext option>
. These combinators therefore always take a single HttpContext and produce a new HttpContext, wrapped inside an async option workflow.
Together these are used to create web parts, combine them to produce new webparts, and ultimately combine them all into a single webpart passed as an argument used to initialise the web server.
We have already seen several examples of combinators. The choose
function seen below takes a list of WebPart
, and combines them all into a single new WebPart
:
The choose
combinator is implemented such that it will execute each webpart in the list until one returns success.
>=>
is also a combinator, one that combines exactly two web parts. It runs the first, waits for it to finish, and then either passes the result into the second part, or short circuits if the first part returns None
.
OK
is a combinator of the second type. It always succeeds and writes its argument to the underlying response stream. It has type string -> WebPart
.
To gain access to the underlying HttpRequest
and read query and http form data we can use the request
combinator (the ^^
custom operator, you have to add open Suave.Utils.Collections
, is shorthand for searching a list of key-value option pairs and returning the value (or None if not found)):
You can similarly use context
to gain access to the full HttpContext
and connection.
To protect a route with HTTP Basic Authentication the combinator authenticateBasic
is used like in the following example.
Your web parts are “values” in the sense that they evaluate
once, e.g. when constructing choose [ OK "hi" ]
, OK "hi"
is evaluated once,
not every request. You need to wrap your web part in a closure if you want to
re-evaluated every request, with Suave.Http.warbler
, Suave.Types.context
or
Suave.Types.request
.
warbler : (f : 'a -> 'a -> 'b) -> 'a -> 'b
- a piece of the applicatives
puzzle, which allows you to act on the 'a
argument and return a function that
‘is the same’ as after your acting on it. Using this is very useful for control
flow, because you can then inspect HttpContext
and choose what applicative
function to return.
context
: basically the same as warbler.
request
: basically the same as context, but only looks at the request - allows
you to cut down on the pattern matching of HttpContext a bit: but you have to
return an applicative that is a WebPart (i.e. something that isn’t from
HttpRequest to something else, but from HttpContext to async http context
option).