F# is a strongly typed, functional-first programming language that lets you solve complex problems by writing simple code. Based on ML and built on the .NET Framework, F# offers good interoperability, portability, and run-time speed, as well as the “Five Cs”—conciseness, convenience, correctness, concurrency, and completeness.
F# was initially available only on Windows, as a Microsoft Research project, but it’s now a first-class language on a number of platforms. You can use F# on the Mac and Linux with tool support in Xamarin Studio, MonoDevelop, Emacs, and others; on Windows with Visual Studio, Xamarin Studio, and Emacs; and on Android and iOS devices and on the Web using HTML5. In addition to general purpose programming, F# is applicable to GPU code, big data, games, and much more.
Why use F#? Let me give you 14 reasons.
F# is interactive
One of the advantages of F# is that it has an interactive REPL (read, evaluate, print, loop) where you can try out code, as shown in the screen image below. Clockwise, from the top left, we are seeing F# Interactive windows from Visual Studio in Windows, from TryFSharp running in Chrome, and from Xamarin Studio running on Mac OS X. The ;;
tells F# Interactive to evaluate what you’ve typed; on TryFSharp the “run” button sends the same signal. Using a REPL to compile and test code before it goes into a full program both speeds up development and reduces bugs.
The F# REPL.
F# is for scripting
F# can be used as a scripting language as well as a programming language. Below we see a Visual Studio sample in which an F# script loads four F# program files and opens two .NET libraries before executing its own code. The notation [|…|]
used here declares an array. The notation |>
is a forward pipe, which passes the result of the left side to the function on the right side. The new lines here are not syntactically significant. They just make the code easier to read than having entire pipe expressions on a single line.
F# is for scripting.
F# is functional
F# supports functional programming constructs such as treating functions as values, using unnamed functions in expressions, composition of functions to form new functions, curried functions, and the implicit definition of functions by way of the partial application of function arguments. In the upper screen shot below, we define and use an add
function. The body of the function is indented (like Python) and the argument types are inferred as integers because of the +
operator. In the lower screen shot, we supply a type annotation after the argument name using a colon and a type name, so F# knows that phrase
is a string
type.
F# function definitions.
F# is concise
The code below is a Quicksort-like algorithm implemented in F# (by Scott Wlaschin). The rec
keyword indicates that the function is recursive. The match..with
syntax is a switch
statement on steroids, with |
indicating cases. The []
indicates an empty list. The firstElem
and otherElements
are created automatically.
Note that there are no type declarations mentioned anywhere in the code, meaning that the function can sort lists containing any type that supports comparison operators. The fun
keyword is for defining an anonymous lambda function.
let rec quicksort list =
match list with
| [] -> // If the list is empty
[] // return an empty list
| firstElem::otherElements -> // If the list is not empty
let smallerElements = // extract the smaller ones
otherElements
|> List.filter (fun e -> e < firstElem)
|> quicksort // and sort them
let largerElements = // extract the large ones
otherElements
|> List.filter (fun e -> e >= firstElem)
|> quicksort // and sort them
// Combine the 3 parts into a new list and return it
List.concat [smallerElements; [firstElem]; largerElements]
//test
printfn "%A" (quicksort [1;5;23;18;9;1;3])
For comparison, take a look at the traditional C# implementation below.
public class QuickSortHelper
{
public static List<T> QuickSort<T>(List<T> values)
where T : IComparable
{
if (values.Count == 0)
{
return new List<T>();
}
//get the first element
T firstElement = values[0];
//get the smaller and larger elements
var smallerElements = new List<T>();
var largerElements = new List<T>();
for (int i = 1; i < values.Count; i++) // i starts at 1
{ // not 0!
var elem = values[i];
if (elem.CompareTo(firstElement) < 0)
{
smallerElements.Add(elem);
}
else
{
largerElements.Add(elem);
}
}
//return the result
var result = new List<T>();
result.AddRange(QuickSort(smallerElements.ToList()));
result.Add(firstElement);
result.AddRange(QuickSort(largerElements.ToList()));
return result;
}
}
You’ll notice how much extra cruft the C# code has compared to the F# code.
F# is really concise
According to Scott Wlaschin, the version of quicksort shown below—all four lines of it—has the typical concise look of F# written by an experienced functional coder. Of course, he’d be the first to point out that it doesn’t sort in place. It took me multiple readings to make sense of the code, but it was worth the time.
let rec quicksort2 = function
| [] -> []
| first::rest ->
let smaller,larger = List.partition ((>=) first) rest
List.concat [quicksort2 smaller; [first]; quicksort2 larger]
// test code
printfn "%A" (quicksort2 [1;5;23;18;9;1;3])
Briefly, the first case returns an empty list if passed one, providing an exit criterion; the second case splits the list into the first element and the rest, assigning the sublist starting with the smaller value to smaller
and the other sublist to larger
. Within the concatenation of the sublists, the function recursively sorts the smaller
and larger
lists.
F# reduces bugs through strong typing
Unlike JavaScript, Ruby, and Python, F# is strongly typed, not dynamically typed. Unlike C and C++, which are also strongly typed, but require all types to be declared, F# performs type inference whenever possible. When type inference is not possible, but the type needs to be known, the F# compiler will throw an error and suggest that you supply a type annotation, as we had to do in an earlier example for the (phrase:string)
argument to the toHackerTalk
function. Catching a type mismatch at compile time eliminates a whole class of run-time errors to which dynamically typed languages are prone.
F# is strongly typed with inference.
By the way, F# let
bindings are immutable unless you specifically declare them mutable
.
F# has a large, well-chosen set of objects, including List, String, and Array
As you can see from the IntelliSense below, F# has rich List, String, and Array modules based on the .NET Framework. In this respect, it is also an object-oriented language, even though it is first and foremost a functional language. Notice that it doesn’t matter whether you use the module name or a typed variable name—when you add the dot, the member functions will pop up. Some people argue that explicitly using the module name is a better style for a functional language than dotted variables, but I don’t completely buy that argument.
F#’s String, List, and Array module functions.
F# is useful for MapReduce
MapReduce is an efficient two-step process often used on big data, and explicitly supported in Hadoop. In this F# example, we are mapping and reducing a list of integers. First we filter the list to the even numbers, then we double each number, and finally we take the sum of all the elements in the list to aggregate or reduce the result. List.map
is a powerful higher-order function; a higher-order function is one that takes another function as an argument. In addition to lists and arrays, F# supports records, sequences, data type providers, and LINQ (language-integrated query).
F# mapreduce on a List.
F# has records
F# records represent simple aggregates of named values, optionally with members. In the example below, first we define a Book
record type with four named values, and then we create a record using the same four names. The F# compiler correctly infers the Book
type by matching the names.
An F# record.
F# records can have optional values
Records don’t always have to include all of their named values. If you give a named value the option
attribute when you define the type, then it can be left out of a record. When you set an optional value, it can either be None
, which winds up as a null
, or it can be Some
followed by the value you want to set. Record fields differ from classes in that they are automatically exposed as properties. Classes and structures in F# are .NET classes and structures, compatible with C# and Visual Basic .NET, so I’ll forgo examples.
An F# record with an optional attribute.
F# has sequences
A sequence in F# is a logical series of elements all of one type. Sequences are particularly useful when you have a large, ordered collection of data but do not necessarily expect to use all the elements. Individual sequence elements are computed only as required, so a sequence can provide better performance than a list in situations in which not all the elements are used. The Seq
module provides support for manipulations involving sequences. In the image below, we demonstrate simple sequences, sequences with expressions, and sequences with filters.
F# sequences.
F# supports data providers and LINQ
Below we are using the TryFSharp editor to open an online Freebase meteorology data set and query the data provider for cyclones that have recorded the highest wind values. The query { }
syntax implements LINQ for F#. Use of this DLL is specific to TryFSharp. In Visual Studio, you would open Microsoft.FSharp.Data.TypeProviders
and then use the appropriate data provider service.
Querying a data provider in F#.
The result:
[Hurricane Andrew; Hurricane Hugo; 1900 Galveston hurricane;
Tropical Storm Allison; Cyclone Tracy; Hurricane Iniki; Hurricane Ivan;
1999 Odisha cyclone; Hurricane Katrina; Typhoon Talim; Hurricane Rita;
Typhoon Herb; Hurricane Wilma; Typhoon Vera; 1962 Pacific typhoon season;
Typhoon Ike; Typhoon Mireille; Typhoon Babe; Tropical Storm Arlene;
Hurricane Irene; Typhoon Zeb; Typhoon Maemi; Typhoon Bess; Typhoon Chanchu;
Typhoon Patsy; Typhoon Ewiniar; Hurricane Ioke; Typhoon Xangsane;…
F# can analyze Hadoop data
In this example, we use the TryFsharp editor to open a Hadoop Hive instance that contains, among other data sets, measurements of iris flower features, along with units of measure annotations. Accordingly, we’ve enabled the use of unit annotations in the properties of the HiveTypeProvider
.
F# analyzes Hadoop data.
This calculation returns:
val avgPetalLength : float<Data.UnitSystems.SI.UnitNames.metre> = 0.0374966443
F# does pattern matching
The F# match
expression provides branching control that is based on the comparison of an expression with a set of patterns. Lines 1-7 of the example below define a recursive isPalindrome
function. Lines 8-10 define a wrapper function for isPalindrome
that calls it the first time using the entire string. Because “aba” is a palindrome, the then
clause of line 9 fires and returns Some s
, and the match
statement in line 11 generates “The string aba is palindrome”. The _ pattern
in line 14 is the default case.
Using the F# match
statement.
The match..|
statement in F# has many benefits over the switch..case
statement in C#, C++, and Java, the most important of which is that it causes fewer bugs.
F# supports asynchronous workflows
F# has access to all of the .NET Framework, but it also has its own syntax for asynchronous workflows. The async { expression }
syntax defines a non-blocking computation. The do!
keyword performs an asynchronous operation and waits for the result. The let!
keyword waits on an asynchronous operation and assigns the result. And use!
waits on an asynchronous operation, assigns the result, and releases the resource. Async.RunSynchronously
executes an asynchronous operation and waits for its result. To add parallelism, use the Async.Parallel
function, which takes a list of the Async
objects, sets up the code for each Async
task object to run in parallel, and returns an Async
object that represents the parallel computation. Then pipe that result to Async.RunSynchronously
. (The example below is from F# for Fun and Profit.)
An asynchronous operation in F#.
F# resources
For more information on F#, follow the links below.