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
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.
Copyright (c) Ivan Krivyakov. Last updated: July 1, 2008