Moving Surge.Core from C# to F#
As I’ve mentioned before, learning F# has been one of my goals for a while. During Christmas (2014), I decided to use the free time to start on this, and it’s gone very well. I went through various tutorials and books, and then went in the deep end and rewrote the entire back-end of Surge in F#.
The Universal App namespace (root name Windows) is not exposed to F# projects (even with Visual Studio 2015 and all of the preview bits), so the UI itself (the .xaml and .xaml.cs files) are still C# based, as are the Converters (as many of them use the Windows namespace). You can get around this by creating a C# PCL that exposes the functionality of the Windows namespace, include it as a DLL (a direct project reference gives an error), but frankly that’s a lot of work with limited benefit when it comes to the UI.
I’ve also kept the ViewModels in C# for now, because I want to research proper MVVM work in F# before I think about rewriting the ViewModels. It’s something I’d love to do, as that would mean much more of the app is in F#, but that’s a future project. Even then, some parts of the app MUST be in C# for now.
As this was my first proper attempt at writing something in F# (rather than following tutorials, etc.), I decided to make things easy for myself by changing the project in C# before writing an F# version. It was a good thing too, because the state of Surge was rather abysmal.
It was originally written using MVVM but there was so much bad code that snuck in over time that it became impossible to say where the borders were between the Views, ViewModels and Models. The first step, then, was to do a bunch of refactoring so that I had distinct Views, ViewModels and Models.
I started by extracting the Model/Business Logic layer to a Portable Class Library I named Surge.Core, while the UI & ViewModel stuff were left in the Universal Surge project. During the refactoring I rewrote Surge.Core to use the Immutable versions of System.Collection.Generics and made a few other changes so that I had a better idea about how to write Surge.Core in F# in terms of what I’d expose to the rest of Surge, even though the internal program flow could be quite different.
After that, I rewrote the entire library in F# over the period of about 2 weeks. After the rewrite it offered identical (or at least equal) functionality to the C# version, but is nicer to use and I expose fewer public methods and types.
In the spirit of F# blog posts everywhere, inspired by Kit Eason and Scott Wlaschin, I gathered some statistics about the library as an Immutable C# project and then as an F# project. I’d love to have numbers on the project before I made it immutable, but it was so intertwined with the ViewModel layer that it’s not feasible.
C# Project | F# Project | |
Useful Lines | 891 (48% of total) | 555 (79% of total) |
Null Checks | 13 (1%) | 0 (0%) |
Blank Lines | 315 (17%) | 103 (15%) |
Brace Lines | 468 (25%) | 1 (0%) |
Comment Lines | 167 (9%) | 43 (6%) |
Total Lines | 1854 | 702 (almost 3x fewer) |
Despite the immutability, it’s relatively close to the results Scott had. In C#, about 50% is actual code, and then the vast majority of what’s left is blank lines and brace lines. I have very few null checks, but that’s thanks to the immutable re-write, I suspect. Likewise, with F#, over 75% is actual code. The only real difference here is that I have quite a few blank lines, but that’s probably because I find spacing makes things easier to read.
Performance:
I tested performance of all 3 implementations – C# mutable, C# immutable and F#. The test was very simple – I just timed each update with System.Diagnostics.Stopwatch and averaged out several runs.
As such, all three implementations had a harder initial update (as Surge has to create a complex file structure for a torrent the first time it is added), but each successive update is much faster.
Initial Update:
C# Mutable:
~1100-1200ms.
C# Immutable:
~1500ms. From what I can see, it’s the use of Microsoft.Bcl.Immutable in the file structure algorithm.
F#:
~600ms. A massive improvement, which is likely because the file/folder structure algorithm is surprisingly complex and I’ve just managed to write a better implementation in F# because of the elegance of the language.
Successive Updates:
C# Mutable:
~150-200ms per update.
C# Immutable:
~250ms per update. Just like in the initial update, using Immutable Collections seems to be the culprit.
F#:
~200ms per update. Roughly the same as the initial C# mutable form.
So F# is substantially faster when doing the initial update, and about on par with the best when doing the other updates. Fantastic!
Next, I’m going to work on a “Shared” UAP F# library I can reference in various projects. Amongst other things, I’m going to convert my LittleWatson tool to F#, and make a few improvements (primarily handling async better and transmitting crash data silently rather than requiring the user to send an e-mail). It will be open source, as will the server-side part so that anyone who wants to can add free WinRT bug reporting to their app.
[…] Moving Surge.Core from C# to F# – Alex Hardwicke […]
Excellent job, congratulations, By the way have youheard about MBrace (http://www.m-brace.net)? If you need any assistance contact us on github (https://github.com/mbraceproject