Why not try F#?

I have avoided seriously trying F# for years now, mainly because:

  • F# was described as a "functional programming language", and I didn’t know FP. I was keen to learn FP, but prioritised learning more about OO design and patterns that seemed more immediately applicable to my everyday work.
  • The syntax looked really confusing.
  • Whenever I heard F# mentioned it was in the same breath as "financial data processing" or some other niche area that seemed to have little to do with the types of applications I wrote.

I carried these hastily-acquired preconceptions around for years, until this year I needed to do a small application for work, and decided to try it in F#. To my surprise I found that none of these preconceptions were valid! What’s more, I actually quite enjoyed it. F# seemed to let me do everything I would normally do in C#, only with less code, and with more powerful features waiting in the wings should I want to dabble with them.

So in this post I wanted to go through why these assumptions were false, just in case they are holding you back too. I think F# is well worth trying out for every developer that does anything with .NET, but rather than trying to sell you on why you should try F#, I’m going to focus on the reasons you may think that you shouldn’t, and trust your natural developer curiosity to do the rest. ;)

Misconception 1: We have to know FP to use F#

Functional programming is programming with pure functions. Functions return the same output whenever applied to a specific input, and they have no side-effects. With this in mind, here is a snippet of F#:

let mutable counter = 0
let NotAFunction() =
    counter <- counter + 1
    printfn "%d" counter

NotAFunction takes no input and produces no output, so it doesn’t meet the first criterion for a function. It also has side-effects – it mutates the counter variable (the <- operator is for destructive assignment) and prints to the console. The first time we call NotAFunction() it prints 1, then next call prints 2.

F# is more than capable of expressing mutable, imperative procedures. It has some features that can help if we want to do FP, just like C# does. We don’t have to use them, nor do we have to be an applicative functor-toting, monad-wielding FP boffin in order to use either language.

Misconception 2: F# syntax is confusing

When I thought "confusing", what I really meant was "different". And what I should have meant was "a little different". F# is often terser than C# which threw off my brain’s pattern matching a bit, but this disorientation didn’t last much longer than my first hour of writing F#.

F# has most of the C# constructs you’ve come to know and love, and in very similar forms. We still have equivalents to for, while and foreach loops (F# uses for .. in instead of foreach), if .. else if .. else conditionals, try .. catch (with instead of catch), using (open) etc. Nothing a quick trip to the F# language reference on MSDN won’t fix.

Granted, we can (and should) write F# in a quite non-C#ish style which can result in more of a culture-shock1, but we don’t have to start out like that. We can happily write C#ish F# code and get immediate benefits from the relative terseness of the language, with the added benefit of gaining access to more powerful features to explore later on if we choose to.

Misconception 3: F# is not for "normal" applications

F# seems more than capable of expressing the standard OO designs we tend to see in general C# applications, including GOOS-style, class-with-injected-dependencies-style OO.

public class MonadPolice {
  private readonly ImDave _dave;
  private readonly IEmailGateway _email;

  public MonadPolice(ImDave dave, IEmailGateway email) {
    _dave = dave;
    _email = email;
  }

  public void Surveil() {
    var overheard = _dave.RecentRamblings();
    var zealotTalk = overheard.Where(x => x.Contains("monad"));
    foreach (var outburstOfZealotry in zealotTalk) {
      _email.Send("xerx", "the monad police",
                  "Lack of pragmatism detected", outburstOfZealotry);
    }
  }
}

Here’s one way to express this in F#:

type MonadPolice(dave : ImDave, email : IEmailGateway) =
  member this.Surveil() =
    let overheard = dave.RecentRamblings()
    let zealotTalk = overheard.Where(contains "monad")
    for outburstOfZealotry in zealotTalk do
        email.Send "xerx" "the monad police"
                   "Lack of pragmatism detected" outburstOfZealotry

We get the same capabilities and style, in a similar albeit terser syntax. We provide the default constructor arguments in the type declaration, which gives us a constructor and what are effectively private, readonly fields for free. F# also has sensible defaults for member visibility, so much of the time we won’t need to explicitly mark something as public or private. And we can test it in the same way as usual too.

So F# is not just for modelling financial or scientific calculations. It’s fine for querying repositories, sending emails, and doing whatever useful stuff our apps normally need to do.

The fine print

Not everything about my first F# project was positive. The main problems I had with F# were around tooling.

  • No templates (as far as I could tell) for F#-backed WPF views (same for MVC?), so it may be easiest to stick a C# shell around a core F# project. Update July 2014: This is no longer strictly true. There are quite a few templates in the VS gallery and in the F# Community Templates site.
  • Many ReSharper and VS shortcuts and refactorings I reach for by reflex just weren’t there. Update July 2014: Still true of ReSharper and VS, but much less of an issue now that VS F# Power Tools covers the main stuff I need.
  • For some reason VS doesn’t provide a way to add folders to an fsproj. Update July 2014: This is also covered by VS F# Power Tools. Thanks to Isaac for pointing this out.
  • And perhaps most disturbingly, I have to define members before they are referenced elsewhere, which means I actually have to explicitly order the files in my project (ugh!). Update July 2014: Still true, but after using F# for a while now I’m surprised by just how little this bothers me. It actually has some advantages.

There are also a couple of things we lose that C# has, like combined try .. catch .. finally (we have to use nested try/with and try/finally for F#), and the protected modifier (it works for interop, but we can’t declare a member protected as of F# 3). But F# offers a lot of features C# lacks to compensate.

Why bother?

I’ve attempted to argue we shouldn’t ignore F# on the grounds that it can work similarly to C#. Why not just stick to C# then?

For me, F# offers new opportunities to improve the way I develop software. We gain access to other sorts of types, like discriminated unions and records. We get usable tuples. We gain the ability to compose methods from existing methods, rather than requiring us to create new classes and plumb objects together. We get terser syntax and some interesting ways to create embedded DSLs. We get immutability by default, as well as non-nullable references.

And perhaps best of all, we can choose when we start experimenting with this stuff. To me it seems we can start off using F# as a terser C# with some neat defaults, and slowly start exploring some of these other ideas that have been largely inaccessible to us C# devs, without the risk of having to learn a whole lot of new stuff before we can be productive.

Conclusion

My F# experience has admittedly been very limited so far, so I’m not sure what problems await for larger projects, but it has been more than enough to encourage me to spend more time with F#.

I’ve learned we don’t need to know FP to use F#, the syntax isn’t really a problem and can actually be used as a terser form of what we already know, and that we can apply F# to the same problems and even using the same patterns as we normally do in .NET. In exchange, we gain the ability to try out new techniques and improve the way we develop software.

I’m tempted to stick an F# project into every VS solution I work with, and see if it ends up accumulating loads of useful functionality, or whether it will need a more concerted effort at adoption.


  1. I’ve also seen some C# written in a style that has taken me quite a while to parse, despite me knowing the language reasonably well.

Comments