F# is a functional-first language on the .NET platform, which focuses on helping you solve complex problems with simple, clean code. I'll show you today how writing code that is similar to C#, and writing code that uses features that are completely unique to F#, empowers you to create robust, maintainable solutions. Let's get started!

Concise and Familiar Code

The ability to write clean and concise code that interoperates with all .NET languages is one of the most-cited favorite features of F#. It's possible - and common - to use familiar .NET libraries from F#. Let's compare two very simple code samples that build a console app creating a stopwatch calculating elapsed time. First, the code in C#:

using System;
using System.Threading;
using System.Diagnostics;

namespace cstopwatch
{
    class MainClass
    {
        public static void Main (string[] args)
        {
            var sw = Stopwatch.StartNew();
            Thread.Sleep(150);
            sw.Stop();
            Console.WriteLine(
                "elapsed time is {0}",
                sw.ElapsedMilliseconds);
        }
    }
}

Next, the equivalent F#:

open System
open System.Threading
open System.Diagnostics

[<EntryPoint>]
let main argv = let sw = Stopwatch.StartNew ()
    Thread.Sleep 150
    sw.Stop ()
    Console.WriteLine (
        "elapsed time is {0}", sw.ElapsedMilliseconds)
    0 // return an integer exit code

You'll notice first that the F# is somewhat shorter and lacking in punctuation, but that it's otherwise quite comparable. F# doesn't need curly brackets or semicolons because it's whitespace-sensitive. For those of you who use, or have used, StyleCop, it's like having some of that functionality built right into the language!

For those of you who use, or have used, StyleCop, it's like having some of that functionality built right into the language!

In addition to the lack of curly brackets, there's a return value but no return statement. This is because every function in F# is, well, a function; it should produce the same result every time it's given the same input. When writing F#, consider your inputs and your outputs for each function you write, and strive to minimize the side-effects in your code. Even the main function has a return value. To make it super easy, though, the return keyword is unnecessary. In F#, the last statement in a function that's evaluated is the return value. In this case, that's 0.

Type Inference

F# also has very powerful type inference. Based on the code samples above, it appears that F#'s type inference is similar to C#'s when using var. However, consider the following C#:

public static int square(int x) {
    return x * x;
}

In F#, type inference works even at the function and parameter level. You only have to write:

let square x = x*x

The function is automatically inferred to be of type x:int -> int because the compiler has no additional information as to what type it should be using. In F#, function signatures are written much differently. This means that the function square takes in a single integer x as a parameter, and returns an integer. But here's where it gets interesting: if you need to call that function later using a different type, you have to convert the previous function.

public static float inverse(float y) {
    return 1/(square(y));
}

However, with F#, it's merely a matter of writing the new function.

let inverse y = 1.0/(square y)

The previous function, square, is automatically updated to be of type x:float -> float now, as will be any other relevant code snippets because of the new information given to the compiler about these functions.

REPL

F# provides a REPL (Read-Eval-Print-Loop) to evaluate your code easily. It's a way to instantly evaluate your code without compiling an entire project. You can either type code directly into the REPL, or, if you're working in a script file, it's simply a matter of highlighting the code you'd like to run and choosing alt-enter (in Visual Studio) or control-enter (in Xamarin Studio). This loads the F# Interactive window and sends the code to the REPL. It is automatically evaluated and the result is printed, as in Figure 1 and Figure 2.

Figure 1: Sending code to F# Interactive
Figure 1: Sending code to F# Interactive
Figure 2: Code that has been evaluated in F# Interactive
Figure 2: Code that has been evaluated in F# Interactive

Concise and Powerful Code

F# has several features that let you succinctly model a number of situations and lead you to write beautiful, declarative code. I'll be covering discriminated unions, option types, and pattern matching in this article.

Discriminated Unions and Pattern Matching

If you're unfamiliar with discriminated unions, you can think of them in two ways. First, they're similar to a C# enum type. In the snippet below, I've declared a DateTimeInfo that can be one of the six subtypes.

type DateTimeInfo =
    | OrderDate
    | PickDate
    | ShipDate
    | DeliveryDate
    | CancelDate
    | ReturnDate

Second, unlike an enum type, you can add qualifying information to each subtype. In this usage, the discriminated union really acts like a simple object hierarchy. Consider the following Payment type:

type Payment =
    | CreditCard of CreditCardNumber
    | DebitCard of CreditCardNumber
    | Checking of Routing * AccountNumber

Each subtype here, CreditCard, DebitCard, and CheckingAccount, are types of payments, but have slightly different requirements and need to be handled differently in each case. These four lines of code contain the same basic information as in Listing 1, an idiomatic version of this code in C#, which is normally written into four separate files. In fact, there's even more information in the F# version, as the C# code still lacks structural equality. In order to provide that, you'd have to override the equality implementation, the comparison implementation, and the GetHashCode. Additionally, the C# version still doesn't have proper immutability; there should be no setter at all, and the backing field should be read-only.

Listing 1: C# Simple hierarchy to compare to F# discriminated union

public abstract class   Payment { }

public abstract class   CreditCard : Payment
{
    public CreditCardNumber CreditCard { get; private set; }
    public CreditCard(CreditCardNumber creditCard)
    {
        CreditCard = creditCard;
    }
}

public abstract class DebitCard : Payment
{
    public CreditCardNumber CreditCard { get; private set; }
    public DebitCard(CreditCardNumber creditCard)
    {
        CreditCard = creditCard;
    }
}

public   abstract   class   CheckingAccount : Payment
{
    public RoutingNumber RoutingNumber { get; private set; }
    public   Account  Number   AccountNumber { get; private set; }
    public CheckingAccount(RoutingNumber routingNumber,
        Account  Number   accountNumber)
    {
        RoutingNumber = routingNumber;
        AccountNumber = accountNumber;
    }
}

These four lines of code contain the same basic information as an idiomatic version of the same code in C#, which is normally written into four separate files!

Pattern matching is a natural fit with discriminated unions. It's very similar to a select case statement in C#; it's simply a way to branch your code based on certain cases, but with several more options. See Figure 3 for the many ways of handling pattern matching in F#. C# can only handle the first pattern: the constant pattern.

Figure 3: The F# pattern matching options, according to MSDN
Figure 3: The F# pattern matching options, according to MSDN

What does pattern matching actually look like? For the Payment type in the previous snippet, you have:

let makePayment payment =
    match payment with
        | CreditCard number -> payWithCredit number
        | DebitCard number -> payWithDebit number
        | Checking(rout,acc) -> payChecking rout acc

Because payment is a discriminated union, I'm able to naturally pattern-match on the sub-types, CreditCard, DebitCard, and Checking. I'm also able to access any additional information - for example, the AccountNumber and RoutingNumber for the checking account - in order to use it after the arrow. Even more useful, however, is this feature: When you add a new sub-type to the discriminated union, such as ApplePay or AndroidPay, you'll receive a warning the next time you compile for each and every time that you have pattern-matched on the Payment type but forgotten to include the new subtype. That doesn't happen when you add a new overloaded class in C#!

Warning FS0025: Incomplete pattern matches on this expression. For example, the value 'ApplePay' may indicate a case not covered by the pattern(s).

It's only a compiler warning so it can be ignored, but this has saved many a developer from a 3am phone call to fix a bug in production.

Expanded Feature Set

There are many features of F# that don't exist in C#. So far, you've seen type inference, discriminated unions, and pattern matching, which aren't possible in C#, but do have similarities to some C# features. Let's next check out option types, units of measure, and type providers. None of these are fully available in C#, but type providers are a completely unique feature to F#.

Units of Measure

Do you remember the Mars Climate Orbiter crash in 1999? NASA engineers lost a $125 million machine because a crucial section of code expected metric units, but was unfortunately sent English units instead, resulting in catastrophic miscalculations. If only those engineers had used F#! There's a feature that allows any numeric input to be tagged with any identifier, indicating what type of unit it should have. It doesn't have to be only scientific programming; in fact, it's easy to use units of measure in the code around a warehouse to safely stock, pick, or ship a case instead of accidentally substituting a whole pallet of something.

For example, the following snippet shows how you could write a function to calculate the wind chill temperature in the US:

[<Measure>] type F // Fahrenheit
[<Measure>] type Mi // Miles
[<Measure>] type Hr // Hour

let windChillU (t:float<F>) (v:float<Mi/Hr>) =
    35.74<F> + 0.6215 * t
        - 35.75<F> * float(v) ** 0.16
        + 0.3965 * t * float(v) ** 0.16

First, declare the new types: F, Mi, and Hr, for Fahrenheit, miles, and hours. You don't need to use standard abbreviations, although where standard abbreviations exist, using them would be a good practice. It's also a good idea to use comments to clarify (as in my examples) especially in cases where there might be several standard units for a specific abbreviation. In general, units of measure are often collected together in a single file, near the top of your project. More on this later.

Compare that code to the next example, which adds types for Celsius and kilometers, and contains the formula for calculating the wind chill in Canada.

[<Measure>] type C // Celcius
[<Measure>] type Km // Kilometers

let windChillC (t:float<C>) (v:float<Km/Hr>) =
    13.12<C> + 0.6215 * t
        - 11.37<C> * float(v) ** 0.16
        + 0.4275 * t * float(v) ** 0.16

Here's an interesting fact: When researching the wind chill calculation, I discovered that, although the US and Canada use very similar formulas, the formula that Australia uses is completely different and much more complicated! The US and Canada can use the simpler formulas by assuming that the temperature is already sufficiently cold - at warmer temperatures, relative humidity plays a significant role. The Australian formula looks like this, where rh is the relative humidity:

[<Measure>] type C // Celcius
[<Measure>] type m // Meters
[<Measure>] type s // Seconds

let windChillAus (t:float<C>) (v:float<m/s>) rh =
    t + 0.33<C> *
        (rh/100.0 * 6.105**((17.27*t)/(237.7<C> + t))) -
        0.7<C> * float(v) - 4.<C>

Option Types

Option types are a special kind of Discriminated Union, similar to nullable types in C# with a few added features. In F#, anything can be optional, not just a numeric type. A string, a custom type, even a function all can be optional. Let's consider the following function, containing a pattern match to convert a string array to a string array option. You use this in a console app to safely handle the command line arguments.

let input = match argv with
    | [|""|] -> None
    | x -> Some x

You can also use combinators, which are higher-order functions, such as map, iter, and fold, to act on the optional types. In the following code, you want to retrieve the first element of an array that could be empty, so you use Array.tryHead. This returns an option, possibly containing the first element of the array. Next, you use Option.map to evaluate the Name property of that first element, if it exists. If the element doesn't exist, the following code returns None.

items
|> Array.tryHead
|> Option.map (fun item -> item.Name)

Type Providers

Let's turn now to type providers, which is a powerful feature, completely unique to F#. Simply put, type providers are a means of accessing data, any data that has a schema. The most common type providers connect you with SQL Server, CSV files, JSON, and XML, but there are type providers available for nearly all data forms. There are even type providers for other languages, including R and Python. Using type providers is quite easy, just a matter of a couple lines of code to connect to your data source. Writing a type provider is a very complicated endeavor and won't be covered here.

The benefits to using a type provider over an ORM, such as Entity Framework, are huge.

  • Type providers work within both script files and the REPL, making it much faster and simpler to prototype data-driven code.
  • There are no code-generated files that need to be maintained, even for the SQL Server-type provider that sits on top of Entity Framework. The necessary code generation happens, the files are incorporated into the dll, and then they're cleaned up. This means that your source is always in sync and there's no code lying around that you have to remember to keep updated. Most importantly, though, your data layer is scalable to millions of types. There's a type provider for the World Bank database, which contains thousands of sets of information for every country in the world. Freebase, the now-defunct online database, also had a type provider; imagine an ORM trying to manage that scale!
  • Type providers also provide IntelliSense in the data source, as you can see in Figure 4, which calls in to the World Bank type provider. For a database or CSV file, you'll see a list of table names and then column names. For a Web service, you see a list of available methods to call.

Your source is always in sync and there's no code lying around that you have to remember to keep updated. Most importantly, though, your data layer is scalable to millions of types!

Figure 4: Using the World Bank type provider
Figure 4: Using the World Bank type provider

Let's see an example using two type providers: the World Bank type provider and the JSON type provider. First, I'll connect to the World Bank type provider to find the capital city for every country in the world. Then, using that information, I'll call into the Foursquare API with the JSON type provider to determine the top venue in each capital city. For the complete code, see Listing 2.

Listing 2: Using the World Bank and JSON type providers

// Get data
let wb = WorldBankData.GetDataContext()

[<Literal>]
let Path = __SOURCE_DIRECTORY__ + "\example.json"

type Venues = JsonProvider<Path>
let rest = Venues.GetSample()

// Parse venue data
let   foursquareBaseUrl =
    "https://api.foursquare.com/v2/"; +
    "venues/explore?client_id=" + keys.ClientId +
    "&client_secret=" + keys.ClientSecret +
    "&v=20150907"
let nearPlaceUrl = foursquareBaseUrl + "&near="
let getVenuesFor city =
    if city = "" then None
    else
        let venues =
            try
                Some(Venues.Load(nearPlaceUrl + city))
            with
                | _ -> None

        venues
            |> Option.map (fun v -> v.Response.Groups)
            |> Option.bind
                (fun groups -> groups
                    |> Array.filter
                        (fun g -> g.Name = "recommended" &&
                                  g.Items.Length > 0)
                    |> Array.map (fun g -> g.Items)
                    |> Array.tryHead
                    |> Option.bind
                        (fun items -> items
                    |> Array.tryHead
                    |> Option.map
                        (fun item -> item.Venue.Name)))

// Get top venue by capital city
[for country in wb.Countries ->
    let city = country.CapitalCity
        if city <> "" then
            getVenuesFor city
            |> Option.iter
                (fun v -> printfn "Top recommended venue in %s,
                    %s is %s"   city country.Name v)]

The first piece of code sets up the type providers. Connecting to the World Bank is only one line of code. Setting up the JSON type provider is only slightly more complicated. You must provide a sample set of JSON so that the type provider knows how to parse what it will receive. To do this, you create a literal string value, pointing to a file in the current directory. You declare the type using the sample, then call GetSample. Now you're connected. This is all the set up needed to connect to a data source.

// Get data
let wb = WorldBankData.GetDataContext()

[<Literal>]
let Path = __SOURCE_DIRECTORY__ + "\example.json"

type Venues = JsonProvider<Path>
let rest = Venues.GetSample()

The main piece of code involves parsing the JSON for each city, but first, you need to load the response by calling Venues.Load.

let venues =
    try
        Some(Venues.Load(nearPlaceUrl + city))
    with
    | _ -> None

This returns an option type, so the processing relies heavily on option combinators to process the several levels of returned information. First, you request all of the groups for that city.

venues
|> Option.map (fun v -> v.Response.Groups)

Next, you look for the group containing “recommended” items, and ask for the first recommended item in that array.

groups
|> Array.filter
    (fun g -> g.Name = "recommended" &&
         g.Items.Length > 0)
|> Array.map (fun g -> g.Items)
|> Array.tryHead

Finally, you try to return the name of that first recommended item.

items
|> Array.tryHead
|> Option.map (fun item -> item.Venue.Name)))

None of these levels necessarily exist, yet you're able to continue piping the requests in as normal, knowing that the code is safe. Now that you have a function to handle finding the venue for a city, you create a simple list expression to return all cities. In this case, you choose Option.iter because you're printing each item to the output screen (and causing a side effect) rather than creating a list to manipulate later.

[for country in wb.Countries ->
    let city = country.CapitalCity
    if city <> "" then
        getVenuesFor city
        |> Option.iter
            (fun v -> printfn "Top recommended
                    venue in %s, %s is
                    %s" city country.Name v)]

This is all the code you need - 54 lines total - to take data from two data sources, combine it, and produce a meaningful result. There's no lingering generated code to maintain and no cached files to sort through. As soon as a new country is in the World Bank and the capital city has venues listed in Foursquare, your code will see it and print out a result.

Getting Started

Now that you know all the reasons to try F#, let's get started. If you don't have a copy of Visual Studio installed, you can download a free copy of Visual Studio Community Edition or Xamarin Studio. There are also plug-ins available for Emacs and Atom, among others.

Once you've chosen your editor, it's time to code. F# supports script files, so you don't have to create an entire solution just to play around with code; one file can act as a whole project. To start, simply create a script file called “code.fsx” and some of the code from this article.

F# is whitespace-significant, so be sure that your indentations are correct. Next, highlight the code, and choose Alt-Enter (VS) or Ctrl-Enter (Xamarin Studio). This brings up the F# Interactive window (the REPL) and runs your code automatically.

One more thing to note: In order for F#'s type inference to work properly, F# also uses a strict file ordering in projects. Files are not listed alphabetically in Visual Studio. They're listed in compile order, from top to bottom. In F#, a code snippet can only reference another piece of code that's above it, literally. This is true both within a file and within the solution. For example, Figure 5 shows an example project containing some of the code from this article. Code that's placed at the end of the TP.fsx file will be able to reference all of the code in the project. Code in keys.fsx, however, won't be able to reference any code in either Intro.fsx or TP.fsx. This completely eliminates circular references in your code.

Figure 5: File listing in a solution
Figure 5: File listing in a solution

Conclusion

As you can see, using the F# language allows you to write clean, concise code to solve complex problems. Using some of the features that I've covered here, you can accomplish significant tasks in a very few lines of code. Visit https://fsharp.org/, the F# Software Foundation's official website, for additional information and learning tools.