Sunday, June 26, 2011

My impressions of F# after three years

It will be soon 3 years that I have been using F# both professionally and privately.

At work, I have used it to implement the simulator module of Prover iLock. This is a complete rewrite of the first version of the simulator, which was initially written in C#. The rewrite was necessary for a number of reasons. As is often the case with the first version of any software, its design did not age well, leading to bugs, excessive memory use and long execution times. I think the main reason was simply that requirements were not clear when the project was started.

The main challenge of this simulator is to support distributed systems where components use different execution models and link protocols. The term "execution model" is a bit specific to the field, namely railway signalling systems. Historically, these systems were implemented using relays. Their modern counter-parts include relays (they are still popular), software which emulates relays (to take advantage of existing field knowledge and practices) and to a lesser extent procedural languages.
The second category include platforms such as VHLC and Microlok, but there are many other similar yet different technologies. Although similar, they all differ slightly in how they handle communication with remote devices, how timers are handled, in what order instructions are executed...

I am happy to report that so far, most of the objectives for the new implementation have been reached or exceeded:

  • Adding a new platform takes little effort.
  • Memory use has drastically gone down, despite massive use of short-lived arrays to hold state.
  • Run-time efficiency is OK, although a bit worse than the first version in some cases. The main reason here is that I made no attempt to perform some of the optimizations that introduced bugs in the first version (maintaining consistency with respect to time when simulating distributed systems is difficult). 
  • Few known bugs.

Time will tell how this second version ages, but so far, I haven't needed to spend a lot of time extending or fixing it. Which is a bit sad, as it means I am not spending as much time working with F# as I would like :)

Another important F# module I developed was a bridge translating the C# representation of systems from the main program to the simulator module. This other project is also successful. The main lesson learned is not to reuse data between different software modules. This may sound surprising at first, but the gains of designing data structures that do one thing well outweigh the costs of maintaining similar types which appear to duplicate code. The data structures in the main program are designed to make it easy to build and modify them via a scripting API. The data structures in the simulator are designed for efficiency of simulation.

Defining types in F# is easily done thanks to records and discriminated unions. Translating from one type hierarchy to another is facilitated by pattern matching. I'm inclined to think the lack for these constructs in other languages leads to excessively large and complex data-types, as they try solve multiple problems at once. Although the evils of code duplication are well known, there are also risks associated to excessive code sharing. Getting the right balance in the number of data types requires a flexible and rich family of types, and it's now clear to me that C# is too poor in that respect. Classes and enums are not enough.

I have used F# in a range of projects, mostly game-related. Asteroid Sharpshooter was released in January. Although not a commercial success (168 copies sold so far), it showed it was possible to write games with high performance requirements in F#, despite the weaknesses of .Net on the Xbox 360. I have not gotten a single bug report so far, but this may be due to the small amount of users. Its rating of slightly over 3 stars out of 5 seems to indicate users are moderately satisfied with the game.

I have also developed a "new" way of developing game user interfaces which I think blows the "old" way out of the water. The new way I am referring too allows to write reactive UIs over the game update loop in a simple way, allowing for much faster development and more robust software. The old way either leads to unresponsive interfaces or buggy ones that fail to deal with storage management or any other lengthy processing requiring asynchronous programming. I doubt many programmers without a functional background realize the power of computation expressions in F#.

By the way, I often hear and read that "F# is not good for UI". This is total nonsense. I normally try to express myself in a gentle, moderate, non-controversial way on this blog, so just to emphasize how I feel about this:

"F# is not good for UI" is total nonsense.

There are basically two problems with UI.

  1. Lots of boring code to write: widget layout, boiler-plate...
  2. Achieving good responsiveness
Point 1 is addressed by tools and code generation, and is actually language-independent. What do I care if automatically-generated code is in C#, IL or F#? As long as I don't have to write it...

Even in the case when I do write the code myself, I don't find C# and F# to differ significantly in that matter. F#'s supported for nested  variable and function definitions help limit the number of members in a class, which avoids one of C# problems, namely over-blown classes with numerous private fields and methods.

Point 2 is solved by F#'s async. In that respect, I wonder how people have been dealing with the problem in C#? BackgroundWorker and threads are not so fun to use.

In all my projects, I think I have noticed two strong differences with developing in a C++-like procedural language:

  1. I spend a lot less time debugging. Modulo trivial bugs, functional code that is correctly typed just works out of the box. Any time I went out of the pure functional path to introduce mutability, direct access to items in arrays... I introduced bugs.
  2. The relatively low verbosity of F# and its high-level constructs make it easier to implement more complex algorithms. For instance, I could never get my head around Runge-Kutta(4) in C++, implementing it in F# was trivial, all while learning F#.
On the negative sides, I found that Visual Studio's debugger can be difficult to use with F#. In other words, it's good you don't spend a lot of times debugging, because it can be hard to understand what's going on in the debugger. Two of the main issues, I think, reside on the one hand in the fact that a debugger is statement and line oriented, where a functional language is expression-oriented, and on the other hand in a somewhat confusing handling of F# closures and lambdas by the debugger. For instance, debugging code in computation expressions is close to impossible.

That's all for now. If you are decision-taker in your company, and .Net is part of the technologies you use, I think you simply cannot afford not looking at F#. I know every time a new language is introduced, productivity gains are claimed by its inventors.

I did not invent F# and don't work for Microsoft, but I can vouch for the alleged productivity gains. Your mileage may vary, but I don't think it will.

1 comment:

Art said...

John your blog is very welcome.
I'm still learning, so it's hearting to hear you confirm F# for UI ...

I'm developing Refolding Planar Polygons by Iben O'Brien and Demaine.