F# Suave warbler function
Asked Answered
R

3

20

I've started learning F# and Suave and I'm reading the book F# Applied.

One thing I'm struggling with is the warbler function. I know its something to do with deferring execution but I don't really understand why and when its needed.

Apparently we could also use the request function as an alternative to warbler.

Can anyone provide any more detail on why and when these functions are used.

Record answered 12/11, 2016 at 9:21 Comment(0)
Y
20

These three functions are related in the sense that request and context are specialized versions of warbler. They all do the same thing - they inspect (some aspect of) their argument and give you back a function to apply to that argument.

Remember that the basic "building block" of Suave, WebPart, is a function HttpContext -> Async<HttpContext option> rather than some concrete object. What this effectively means is that those three functions allow you to inspect this HttpContext and based on that compose a WebPart to use.

At its core, what warbler does is very simple:

let warbler f a = f a a  
// ('t -> 't -> 'u) -> 't -> 'u

You give it a function f and argument a. Function f looks at a and gives you back a new function 't -> 'u which is then applied to a.

The thing about warbler is that it's entirely generic - you can use it anywhere you'd use context or request as long as the types align, but it doesn't know anything about the domain Suave is interested in.

That's why there are the specialized versions of it that "speak the domain language":

let request apply (a : HttpContext) = apply a.request a
// (HttpRequest -> HttpContext -> 'a) -> HttpContext -> 'a 
let context apply (a : HttpContext) = apply a a
// (HttpContext -> HttpContext -> 'a) -> HttpContext -> 'a 

Notice that they have the same "shape" as warbler - the only difference being that the HttpContext type is "hardcoded" - making it more convenient to use.

Yaupon answered 12/11, 2016 at 10:46 Comment(2)
Thanks scrwtp. Good answer.Record
Simple correction suggestion: type WebPart = HttpContext -> Async<HttpContext option>Diastole
G
32

The other answer already explained the warbler function and its relation to context and request functions. I'd like to show when do you want to use these.

When you start a Suave server, you need to provide it with the request processing pipeline of WebParts - routing, HTTP methods and the response-generating functions. This means that by the time you start the web server all the parameters provided to the partially applied WebPart functions have already been evaluated.

Imagine a minimalistic web app that prints the current server time:

let app = GET >=> path "/" >=> OK (string DateTime.Now)

If you start a web server using this app pipeline, you'll always see the same timestamp generated when the app value was created, no matter when you make the web requests retrieving it.

The warbler function and its specialized versions context and request not only defer the execution, but also enable the web server to call the provided function every time it needs its result.

In the example scenario this app will provide expected results:

let app = GET >=> path "/" >=> warbler (fun ctx -> OK (string DateTime.Now))

@adzdavies' comment shows an alternative approach where you don't necessarily need warbler. In the example you can also defer the parameter evaluation if you use anonymous function syntax instead of partially applying OK.

let app = GET >=> path "/" >=> (fun ctx -> OK (string DateTime.Now) ctx)
Goop answered 12/11, 2016 at 11:13 Comment(3)
Thanks. Yours and scrwtp's answer in combination are very useful. Thanks again. I've upvoted your answer.Record
Note that 'all the WebPart functions have already executed' isn't strictly true. In this case, just the argument to "OK" has. It doesn't really execute until a request comes in. It is possible to replace warbler with a lambda, but this requires you to pass along the context: (fun ctx -> OK (string DateTime.Now) ctx) which is, as shown, directly derivable from it's definition (f a -> f a a)Entrain
Thanks... I was struggling to understand how suave was working, and your answer, the source code, and trying out that lambda helped make it click.Entrain
Y
20

These three functions are related in the sense that request and context are specialized versions of warbler. They all do the same thing - they inspect (some aspect of) their argument and give you back a function to apply to that argument.

Remember that the basic "building block" of Suave, WebPart, is a function HttpContext -> Async<HttpContext option> rather than some concrete object. What this effectively means is that those three functions allow you to inspect this HttpContext and based on that compose a WebPart to use.

At its core, what warbler does is very simple:

let warbler f a = f a a  
// ('t -> 't -> 'u) -> 't -> 'u

You give it a function f and argument a. Function f looks at a and gives you back a new function 't -> 'u which is then applied to a.

The thing about warbler is that it's entirely generic - you can use it anywhere you'd use context or request as long as the types align, but it doesn't know anything about the domain Suave is interested in.

That's why there are the specialized versions of it that "speak the domain language":

let request apply (a : HttpContext) = apply a.request a
// (HttpRequest -> HttpContext -> 'a) -> HttpContext -> 'a 
let context apply (a : HttpContext) = apply a a
// (HttpContext -> HttpContext -> 'a) -> HttpContext -> 'a 

Notice that they have the same "shape" as warbler - the only difference being that the HttpContext type is "hardcoded" - making it more convenient to use.

Yaupon answered 12/11, 2016 at 10:46 Comment(2)
Thanks scrwtp. Good answer.Record
Simple correction suggestion: type WebPart = HttpContext -> Async<HttpContext option>Diastole
B
3

I found the prior explanation confusing (to me). This is my attempt at clarity...

warbler resolves a problem with optimized impure eager evaluated functional languages in which partially applied arguments are evaluated early and cached. This caching presents a problem when those applied arguments are dependent on side effects and it becomes desirable to have fresh values with each invocation. For example, the following query for the string representation of the current system's time will occur and be cached at the definition of g: string -> string. As such, it will return the same value for each subsequent call to g:

let g = sprintf "%s %s" (string DateTime.Now)

g "a"  //"12/09/2020 18:33:32 a"
g "b"  //"12/09/2020 18:33:32 b"

However, the warbler concept is unnecessary to resolve this reevaluation issue. It is enough to simply wrap the subject function inside an anonymous function that then fully applies the subject function each time, as follows:

let f = (fun x -> sprintf "%s %s" (string DateTime.Now) x)

f "c"    //"12/09/2020 18:53:32 c"
f "d"    //"12/09/2020 18:53:34 d"

What warbler is doing instead is using the above anonymous function as a function factory that produces the subject function when invoked. Then invoking that subject function with its second argument. It is incidental that warbler uses its second argument to invoke the factory function but it does present a point of misdirection. Conceivably, passing the argument to the factory can allow the factory to configure the subject function function or select alternative type compatible functions to return to the warbler. Still, that is not what the warbler is intended for.

let warbler f x = (f x) x

It should be noted that for reevaluation to work, f, must be an anonymous function at the point of call. Consequently, there seems to be no longer any utility for the warbler concept and the cool name should probably be deprecated and allowed to resurface for some other useful concept.

Incidentally, my encounter with warbler is with Giraffe.

Been answered 9/12, 2020 at 18:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.