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
WebPartinto a single
- Combinators that produce
WebPartfrom more primitive values. Recall that
WebParthas the type
HttpContext -> 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
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
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
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