From Bistro
Jump to: navigation, search

Bistro's compositional and parameterized nature lends itself very well to a functional approach, like Microsoft F#. One of the challenges of using F# in a typical Web environment is that most web frameworks require components to be classes, and the methods on those classes are required to have side-effects as their main outcome (such as modification of a request or session variable).

The version of FSharpExtensions that ships with the inital bistro release is at an earlier development state than bistro itself. It is, however, an actively supported extension, that will see updates on a similar timeframe as the rest of the runtime.

Functional Controllers

Bistro controllers in their C# incarnation do follow the same convention - a class with methods, and methods that have side-effects as their main function. However, the key difference is that in bistro, side-effects are contained to operations on the owner class. As such, those side-effects are well defined, and well-encapsulated. What's more is that the full set of scoping and sourcing attributes implicitly encodes a set of input and output parameters that the controller consumes and produces. What's more, is that the concept of a set of controllers composing the actual work performed by invoking an application method forms controllers that are small and well-defined.

Essentially, the structure of a bistro application tends towards having components as discrete functions with well-defined inputs and outputs. A recipe that's perfect for a final step towards functional language supports.


Functional controllers mean that given some runtime support, controllers could be expressed as just F# functions. The Bistro.FSharpExtensions library provides just that support. Bistro uses F# Quotations and Reflection, combined with discriminated union markers to let the user write controllers as functions:

<source lang="ocaml"> [<ReflectedDefinition>] [<Bind("get /home/index")>] [<RenderWith("Views/Home/index.django")>] let home_ct (ctx: IExecutionContext) = let Message = "Welcome to Bistro.FS!" Message </source>

The code above is equivalent to

<source lang="csharp"> [Bind("get /home/index")] [RenderWith("Views/Home/index.django")] public class HomeController : AbstractController { [Request] string Message = "Welcome to Bistro!";

public override void DoProcessRequest(IExecutionContext context) { } } </source>

You'll notice that there is no need for field declarations, and that the runtime infers that controller home_ct doesn't have any dependencies, but provides a value of type string called "Message" onto the request context. Specifying input values is equally trivial:

<source lang="ocaml"> [<ReflectedDefinition>] [<Bind("post /auth/logon")>] [<RenderWith("Views/Account/logon.django")>] let do_logon_ct (ctx: IExecutionContext) (username: string form) (password: string form) (rememberMe: bool form) (errors: Errors) = let username, password, rememberMe = username.Value, password.Value, rememberMe.Value

if not <| validate_logon username password (report_error errors) then () else ctx.Authenticate (forms_auth.SignIn (username, rememberMe)) </source>

In this example, the controller do_logon_ct pulls three values from the Form collection, username, password and rememberMe, of different types. It also provides nothing, as the function's return type is () or unit.

All field-level attributes typically used in a C# application have discriminated union counterparts, that can be combined to annotate source and requirement strength on input values:

  • Request values are the default in F#. If a value is not marked as a session value, and is not a parameter in the bind point, it is defaulted to Request scope
  • Session values are specified via the session discriminated union.
  • CookieField values with Outbound = true are specified via the outboundCookie discriminated union.
  • CookieField values with Outbound = false are specified via the inboundCookie discriminated union.
  • FormField values are specified via the form discriminated union.
  • DependsOn values are specified via the standard F# Option type.
  • Requires values are the default in F#. If an input parameter is not marked as an Option type, it is assumed to be a Requires value.
  • Provides values are inferred from the return types of the function. All values, regardless of type, are registered as Provides values with the runtime.

This set of tools, combined with inference wherever possible, allow controllers written in F# to be much more concise than their C# counterparts.

Something to remember
The functionality afforded by the FSharpExtensions library does not come without limitations. Function return value names are parsed run-time out of the AST representation of the function created through the ReflectedDefinition attribute. As such, the actual return expressions of controller functions must be a tuple listing out bound values, and cannot be expressions. The reason for this is that names need to be inferred for all return values. This is difficult, and sometimes impossible to do if the return value is anything other than the name of a bound value.
Future direction
To make F# controller code more succinct, future versions of the FSharpExtension library will define a different attribute for binding than Bind, that will combine the Bind and ReflectedDefinition attributes.

Getting started

Since F# is just about to hit beta, visual studio support for it is still limited, and there is currently no way to quickly create an F# web project. Instead, it's easier to create a c# web project that will house the view and the content, and then an f# library to house the controllers.

To create an F# bistro application:

  1. Follow the standard bistro quick-start
  2. Once you have a web project created, add an F# library project
  3. Add a reference to your F# project from the web project

... aaand you're done.