The last few months have certainly been interesting. I was busy building an MVC framework, and decided to use NVelocity as the view. A friend pulled me aside, and (after slapping me for using a 6 year old, poorly-supported java port), said "take a look at the templating language in django". The rest, as they say, was history. Realizing how clean and simple the language was, I set out to build an implementation (of the template piece). As I was just starting to notice F#, i decided - why not? And so my first major project in F# was the core of the NDjango parser , a pure F# (and consequently .NET-friendly) implementation of the django templating language. Now, it's certainly gone through a number of iterations, so most of my early f#-n00b mistakes should have been excised.
The really interesting thing, though, is that as I was building this parser, I started to really get an appreciation for F#, and for it's uses in a more business-oriented environment. This was all coinciding with me building out Bistro, and so concepts merged. Bistro focuses on having multiple controllers, with well-defined inputs and outputs, service a single request. I started seeing parallels to functions - each controller can be viewed as a function with discrete parameters and tupled return values.
In c#, these concepts are relayed to the runtime through class members that are marked with scope and directionality attributes (request-level input parameter, session-level output parameter, etc). Well, with f#, and quotations, you can get away with not having to be that explicit. Whether something is an input parameter or an output parameter becomes easy to tell. The rest, can be handled through discriminated union markers (see earlier post). What that means is that I can write an F# function, and it becomes a controller, almost instantly:
[<ReflectedDefinition>]
[<Bind("get /home/index")>]
[<RenderWith("Views/Home/index.django")>]
let home_ct (ctx: IExecutionContext) =
let Message = "Welcome to Bistro.FS!"
Message
this controller has no input parameters, but has an output parameter called "Message" (yes, the resource name is pulled from the Quotation tree), of type string. We use defaults for most common behaviors - so that one right there is scoped to "request".
A more complex example
[<ReflectedDefinition>]
[<Bind("get /auth/changepassword")>]
[<RenderWith("Views/Account/register.django")>]
let do_change_pass_ct (ctx: IExecutionContext) (currentPassword: string form) (newPassword: string form)
(confirmPassword: string form) (errors: Errors) =
let currentPassword, newPassword, confirmPassword =
currentPassword.Value, newPassword.Value, confirmPassword.Value
let new_user =
if validate_change_pass currentPassword newPassword confirmPassword (report_error errors) then
try
if membership_svc.ChangePassword(ctx.CurrentUser.Identity.Name, currentPassword, newPassword) then
ctx.Response.RenderWith "Views/Account/changePasswordSuccess.django"
else
report_error errors null "The current password is incorrect or the new password is invalid."
with | _ ->
report_error errors null "The current password is incorrect or the new password is invalid."
errors
This one validates some data that came from the form (currentPassword, newPassword and confirmPassword), and adds to the supplied errors collection, which it then returns as a request parameter.
The semantics of defining controller behavior become incredibly simple. Bistro already strives for smaller controllers with well-defined functions. This just takes advantage of it. Now you can write your controller tier in f#, and it actually makes sense there. If you think about it - the controller tier is effectively small data processing. It's validating and transforming input data, and then relaying it to the model, which can be in c# or whatever other language you chose. With NDjango in the mix, your view is guaranteed to be free of business logic, and your separation of concerns becomes complete!
I'm sure a lot of this makes less sense on this blog than it does in my head - I've been living this for the past few months, so i'm sure some of the connecting dots didn't make it on here, but if you're interested, take a look at some of the writeups that i've put together:
Bistro itself is here, the FSharpExtensions component is here, and NDjango is here
The FSharpExtensions library still has some issues, (like url parameter values can't be option types right now, and a few others), plus the performance piece needs some work, but it's a start, and we'll be working on it. Already it performs within 90% of its c# counterpart, and that's with full-on reflection lookups on every request. Once that stuff is cached up, it'll be on-par if not faster.
As always, I welcome all feedback, on any of these projects.