Why not F#?
This post is a response to a discussion that was held on twitter as a result of me stating that the next version of IronJS will be completely in C# and not contain any F# code at all. Before I explain my exact reasoning behind this I want to state that I find F# to be an excellent language and it’s a joy to work in, Don Syme & Co have done an excellent job with it. If you have followed IronJS over the past two years it should be clear that I have tried really hard to make it work in F#, but as a firm believer in “the right tool for the job” I’ve come to the conclusion that it’s not a feasible to build a high performance dynamic language on top of F# while staying true to F# itself.
The key statement here really is “while staying true to F# itself”, as both F# and C# run on top of .NET they have (at least in theory) the exact same performance characteristics. F# has access to the same things as C# in terms of .NET “low level” features such as native objects, mutability, structs, p/invoke calls, etc. It even adds a couple of things on top of this that C# doesn’t have such as the inline keyword and the ability to mix code and IL instructions inside source files.
But F# adds so much more on top of C#, such as: Immutable Values, Computation Expressions, Pattern Matching, Discriminated Unions, Fast Native Functions, etc. – this list can be made very long. But if you inspect all of these features, you will notice that they are all abstractions on top of the existing .NET functionality (or a compiler feature in the case of immutable values). While these abstractions allow you to write very elegant and concise code, they are often slow. For example with computation expressions and discriminated unions you can create a very elegant AST definition and an equally gorgeous parser, as demonstrated by Matthew Manela.
But again, the parser will be very slow compared to one that is written in hand optimized C#. The AST is immutable, so when you want to optimize a node far down in the AST you need to throw away and re-create every parent node to it, this ends up being slow also due to the overhead of the garbage collector and heap allocations. Now, if you don’t need the absolute best performance you could squeeze out of .NET (which most don’t) then all of these constructs will give you clear code, that is easy to understand and reason about. But in the case of IronJS not being able to squeeze all the performance out of the .NET run-time is not an option.
A lot of people will now say something like: “But F# supports mutability, native .NET objects, etc”. Yes, it does. But the “mutability/imperative/OO” features in F# are hamstrung by several crippling issues, and what you end up with is what feels like a slightly crappier version of C#. Just to mention a few things (there are a lot more, both major and minor):
- Mutually Recursive types must be defined in the same file, so when two classes need to be able to refer to each other they need to be right next to each other in the source also (I understand why F# works like this). This is fine for a few grouped objects, but when you end up with a 2.5k line behemoth that used to be Runtime.fs (open at your own risk) inside IronJS, it’s not fun any more. Whenever I bring this up in ##fsharp on irc.freenode.net everyone tells me that this is due to bad library design. While there are several ways to break apart two classes from each other, they all end up being slow compared to just directly accessing a field on the object instance. Maybe it’s fast enough for you, but it’s not fast enough for IronJS.
- Constructors if used incorrectly will cause a run-time over head on every method call, to verify that the class has been initialized properly. There are also multiple other problems with initializing an instance inside both the implicit and explicit constructor syntax.
- Interfaces can only be implemented explicitly, so you end up having to cast your objects into their interface type constantly.
- Collections the specialized F# collections are slow compared to their BCL counter-parts, especially
Map<K, V>compared to
Dictionary<K, V>, while I understand why they are slower, it doesn’t change the fact that they are.
Again, a lot of people will say something like: “All things you said is true, but just use mutability where you need it for speed and stick to immutability everywhere else”. Oh, how I wish it was this simple. The problem with mutability is that once you let a little of is get in, it spreads like a wild-fire through out your code. Even if a piece of code itself looks immutable, if it ends up depending on something deep down in your code base that uses mutability, then it is by definition is not immutable any more.
In conclusion: I love F#, it’s a great language and if you can stick to what it does really well (immutability and FP) it’s a joy to work in. But as soon as the mutability starts creeping it’s way into the code due to performance reasons (which is exactly what happened to IronJS) then it falls apart very fast and you end up with code that is hamstrung by several crippling issues and that is very hard to follow due to a bunch of quirks in the F# syntax. I usually describe F# OO as a bad version of C#.