Friday, April 15, 2011

Refactoring to monadic C#

You may have heard or read that LINQ is more than just queries, that it's actually syntax sugar for monadic code. But unless you previously used monads in another language with better, more explicit support (like Haskell or F#) you might not grok the concept of monads or how exactly monads are useful in real-world code.

No, this is not yet another monad tutorial. Instead, I'll show how a concrete function in a real-world C# project can be refactored to take advantage of a monad. But I'm not going to explain how this monad works or how it's implemented.

First, a warning: there is quite a bit of code in this post. It's simple code, but I recommend really reading it and not just skimping over it to understand what's going on.

The code we'll work with is a simple utility function that parses version ranges in NuGet, used to express dependency versions. For example, the string "2.1" indicates version 2.1. Another example: "[2.0,2.5]" is an interval between versions 2.0 and 2.5, inclusive. You can find the full specification here.

This post is not a criticism of NuGet or its code, or even imperative programming. There's nothing "wrong" here. I was just browsing NuGet's code and thought this would make a good example on how to refactor imperative code to functional, monadic style. It's a decidedly different style, but I'll leave it to you to decide if it's better or not. In my experience, shoving functional programming down programmers' throats doesn't work. People have to see and decide for themselves.

The code we'll refactor can be found here in NuGet's repository. I'll copy it here for convenience:

public static bool TryParseVersionSpec(string value, out IVersionSpec result) {
    if (value == null) {
        throw new ArgumentNullException("value");
    }

    var versionSpec = new VersionSpec();
    value = value.Trim();

    // First, try to parse it as a plain version string 
    Version version;
    if (Version.TryParse(value, out version)) {
        // A plain version is treated as an inclusive minimum range 
        result = new VersionSpec {
            MinVersion = version,
            IsMinInclusive = true
        };

        return true;
    }

    // It's not a plain version, so it must be using the bracket arithmetic range syntax

    result = null;

    // Fail early if the string is too short to be valid 
    if (value.Length < 3) {
        return false;
    }

    // The first character must be [ ot ( 
    switch (value.First()) {
        case '[':
            versionSpec.IsMinInclusive = true;
            break;
        case '(':
            versionSpec.IsMinInclusive = false;
            break;
        default:
            return false;
    }

    // The last character must be ] ot ) 
    switch (value.Last()) {
        case ']':
            versionSpec.IsMaxInclusive = true;
            break;
        case ')':
            versionSpec.IsMaxInclusive = false;
            break;
        default:
            return false;
    }

    // Get rid of the two brackets 
    value = value.Substring(1, value.Length - 2);

    // Split by comma, and make sure we don't get more than two pieces 
    string[] parts = value.Split(',');
    if (parts.Length > 2) {
        return false;
    }

    // If there is only one piece, we use it for both min and max 
    string minVersionString = parts[0];
    string maxVersionString = (parts.Length == 2) ? parts[1] : parts[0];

    // Only parse the min version if it's non-empty 
    if (!String.IsNullOrWhiteSpace(minVersionString)) {
        if (!Version.TryParse(minVersionString, out version)) {
            return false;
        }
        versionSpec.MinVersion = version;
    }

    // Same deal for max 
    if (!String.IsNullOrWhiteSpace(maxVersionString)) {
        if (!Version.TryParse(maxVersionString, out version)) {
            return false;
        }
        versionSpec.MaxVersion = version;
    }

    // Successful parse! 
    result = versionSpec;
    return true;
}

Going functional

Let's start refactoring. The first thing we should note is that by functional standards this is a pretty monolithical function. In functional programming we tend to write very little functions instead, sometimes even trivial, and avoiding state as much as possible, then compose them to achieve the end result.

In this case, we can identify several functions:

  • Check input length
  • Parse interval start inclusiveness
  • Parse interval end inclusiveness
  • Check interval syntax
  • Parse lower bound version
  • Parse upper bound version

In order to make these functions composable, we'll avoid using out parameters. We could do this by making them return a Tuple<bool,T> where the bool represents if the function was successful or not, and T is the type of the actual result. For example, we could wrap Version.TryParse like this:

Tuple<bool, Version> ParseVersion(string value) {
    Version v;
    var ok = Version.TryParse(value, out v);
    if (ok)
        return Tuple.Create(true, v);
    return Tuple.Create(false, v);
}

In fact F# does this automatically for you. But we can do better by using an option type. An option type is similar to the familiar Nullable<T> type: we can ask if it contains a value or not, and if it does have a value we can retrieve it. The difference is that an option type works for any underlying type, not just value types.

It's not hard to implement a basic option type, but F# already has a proper implementation, and if you have Visual Studio 2010 you already have F#. If you don't, get F# here. So we'll just reference FSharp.Core.dll and use F#'s option type. With a couple of extension methods to ease the syntax in C#, ParseVersion can be written as:

FSharpOption<Version> ParseVersion(string value) {
    Version v;
    var ok = Version.TryParse(value, out v);
    if (ok)
        return v.ToOption();
    return FSharpOption<Version>.None;
}

At this point you may ask why not just return null when value is not a valid version. Yes, we could do that in this particular case, but we'll see that sometimes null can be valid value to return, and then we wouldn't be able to tell an invalid value from a valid null value.

Using FSharpOption<T> the signature of the main function:

bool TryParseVersionSpec(string value, out IVersionSpec result)

becomes:

FSharpOption<IVersionSpec> ParseVersionSpec(string value)

Now let's start implementing one by one the functions in the bulleted list above. I'll write them as local Funcs, but you could just as well make them regular methods if you prefer.

var checkLength = L.F((string val) => val.Length < 3 ? FSharpOption<Unit>.None : FSharpOption.SomeUnit);

L.F() is a little function to help C# with type inference of Funcs, as described here, so checkLength is of type Func<string, FSharpOption<Unit>>

Unit is like void. It means "nothing", but unlike void, it's an actual type we can use, so we can write FSharpOption<Unit> (C# won't allow FSharpOption<Void>). FSharpOption<Unit> has only two possible values: None and Some(null) (represented by SomeUnit)

The rest of the functions look like this:

var minInclusive = L.F((string val) => {
    var c = val.First();
    if (c == '[')
        return true.ToOption();
    if (c == '(')
        return false.ToOption();
    return FSharpOption<bool>.None;
});
var maxInclusive = L.F((string val) => {
    var c = val.Last();
    if (c == ']')
        return true.ToOption();
    if (c == ')')
        return false.ToOption();
    return FSharpOption<bool>.None;
});
var checkParts = L.F((string[] parts) => parts.Length > 2 ? FSharpOption<Unit>.None : FSharpOption.SomeUnit);
var minVersion = L.F((string[] parts) => {
    if (string.IsNullOrWhiteSpace(parts[0]))
        return FSharpOption<Version>.Some(null);
    return ParseVersion(parts[0]);
});
var maxVersion = L.F((string[] parts) =>  {
    var p = parts.Length == 2 ? parts[1] : parts[0];
    if (string.IsNullOrWhiteSpace(p))
        return FSharpOption<Version>.Some(null);
    return ParseVersion(p);
});

So far we've only defined a bunch of functions but haven't done anything with the actual input. Here's the actual processing:

var singleVersion = ParseVersion(value);
if (singleVersion.HasValue())
    return ((IVersionSpec)new VersionSpec { IsMinInclusive = true, MinVersion = singleVersion.Value }).ToOption();

if (!checkLength(value).HasValue())
    return FSharpOption<IVersionSpec>.None;
var isMin = minInclusive(value);
if (!isMin.HasValue())
    return FSharpOption<IVersionSpec>.None;
var isMax = maxInclusive(value);
if (!isMax.HasValue())
    return FSharpOption<IVersionSpec>.None;
var svalue = value.Substring(1, value.Length - 2);
var sparts = svalue.Split(',');
var min = minVersion(sparts);
if (!min.HasValue())
    return FSharpOption<IVersionSpec>.None;
var max = maxVersion(sparts);
if (!max.HasValue())
    return FSharpOption<IVersionSpec>.None;
return ((IVersionSpec)new VersionSpec { IsMinInclusive = isMin.Value, MinVersion = min.Value, IsMaxInclusive = isMax.Value, MaxVersion = max.Value }).ToOption();

What have we really achieved so far, compared to the original imperative solution? We encapsulated all parsing and checking into neat little referentially transparent functions. There's no mutability or side-effects here, whereas in the imperative code the VersionSpec instance gets mutated along the entire function. It might not seem like much in this little example, but once you become reference-transparency aware (and you apply this awareness) your code gets easier to understand and compose, just as Wes describes.

Since VersionSpec is no longer mutated, we could also just ditch IVersionSpec (the readonly interface to VersionSpec) and make VersionSpec truly immutable.

Further modifications and monadifications

But we still have some significant cruft left: all that if (!something.HasValue()) return FSharpOption<IVersionSpec>.None isn't very pretty. Here's where the monad comes in, and another reason to use option types. FSharp.Core.dll already defines a monad around FSharpOption (called the Maybe monad). All we have to do is wrap the functions that implement this into the extension methods that C# requires to enable LINQ syntax sugar (or just implement it directly, it's pretty trivial code), and then we can write that last code block as:

var singleVersion =
    from v in ParseVersion(value)
    select (IVersionSpec)new VersionSpec { IsMinInclusive = true, MinVersion = v };

var versionRange = L.F(() => {
    return from x in checkLength(value)
           from isMin in minInclusive(value)
           from isMax in maxInclusive(value)
           let val = value.Substring(1, value.Length - 2)
           let parts = val.Split(',')
           from y in checkParts(parts)
           from min in minVersion(parts)
           from max in maxVersion(parts)
           select (IVersionSpec)new VersionSpec { IsMinInclusive = isMin, MinVersion = min, IsMaxInclusive = isMax, MaxVersion = max };
});

return singleVersion.OrElse(versionRange)();

By now you're probably thinking "I could have done something similar!". Indeed, you could have invented monads, and you probably have, except that maybe you didn't know you were doing monads so you couldn't take full advantage of the power of this abstraction.

If I got you interested in functional programming and monads, my job here is done. I hope this refactoring was detailed enough to compare the functional and imperative ways of thinking, and also to demystify monads. If you want to learn more about monads, use a language with better syntactic support, like Haskell or F#. Monads don't really require any special language support (you can write and use monads in Java, Python, Ruby, Perl, ...), but without syntax sugar they are harder to read and write, at least until you're comfortable with them. In F# this syntax sugar is called computation expressions, in Haskell do notation.

Desugaring the LINQ expression above into extension methods is a good way to understand what the LINQ syntax does under the covers and see how the functions are composed.

By the way, this is not the only way to apply monads to parsing. We could have used monadic parser combinators, which can provide automatic parsing error messages, but it's a whole different approach from the get-go, not so easily refactorable from existing imperative code. If you're interested, there are some implementations of parser combinators in C#, check out Sprache or RxParsers.

All code shown here (plus the parts I didn't show) is available on github.

All code copied from NuGet is copyrighted by the Outercurve Foundation, licensed under the Apache License, Version 2.0

Tuesday, April 12, 2011

Formlets in C# and VB.NET (part 2)

In my last post I introduced CsFormlets, a wrapper around FsFormlets that brings formlets to C# and VB.NET. I showed how to model a typical registration form with CsFormlets. Now let's see how we can use formlets in ASP.NET MVC.

To recap, here's the form we're modeling:

form_cs

and here's the top-level formlet's signature, the one we'll reference in our ASP.NET MVC controller:

static Formlet<RegistrationInfo> IndexFormlet() { ... }

RegistrationInfo contains all information about this form. You can place this anywhere you want. I chose to put all formlets related to this form in a static class SignupFormlet, in a Formlets directory and namespace. In MVC terms, formlets fulfill the responsibilities of view (for the form), validation and binding.

We'll start with a regular controller with an action to show the formlet:

public class SignupController : Controller {
    [HttpGet]
    public ActionResult Index() {
        return View(model: SignupFormlet.IndexFormlet().ToString());
    }
}

Our view is trivial (modulo styles and layout), as it's only a placeholder for the formlet:

Views/Signup/Index.cshtml

@using (Html.BeginForm()) {
    @Html.Raw(Model)
    <input type="submit" value="Register" />
}

Now we need an action to handle the form submission. The simplest thing to do is handling the formlet manually (let this be case 1):

[HttpPost]
public ActionResult Index(FormCollection form) {
    var result = SignupFormlet.IndexFormlet().Run(form);
    if (!result.Value.HasValue())
        return View(model: result.ErrorForm.Render());
    var value = result.Value.Value;
    return RedirectToAction("ThankYou", new { name = value.User.FirstName + " " + value.User.LastName });
}

The problem with this approach is, if you want to test the action you have to build a test form, so you're actually testing both binding and the processing of the bound object. No problem, just extract methods to the desired level, e.g. (case 2)

[HttpPost]
public ActionResult Index(FormCollection form) {
    var result = SignupFormlet.IndexFormlet().Run(form);
    return Signup(result);
}

[NonAction]
public ActionResult Signup(FormletResult<RegistrationInfo> registration) {
    if (!registration.Value.HasValue())
        return View(model: registration.ErrorForm.Render());
    return Signup(registration.Value.Value);
}

[NonAction]
public ActionResult Signup(RegistrationInfo registration) {
    return RedirectToAction("ThankYou", new { name = registration.User.FirstName + " " + registration.User.LastName });
}

So we can easily test either Signup method.

Alternatively we can use some ASP.NET MVC mechanisms. For example, a model binder (case 3):

[HttpPost]
public ActionResult Index([FormletBind(typeof(SignupFormlet))] FormletResult<RegistrationInfo> registration) {
    if (!registration.Value.HasValue())
        return View(model: registration.ErrorForm.Render());
    var value = registration.Value.Value;
    return RedirectToAction("ThankYou", new { name = value.User.FirstName + " " + value.User.LastName });
}

By convention the method used to get the formlet is [action]Formlet (you can of course override this).

We can take this even further with an action filter (case 4):

[HttpPost]
[FormletFilter(typeof(SignupFormlet))]
public ActionResult Index(RegistrationInfo registration) {
    return RedirectToAction("ThankYou", new {name = registration.User.FirstName + " " + registration.User.LastName});
}

In this case the filter encapsulates checking the formlet for errors and automatically renders the default action view ("Index" in this case, but this is an overridable parameter) with the error form provided by the formlet. The formlet and filter ensure that the registration argument is never null or invalid when it hits the action.

However convenient they may be, the filter and model binder come at the cost of static type safety. Also, in a real-world application, case 4 is likely not flexible enough, so I'd probably go for the integration case 2 or 3.

Testing formlets

Testing formlets themselves is simple: you just give the formlet a form (usually a NameValueCollection, unless a file is involved), then assert over its result. Since formlets are composable you can easily test some part of it independently of other parts. For example, here's a test for the credit card expiration formlet checking that it correctly validates past dates:

[Fact]
public void CardExpiration_validates_past_dates() {
    var twoMonthsAgo = DateTime.Now.AddMonths(-2);
    var result = SignupFormlet.CardExpiration().Run(new NameValueCollection { 
        {"f0", twoMonthsAgo.Month.ToString()},
        {"f1", twoMonthsAgo.Year.ToString()},
    });
    Assert.False(result.Value.HasValue());
    Assert.True(result.Errors.Any(e => e.Contains("Card expired")));
}

Conclusion

CsFormlets provides composable, testable, statically type-safe validation and binding to C# / VB.NET web apps. Here I showed a possible ASP.NET MVC integration which took less than 200 LoC; integrating with other web frameworks should be similarly easy.

One thing I think I haven't mentioned is that CsFormlets' FormElements generates HTML5 form elements, just like FsFormlets. In fact, pretty much everything I have written about formlets applies to CsFormlets as well, since it's just a thin wrapper.

All code shown here is part of the CsFormlets sample app. Code repository is on github. CsFormlets depends on FsFormlets (therefore also the F# runtime) and runs on .NET 3.5 SP1.

Friday, April 8, 2011

Formlets in C# and VB.NET

All this talk about formlets in F#, by now you might think this is something that only works in functional languages. Nothing further from the truth. Tomáš Petříček has already blogged about formlets in C#, and I've been working on a wrapper around FsFormlets to be used in C# and VB.NET I called CsFormlets (I'm really good with names if you haven't noticed).

In this post I'll assume you already know about formlets. If you don't, I recommend reading Tomas' article. If you want to know more about my particular implementation of formlets, see my previous posts on the subject. If you're just too lazy to read all those lengthy articles, that's ok, read on, you'll still get a good feeling of formlets.

So if F# is a first-class .NET language, why is it necessary to wrap FsFormlets for C# consumption? Well, for one the formlet type is utterly unmanageable in C#. The formlet type in FsFormlets is (expressed in F#):

type 'a Formlet = 'a Error ErrorList XmlWriter Environ XmlWriter NameGen

where Error, ErrorList, etc, each are individual applicative functors. Type aliases in F# make it easy to hide the 'real' type underneath that, but unfortunately, C# doesn't support type aliases with type parameters, so the formlet type becomes this monster:

FSharpFunc<int, Tuple<Tuple<FSharpList<XNode>, FSharpFunc<FSharpList<Tuple<string, InputValue>>, Tuple<FSharpList<XNode>, Tuple<FSharpList<string>, FSharpOption<T>>>>>, int>>

And no, you can't always var your way out, so to keep this usable I wrapped this in a simpler Formlet<T> type.

Functions that use F# types like FSharpFunc<...> (obviously) and FSharpList<Tuple<T,U>> are wrapped so they use System.Func<...> and IEnumerable<KeyValuePair<T,U>> respectively. F# options are converted to/from nullables whenever possible. Extension methods are provided to work more easily with F# lists and option types. Active patterns (used in F# to match success or failure of formlet) are just not available. Also, applicative operators like <*>, <*, etc are just not accessible in C#, so I wrapped them in methods of Formlet<T>. This yields a fluent interface, as we'll see in a moment.

As usual, I'll demo the code with a concrete form, which looks like this:

form_cs

As I wanted to make this example more real-world than previous ones, I modeled it after the signup form of a real website, don't remember which one but it doesn't really matter. This time it even has decent formatting and styling!

As usual we'll build the form bottom-up.

Password

First the code to collect the password:

static readonly FormElements e = new FormElements();

static readonly Formlet<string> password = 
    Formlet.Tuple2<string, string>() 
        .Ap(e.Password(required: true).WithLabelRaw("Password <em>(6 characters or longer)</em>")) 
        .Ap(e.Password(required: true).WithLabelRaw("Enter password again <em>(for confirmation)</em>")) 
        .SatisfiesBr(t => t.Item1 == t.Item2, "Passwords don't match") 
        .Select(t => t.Item1) 
        .SatisfiesBr(t => t.Length >= 6, "Password must be 6 characters or longer");

Formlet.Tuple2 is just like "yields t2" in FsFormlets, i.e. it sets up a formlet to collect two values in a tuple. Unfortunately, type inference is not so good in C# so we have to define the types here. We'll see later some alternatives to this.

Ap() is just like <*> in FsFormlets.

SatisfiesBr() applies validation. Why "Br"? Because it outputs a <br/> before writing the error message. If no <br/> was present, the error "Password must be 6 characters or longer" would overflow and show in two lines, which looks bad.
This is defined as a simple extension method, implemented using the built-in Satisfies():

static IEnumerable<XNode> BrError(string err, List<XNode> xml) { 
    return xml.Append(X.E("br"), X.E("span", X.A("class", "error"), err)); 
}

static Formlet<T> SatisfiesBr<T>(this Formlet<T> f, Func<T, bool> pred, string error) { 
    return f.Satisfies(pred, 
        (_, x) => BrError(error, x), 
        _ => new[] { error }); 
}

Now you may be wondering about X.E() and X.A(). They're just simple functions to build System.Xml.Linq.XElements and XAttributes respectively.

Back to the password formlet: ever since C# 3.0, Select() is the standard name in C# for what is generally known as map, so I honor that convention in CsFormlets. In this case, it's used to discard the second collected value, since password equality has already been tested in the line above.

Account URL

Moving on, the formlet that collects the account URL:

static readonly Formlet<string> account = 
    Formlet.Single<string>() 
        .Ap("http://") 
        .Ap(e.Text(attributes: new AttrDict {{"required","required"}})) 
        .Ap(".example.com") 
        .Ap(X.E("div", X.Raw("Example: http://<b>company</b>.example.com"))) 
        .Satisfies(a => !string.IsNullOrWhiteSpace(a), "Required field") 
        .Satisfies(a => a.Length >= 2, "Two characters minimum") 
        .Satisfies(a => string.Format("http://{0}.example.com", a).IsUrl(), "Invalid account") 
        .WrapWith(X.E("fieldset"));

You should notice at least two weird things here. If you don't, you're not paying attention! :-)

First weird thing: I said Ap() is <*> , but you couldn't apply <*> to pure text (.Ap("http://")) or XML as shown here, only to a formlet! This is one of the advantages of C#: Ap() is overloaded to accept text and XML, in which case it lifts them to Formlet<Unit> and then applies <*
Because of these overloads Ap() could almost be thought of as append instead of apply.

Second weird thing: instead of writing e.Text(required: true) as in the password formlet, I explicitly used required just as HTML attribute. However, requiredness is checked server-side after all markup. This is for the same reason I defined SatisfiesBr() above: we wouldn't like the error message to show up directly after the input like this:

http:// Required field.example.com

Alternatively, I could have used a polyfill for browsers that don't support the required attribute, but I'm going for a zero-javascript solution here, and also wanted to show this flexibility.

It's also possible to define default conventions for all error messages in formlets (i.e. always show errors above the input, or below, or as balloons) but I won't show it here.

Oh, in case it's not evident, X.Raw() parses XML into System.Xml.Linq.XNodes.

User

Let's put things together in a User class

static readonly Formlet<User> user = 
    Formlet.Tuple5<string, string, string, string, string>() 
        .Ap(e.Text(required: true).WithLabel("First name")) 
        .Ap(e.Text(required: true).WithLabel("Last name")) 
        .Ap(e.Email(required: true).WithLabelRaw("Email address <em>(you'll use this to sign in)</em>")) 
        .Ap(password) 
        .WrapWith(X.E("fieldset")) 
        .Ap(X.E("h3", "Profile URL")) 
        .Ap(account) 
        .Select(t => new User(t.Item1, t.Item2, t.Item3, t.Item4, t.Item5));

Nothing new here, just composing the password and account URL formlets along with a couple other inputs, yielding a User.

Card expiration

Let's tackle the last part of the form, starting with the credit card expiration:

static Formlet<DateTime> CardExpiration() { 
    var now = DateTime.Now; 
    var year = now.Year; 
    return Formlet.Tuple2<int, int>() 
        .Ap(e.Select(now.Month, Enumerable.Range(1, 12))) 
        .Ap(e.Select(year, Enumerable.Range(year, 10))) 
        .Select(t => new DateTime(t.Item2, t.Item1, 1).AddMonths(1)) 
        .Satisfies(t => t > now, t => string.Format("Card expired {0:#} days ago!", (now-t).TotalDays)) 
        .WrapWithLabel("Expiration date<br/>"); 
}

This formlet, unlike previous ones, is a function, because it depends on the current date. It has two <select/> elements: one for the month, one for the year, by default set to the current date.

Billing info

Now we use the card expiration formlet in the formlet that collects other billing data:

static readonly IValidationFunctions brValidationFunctions = 
    new Validate(new ValidatorBuilder(BrError));

static Formlet<BillingInfo> Billing() { 
    return Formlet.Tuple4<string, DateTime, string, string>() 
        .Ap(e.Text(required: true).Transform(brValidationFunctions.CreditCard).WithLabel("Credit card number")) 
        .Ap(CardExpiration()) 
        .Ap(e.Text(required: true).WithLabel("Security code")) 
        .Ap(e.Text(required: true).WithLabelRaw("Billing ZIP <em>(postal code if outside the USA)</em>")) 
        .Select(t => new BillingInfo(t.Item1, t.Item2, t.Item3, t.Item4)) 
        .WrapWith(X.E("fieldset")); 
}

Transform() is just a simple function application. brValidationFunctions.CreditCard is a function that applies credit card number validation (the Luhn algorithm). The validation function is initialized with the same BrError() convention I defined above, i.e. it writes a <br/> and then the error message.

Top formlet

Here's the top-level formlet, the one we'll use in the controller to show the entire form and collect all values:

static Formlet<RegistrationInfo> IndexFormlet() { 
    return Formlet.Tuple2<User, BillingInfo>() 
        .Ap(X.E("h3", "Enter your details")) 
        .Ap(user) 
        .Ap(X.E("h3", "Billing information")) 
        .Ap(Billing()) 
        .Select(t => new RegistrationInfo(t.Item1, t.Item2)); 
}

LINQ & stuff

I've been using Formlet.Tuple in these examples, but you could also use Formlet.Yield, which behaves just like "yields" in FsFormlets. In F# this is no problem because functions are curried, but this is not the case in C#. Even worse, type inference is really lacking in C# compared to F#. This makes Formlet.Yield quite unpleasant to use:

Formlet.Yield<Func<User,Func<BillingInfo,RegistrationInfo>>>((User a) => (BillingInfo b) => new RegistrationInfo(a,b))

With a little function to help with inference such as this one, it becomes

Formlet.Yield(L.F((User a) => L.F((BillingInfo b) => new RegistrationInfo(a, b))))

Still not very pretty, so I prefer to use Formlet.Tuple and then project the tuple to the desired type.

Another way to define formlets in CsFormlets is using LINQ syntax. Tomas explained in detail how this works in a recent blog post. For example, the last formlet defined with LINQ:

static Formlet<RegistrationInfo> IndexFormletLINQ() { 
    return from x in Formlet.Raw(X.E("h3", "Enter your details")) 
           join u in user on 1 equals 1 
           join y in Formlet.Raw(X.E("h3", "Billing information")) on 1 equals 1 
           join billing in Billing() on 1 equals 1 
           select new RegistrationInfo(u, billing); 
}

Also, where can be used to apply validation, although you can't define the error message in each case or where it will be displayed.

The LINQ syntax has some pros and cons.

Pros

  • Less type annotations required.
  • No need to define at the start of the formlet what values and types we will collect.

Cons

  • join and on 1 equals 1 look somewhat odd.
  • Pure text and XML need to be explicitly lifted.
  • Less flexible than chaining methods. If you use where to apply validation, you can't define the message. If you want to use Satisfies(), WrapWith() or any other extension method, you have break up the formlet expression.

Personally, I prefer chaining methods over LINQ, but having a choice might come in handy sometimes.

VB.NET

The title of this post is "Formlets in C# and VB.NET", so what is really different in VB.NET? We could, of course, translate everything in this post directly to VB.NET. But VB.NET has a distinctive feature that is very useful for formlets: XML literals. Instead of:

(C#) xml.Append(X.E("br"), X.E("span", X.A("class", "error"), err));

In VB.NET we can write:

(VB.NET) xml.Append(<br/>, <span class="error"><%= err %></span>)

which is not only clearer, but also more type-safe: you can't write something like <span 112="error">, it's a compile-time error.

To be continued...

This post is too long already so I'll leave ASP.NET MVC integration and testing for another post. If you want to play with the bits, the CsFormlets code is here. All code shown here is part of the CsFormlets sample app. Keep in mind that you also need FsFormlets, which is included as a git submodule, so after cloning CsFormlets you have to init the submodules.