Mendelssohn. Fantasy in F# minor. Part 1 of 3

Comments on F# Language

Table of Contents

Introduction

This document includes my comments on the current (July 2008) state of the F# language and related tools. It is based on F# 1.9.4.19. Further developments in F# may make some of the statements I make wrong or obsolete.

I look at F# from the point of view of a C#/C++/Java application developer: as in "what will happen if I have to write my next project in F#". So, some views may be biased or seem strange for a seasoned functional programmer.

Also, as F# development team rightfully pointed out, F# is still in a research phase, so judging it by production standards may be a little harsh. Productizing F# is still an ongoing affort. I completely agree with that, but if we intend to write production code on it, we will have no choice, but to judge it by production standards.

Language Name

The Good
It is short and easy to remember
Beats "c-plus-plus" by one sillable :-)
The Bad
Yet another language named after a musical note.
Music fans must be getting mad when they google for compositions written in F# key (one of them is performed by clicking on the video to the right). Programmers also occasionally get links to Beethoven and Mendelsohn when googling for F# stuff.
How much "functional" is in F#?
F# probably stands for "Functional Sharp", or at least it is implied. I have certain reservations on how really functional F# is. It is a multi-paradigm language, and I feel one will be frequently forced to use non-functional elements of it. "How Functional F# Really Is" for more details.

Language Syntax

The Good
Short code
F# code tends to be pretty coincise compared to C# or java.
The Bad
Duality of syntax
You get two languages for the price of one: light and classic. The grammar reflects only the "classic" mode, that uses keywords like "done" and separators like ";;" to delimit source constructs. Light mode allows omitting some keywords, but the line indentation becomes significant. The lexer infers the "missing" tokens from the indentation, and supplies them to the parser. So, you write one thing, and the parser actually sees another. This may lead to confusion and funny error messages, at least in some cases. This problem is unlikely to go away. We want the light mode, but we still need the classic for compatibility with Ocaml.
Skimpy specification
When I started looking at F#, the specification was outright awful. In late June 2008 Microsoft published a much better specification, but there is still some work to do. In some cases the specification is outright wrong (e.g. ~~~ does not work as logical not, at least not for me). I expect the specification will get better and better over time.
Surprises for C-like syntax addicts
Some choices of F# keywords and operators are particularly hurtful to people raised on C-like languages (C, C++, Java, C#). In particular, I am talking about using the exclamation mark ! to denote dereference. When a C/C++/C#/Java programmer sees something like !x, the last thing he would think of is "dereference x". Another surprise is neccesity to explicitly name and reference this parameter in class member declarations. This feels a little bit like when you try to write object-oriented code in C.
Whether we want that or not, C-like languages dominate current programming landscape and cannot be freely dismissed.
Using the same keyword for multiple purposes
E.g. keyword with is used for:
  • Pattern matching: match foo with...
  • Exception handlnig: try ... with ...
  • Extending existing types: type foo with...
  • Creating modified copies of existing objects: {foo with bar=42}
Of course, F# is not the only language guity of such thing. E.g. C++ has at least 3 loosely related usages of the keyword static.
Syntactic confusion between enums and discriminated unions
See "F# enums and discriminated unions" for details.
The Ugly
Poor readability
F# has many symbolic keywords that mean little or nothing for an average programmer. E.g. most people will have an idea what 'foo+bar' might mean, but very few people can predict the meaning of 'foo?:>bar', or intuitively tell the difference between '[<x>]' and '[|x|]', '<@x@>', '<@|x|@>', '<@@x@@>', and '<@@|x|@@>'. I guess, this is a flipside of shorter code. Anyhow, in my humble opinion '<@@|' takes it too far. The next station on that train is APL with expressions like (∼R∈R°.×R)/R←1↓ιR .

Language Features

The Good
Functional programming
is pretty cool. Lambdas, higher-order functions, closures, and the like have made their way into mainstream languages, because they are very powerful and expressive.
Pattern matching
is also a nice feature, especially with active patterns. It allows to think of things differently and may yield more direct and succint code than the standard approach.
The library
F# has some neat classes in its library like immutable sets, structural hashmap, etc., as well as a number of collection-manipulating methods like fold or iter.
The Bad
Skimpy Specification
many features are defined very briefly, or almost not at all. E.g. the F# language specification contains exactly one occurence of construct ->> with comment "yield computation", and one occurence of << with even more enlightning comment 'op_ComposeLeft'. These operators might be something very powerful and interesting, but this does not count as a definition.
Clash of Ocaml and .NET ideologies
F# is in fact a port of Ocaml to the .NET platform. Since F# does not exist in a vacuum, concepts of .NET platform foreign to Ocaml leak in. E.g. Ocaml on principle does not have nulls. .NET does have nulls, and it leads to confusion and duplication. E.g. Ocaml's 'a option and .NET's Nullable<T> are identical in purpose, but distinct in form. Another clash is between seq a.k.a. IEnumerable and LazyList
Limited polymorphism
in F# Liskov substitution principle does not always work. In particular, function parameters are not polymorphic by default.
No automatic memoization
F# is often marketed as the language which promotes immutable variables language, that improves thread safety. While this is true to some extent, F# lacks automatic memoization of intermediate results (unlike, say, Haskell). As ar as I know, this is a property of all ML languages, and not a fault of F# in particular. Whatever the reason, memoization does require intermediate mutable variables, e.g. as shown in "Memoizing computations" paragraph in chapter 8 of the "Expert F#" book.
No native XML serialization
.NET XML (de)serializer requires mutable types. It works by iniializing a "default" object and then fills its fields with what it reads from XML. Since variables in F# are not mutable by default, there is no native XML serialization support for F# types.
Complicated rules and syntax for class constructors
F# rules for class constructors take several pages and are quite complicated. We have explicit constructors, implicit constructors, do declarations, then declarations, as well as val declarations and member declarations. And, by the way, some of those can be static as well. Compared to this, C# and Java are simplicity itself. Or at least I am used to it. The rules are by no means arbitrary, and they make sense in the end of the day, but the whole thing is a little intimidating.
The Ugly
How much "functional" is in F#?
OCaml, the F# predecessor is not a pure functional language. It is a mix of functional and imperative/OO approach. Porting OCaml to .NET, whith its non-functional class library and object model reduced "functionness" and general language coherency even further. See "How much 'functional' is in F#?" for more details.
Dependency on source file order
Most programmign languages that were commercially succesful in the last 40 or so years do not depend on the order of source files. Source files can be supplied to the compiler in any order, and the system magically figures it out. Not so in F#. It seems that F# completely lacks the concept of linker. If construct B uses construct A, the compiler must see the definition of A before the definition of B, or else you'll get "A is not defined" error. This means the developer must constantly keep in mind the right order of source files. It also has significant implications to the tools support quality, since modern tools like Visual Stuido are not designed for this kind of behavior.
No support for native constants
In C# you can write const string fox = "The quick brown fox jumps over the lazy dog". In F# it is not currently possible. Microsoft F# team promised to fix this with [<Literal>] attribute in some future release, but currently it hurts badly, in particular in pattern matching. I ended up defining my literal in C# and referencing it from F#. Ugly-ugly-ugly.

Tools Support

The Good
You can use F# in Visual Studio
this by itself is a huge boost in productivity. One small point: F# projects are under "Other Project Types" rather than expected "Other Languages", so I could not find it for some time after I installed F#, and I don't believe it is mensioned anywhere in the documentation. Small point, but it cost me 40 minutes or so, before I started to meticulously open each and every project type looking for F#. Maybe I am just slow.
Intellisense works
which is of great help.
You can call most (all?) types in .NET class library
so you can stand on the shoulders of giants.
The Bad
Each new source file is pre-loaded with long example code
this is nice the first time. This is amusing the second time. This gets outright annoying the tenth time. Examples do not belong in default content for new source files, period. Of course, this can be easily fixed, and it probably will.
The Ugly
Poor compiler diagnostics
Most compiler messages I received simply stated "syntax error". Paradoxically, as my F# skills improved and I started to make more "sofisticated" errors, compiler messages got better. Generally, "syntax error" is caused by the most trivial mistakes like forgotten equal sign or bracket. My hypothesis is that the F# compiler is tailored to the practical needs of the F# research team. Being quite advanced F# programmers, they don't make many stupid mistakes, and if they do, they can quickly figure out what's wrong. This is why simple mistakes yield "syntax error", while more sophisticated mistakes yield detailed diagnostics. Whatever the reason, current state of the compiler diagnostics is not acceptable for the general public. Beginner programmers will mostly make simple mistakes and will not know how to get out of it, and "syntax error" is not much of a help.
Poor support for large projects
I already mentioned the source file order. Finding the right order is one problem. Maintaining that order is another. Every time you add a new file, it goes to the end. Every time you rename a file, it goes to the end. Current suggestion for changing the compilation order are outright ridiculous. They recommend to unload the project, change project file text by hand (!), and then reload the project. Another problem are assembly references. You cannot easily reference your F# projects from your C# projects and vice versa, even if they are in the same solution. You need to manually specify the references on the project settings level. All this may make maintaining large F# project a nightmare. And the prospects are not very bright here. I am afraid putting decent file order functionality into Visual Studio may be a challenge, since it is not designed for this kind of thing. From the other hand, some people are not as spoiled as I am. Someone commented on my blog that they "maintain F# code bases in excess of 250kLOC of code without a problem".

Summary

Unfortunately, at the time fo writing (July 2008) F# still does not have a proper level of tool support that would allow to do large-scale production development. Even the Microsoft's F# team does not seem to deny that. I believe this situation will improve with time.

F# is a large language and it is not fully documented. There are many cryptic symbol combinations, and certain syntactic choices are surprising for programmers with C/C++/Java/C# background. So, expect steep learning curve.

One of the great advantages of F# is shorter code, but there are other, less radical efforts in that direction, e.g. the Boo language.

Functional paradigm is a powerful tool, but F# is actually a functional/imperative/OO blend, and things don't always blend very nicely.