September 2019

Volume 34 Number 9

[F#]

Do It All with F# on .NET Core

By Phillip Carter

F# is the functional programming language for .NET (bit.ly/2y4PeQG). It’s cross-platform and, like all of .NET, it’s open source (github.com/dotnet/fsharp). At Microsoft, we’re huge fans of F# because it brings functional programming to .NET. For those who aren’t familiar with the concept, the functional programming paradigm is one that emphasizes certain approaches:

  • Functions as the primary constructs used to operate on data
  • Expressions instead of statements
  • Immutable values over variables
  • Declarative programming over imperative programming

This means that F# brings some great features to .NET:

  • Functions that are first-class (they can be passed as values to and returned from other functions)
  • Lightweight syntax that emphasizes expressions and values, not statements
  • Built-in immutability and non-null types
  • Rich data types and advanced pattern matching techniques

Typical F# code often ends up looking like what’s shown in Figure 1.

Figure 1 Typical F# Code

// Group data with Records
type SuccessfulWithdrawal = {
  Amount: decimal
  Balance: decimal
}
type FailedWithdrawal = {
  Amount: decimal
  Balance: decimal
  IsOverdraft: bool
}
// Use discriminated unions to represent data of 1 or more forms
type WithdrawalResult =
  | Success of SuccessfulWithdrawal
  | InsufficientFunds of FailedWithdrawal
  | CardExpired of System.DateTime
  | UndisclosedFailure
let handleWithdrawal amount =
  // Define an inner function to hide it from callers
  // Returns a WithdrawalResult
  let withdrawMoney amount =
    callDatabaseWith amount // Let's assume this has been implemented :)
  let withdrawalResult = withdrawMoney amount
  // The F# compiler enforces accounting for each case!
  match w with
  | Success s -> printfn "Successfully withdrew %f" s.Amount
  | InsufficientFunds f -> printfn "Failed: balance is %f" f.Balance
  | CardExpired d -> printfn "Failed: card expired on %O" d
  | UndisclosedFailure -> printfn "Failed: unknown :("

Beyond these core features, F# can also interop with the entirety of .NET, and has full support for objects, interfaces and so forth. More advanced F# programming techniques often involve subtly combining object-oriented (OO) features with functional code, but without sacrificing the functional programming paradigm.

In addition, F# has lots of unique features that people love, such as Computation Expressions, Units of Measure, and powerful types such as Records and Discriminated Unions. See the F# Language Reference at bit.ly/2JSnipy for more information. F# also encourages a style of programming that tends toward safety and correctness: Many F# developers have turned to it after significant experience with other languages that don’t emphasize safety and correctness as much. It has influenced a lot of the recent work that went into C#, such as async, tuples, pattern matching, and the forthcoming nullable reference types feature set.

F# also has a vibrant community that loves to push the boundaries of .NET and create incredible open source components. The community is highly innovative and incredibly valuable to .NET, pioneering UI libraries, data processing libraries, testing methodologies, Web services and more for .NET!

The abundance of features and the vibrant community of enthusiastic developers have led many people to dive into F# both for fun and for their work. F# powers financial institutions across the world, eCommerce systems, scientific computing, organizations doing data science and machine learning, consultancies, and more. F# is also used quite a bit at Microsoft: As one of the primary languages used by Microsoft Research, it influenced and helped power the Q# development platform; parts of Azure and Office 365; and even the F# compiler and Visual Studio tools for F#! In short, it’s used everywhere.

Interested? Great! In this article, I’ll show you how to do some cool stuff with F# on .NET Core.

Overview

I’ll start with the basics of F# on .NET Core and walk though progressively more advanced (and more interesting) capabilities, including how you can use the .NET CLI to create a console application and library project on any OS. Although simple and bare-bones, this toolset alone can be enough to develop an entire application when coupled with an editor like Visual Studio Code and the official F# plug-in, Ionide (ionide.io). You can also use Vim or Emacs if that’s your thing.

Next, I’ll give a brief overview of some technologies for building Web services. Each has some different tradeoffs that are worth mentioning. I’ll then show you an example using one of these technologies.

Finally, I’ll talk a bit about how you can leverage some of the high-performance constructs in .NET Core, like Span<'T>, to cut down on allocations and really speed up the hot paths in your system.

Get Started with F# Using the .NET CLI

One of the easiest ways to get started with F# is to use the .NET CLI. First, ensure you’ve installed the latest .NET SDK.

Now let’s create a few projects that are all connected to each other. Modern terminals support tab completion, so even though there’s a bit to type, your environment should be able to complete much of it for you. To begin, I’ll create a new solution:

dotnet new sln -o FSNetCore && cd FSNetCore

This will create a new directory called FSNetCore, create a solution file in that directory, and change directories to FSNetCore.

Next, I’ll create a library project and hook it up to the solution file:

dotnet new lib -lang F# -o src/Library
dotnet sln add src/Library/Library.fsproj

I’ll add a package to that library:

dotnet add src/Library/Library.fsproj package Newtonsoft.Json

This will add the Json.NET package to the project.

I’ll now change the Library.fs file in the library project to be the following:

module Library
open Newtonsoft.Json
let getJsonNetJson value =
  sprintf "I used to be %s but now I'm %s thanks to JSON.NET!"
   value (JsonConvert.SerializeObject(value))

This is a module that contains a single F# function that uses JSON.NET to serialize a generic value into a JSON string with the rest of a message.

Next, I’ll create a console app that consumes the library:

dotnet new console -lang F# -o src/App
dotnet add src/App/App.fsproj reference src/Library/Library.fsproj
dotnet sln add src/App/App.fsproj

I’ll replace the Program.fs contents in the console app project with the following:

open System
[<EntryPoint>]
let main argv =
  printfn "Nice command-line arguments! Here's what JSON.NET has to say about them:"
  argv
  |> Array.map Library.getJsonNetJson
  |> Array.iter (printfn "%s")
  0 // Return an integer exit code

This will take each command-line argument, transform it into a string defined by the library function, and then iterate over those strings and print them out.

I can now run the project:

dotnet run -p src/App Hello World

This will print the following to the console:

Nice command-line arguments! Here's what JSON.NET has to say about them:
I used to be Hello but now I'm ""Hello"" thanks to JSON.NET!
I used to be World but now I'm ""World"" thanks to JSON.NET!

Pretty easy, right? Let’s add a unit test and connect it to the solution and library:

dotnet new xunit -lang F# -o tests/LibTests
dotnet add tests/LibTests/LibTests.fsproj reference src/Library/Library.fsproj
dotnet sln add tests/LibTests/LibTests.fsproj

Now I’ll replace the Tests.fs contents in the test project with the following:

module Tests
open Xunit
[<Fact>]
let ``Test Hello`` () =
  let expected = """I used to be Hello but now I'm "Hello" thanks to JSON.NET!"""
  let actual = Library.getJsonNetJson "Hello"
  Assert.Equal(expected, actual)

This simple test just verifies that the output is correct. Note that the test name is enclosed by double-backticks to allow the use of a more natural name for the test. This is quite common in F# testing. Additionally, you use the triple-quoted string when you want to embed quoted strings in F#. Alternatively, you could use backslashes if you prefer.

Now I can run the test:

dotnet test

And the output verifies that it passes!

Starting test execution, please wait...
Test Run Successful.
Total tests: 1
Passed: 1
Total time: 4.9443 Seconds

Ta-da! With a minimal toolset, it’s entirely possible to build a library that’s unit-tested and a console app that runs that library code. This alone is enough to actually build a full solution, especially if you pair it with an F# code editor like Visual Studio Code and the Ionide plug-in. In fact, many professional F# developers use only this to do their daily work! You can find the full solution on GitHub at bit.ly/2Svquuc.

This is quite fun, but let’s look at some material that’s more interesting than a library project or a console app.

Build a Web App with F#

F# can be used for a lot more than just library projects and console apps. Among the most common solutions F# developers build are Web apps and services. There are three main options for doing this, and I’ll briefly discuss each one:

Giraffe (bit.ly/2Z4zPeP) is best thought of as “functional programming bindings to ASP.NET Core.” It’s fundamentally a middleware library that exposes routes as F# functions. Giraffe is somewhat of a “bare bones” library that’s relatively unopinionated about how you build your Web services or Web app. It offers some wonderful ways to compose routes using functional techniques and has some nice built-in functions to simplify things like working with JSON or XML, but how you compose your projects is entirely up to you. Lots of F# developers use Giraffe because of how flexible it is, and because it has such a rock-solid foundation as it uses ASP.NET Core under the covers.

Saturn (bit.ly/2YjgGsl) is sort of like “functional programming bindings to ASP.NET Core, but with batteries included.” It uses a form of the MVC pattern, but done functionally rather than with object-oriented programming abstractions. It’s built atop Giraffe and shares its core abstractions, but it gives a much more opinionated take on how to build Web apps with .NET Core. It also includes some .NET CLI command-line tools that offer database model generation, database migrations, and scaffolding of Web controllers and views. Saturn includes more built-in functions than Giraffe as a result of being more opinionated, but it does require that you buy into the approach it enforces.

Suave (bit.ly/2YmDRSJ) has been around for a lot longer than Giraffe and Saturn. It was the primary influence for Giraffe because it pioneered the programming model that both use in F# for Web services. A key difference is that Suave has its own OWIN-compliant Web server that it uses, rather than sitting atop ASP.NET Core. This Web server is highly portable, as it can be embedded into low-powered devices via Mono. The programming model for Suave is slightly simpler than Giraffe due to not needing to work with ASP.NET Core abstractions.

I’ll start by building a simple Web service with Giraffe. It’s easy with the .NET CLI:

dotnet new -i "giraffe-template::*"
dotnet new giraffe -lang F# -V none -o GiraffeApp
cd GiraffeApp

Now I can build the application by running either build.bat or sh build.sh.

Now I’ll run the project:

dotnet run -p src/GiraffeApp

I can then navigate to the route given by the template /api/hello, via localhost.

Navigating to https://localhost:5001/api/hello gives me:

{"text":"Hello world, from Giraffe!"}

Cool! Let’s take a look at how this was generated. To do that, I’ll open up the Program.fs file and note the webApp function:

let webApp =
  choose [
    subRoute "/api"
      (choose [
        GET >=> choose [
          route "/hello" >=> handleGetHello
        ]
      ])
    setStatusCode 404 >=> text "Not Found" ]

There’s a bit going on here, and it’s actually backed by some rather intricate functional programming concepts. But it’s ultimately a domain-specific language (DSL), and you don’t need to understand every little bit of how it works to use it. Here are the basics:

The webApp function is comprised of a function called choose. The choose function is executed by the ASP.NET Core runtime whenever someone makes a request to the server. It will look at the request and try to find a route that matches. If it can’t, it will fall back to the 404 route defined at the bottom.

Because I have a subRoute defined, the choose function will know to crawl all of its child routes. The definition of what to crawl is specified by an inner choose function and its list of routes. As you can see, within that list of routes, there’s a route that specifies the “/hello” string as its route pattern.

This route is actually another F# function, called route. It takes a string parameter to specify the route pattern, and is then composed with the >=> operator. This is a way of saying, “this route corresponds to the function that follows it.” This kind of composition is what’s known as Kleisli composition, and although it’s not essential to understand that theoretical underpinning to use the operator, it’s worth knowing that it has a firm mathematical foundation. As I mentioned earlier in the article, F# developers lean toward correctness ... and what’s more correct than a mathematical basis?

You’ll notice that the function on the right-hand side of the >=> operator, handleGetHello, is defined elsewhere. Let’s open up the file it’s defined in, HttpHandlers.fs:

let handleGetHello =
  fun (next: HttpFunc) (ctx: HttpContext) ->
    task {
      let response = {
        Text = "Hello world, from Giraffe!"
       }
      return! json response next ctx
    }

Unlike “normal” F# functions, this handler function is actually defined as a lambda: It’s a first-class function. Although this style isn’t quite so common in F#, it’s chosen because the two parameters that the lambda takes—next and ctx—are typically constructed and passed to the handler by the underlying ASP.NET Core runtime, not necessarily user code. From our perspective as programmers, we don’t need to pass these around ourselves.

These parameters defined in the lambda function are abstractions that are defined in and used by the ASP.NET Core runtime itself. Once in the body of the lambda function, with these parameters, you can construct any object you’d like to serialize and send down the ASP.NET Core pipeline. The value called response is an instance of an F# record type that contains a single label, Text. Because Text is a string, it’s given a string to serialize. The definition of this type resides in the Models.fs file. The function then returns a JSON-encoded representation of the response, with the next and ctx parameters.

Another way you can look at a Giraffe pipeline is with some small boilerplate at the top and bottom of a function, to conform to the ASP.NET Core pipeline abstraction and then anything you want in between:

let handlerName =
  fun (next: HttpFunc) (ctx: HttpContext) ->
    task {
      // Do anything you want here
      //
      // ... Well, anything within reason!
      //
      // Eventually, you’ll construct a response value of some kind,
      // and you’ll want to serialize it (as JSON, XML or whatever).
      //
      // Giraffe has multiple middleware-function utilities you can call.
      return! middleware-function response next ctx
    }

Although this may seem like a lot to learn up front, it’s quite productive and easy to build Web apps and services with. To demonstrate this, I’ll add a new handler function in the HttpHandlers.fs file that gives a greeting if you specify your name:

let handleGetHelloWithName (name: string) =
  fun (next: HttpFunc) (ctx: HttpContext) ->
    task {
      let response = {
        Text = sprintf "Hello, %s" name
       }
      return! json response next ctx
    }

As before, I set up this handler with the necessary boilerplate to conform to ASP.NET Core middleware. A key difference is that my handler function takes a string as input. I use the same response type as before.

Next, I’ll add a new route in the Program.fs file, but because I want to specify some arbitrary string as input, I’ll need to use something other than the route function. Giraffe defines the routef function exactly for this purpose:

let webApp =
  choose [
    subRoute "/api"
      (choose [
        GET >=> choose [
          route "/hello" >=> handleGetHello
          // New route function added here
          routef "/hello/%s" handleGetHelloWithName
        ]
      ])
    setStatusCode 404 >=> text "Not Found" ]

The routef function takes in two inputs:

  • A format string that represents the route and its input (in this case, a string with %s)
  • A handler function (that I defined earlier)

You’ll note that I didn’t supply the >=> operator here. This is because routef has two parameters: the string pattern (specified by an F# format string) and the handler that operates on types specified by the F# format string. This is in contrast with the route function, which only takes a string pattern as input. In this case, because I don’t need to compose routef and my handler with anything else, I don’t use the >=> operator to compose additional handlers. But if I wanted to do something like set a specific HTTP status code, I’d do that by composing with >=>.

Now I can rebuild the app and navigate to https://localhost:5001/api/hello/phillip. When I do, I get:

{"text":"Hello, Phillip”}

Ta-da! Pretty easy, right? As with any library or framework, there are a few things to learn, but once you’re comfortable with the abstractions it’s incredibly easy to add routes and handlers that do what you need.

You can read more about how Giraffe works in its excellent documentation (bit.ly/2GqBVhT). And you’ll find a runnable sample app that shows what I demonstrated at bit.ly/2Z21yNq.

Go Faster

Now I’ll diverge from the practical application of F# to delve into some performance characteristics.

When building something like a Web service that has high traffic, performance matters! Specifically, avoiding needless allocations for the GC to clean up tends to be one of the most impactful things you can do for long-running Web server processes.

This is where types like Span<'T> start to shine when you’re using F# and .NET Core. A span is sort of like a window into a buffer of data that you can use to read and manipulate that data. Span<'T> imposes a variety of restrictions on how you can use it so that the runtime can guarantee that various performance enhancements will apply.

I’ll demonstrate this with a sample (as seen in “All About Span: Exploring a new .NET Mainstay” by Steven Toub at msdn.com/magazine/mt814808). I’ll use BenchmarkDotNet to measure the results.

First, I’ll create a console app on .NET Core:

dotnet new console -lang F# -o Benchmark && cd Benchmark
dotnet add package benchmarkdotnet

Next, I’ll modify it to benchmark a routine that has a typical implementation and an implementation that uses Span<'T>, as shown in Figure 2.

Figure 2 Benchmarking a Parsing Routine with and without Span<'T>

open System
open BenchmarkDotNet.Attributes
open BenchmarkDotNet.Running
module Parsing =
  /// "123,456" --> (123, 456)
  let getNums (str: string) (delim: char) =
    let idx = str.IndexOf(delim)
    let first = Int32.Parse(str.Substring(0, idx))
    let second = Int32.Parse(str.Substring(idx + 1))
    first, second
  let getNumsFaster (str: string) (delim: char) =
    let sp = str.AsSpan()
    let idx = sp.IndexOf(delim)
    let first = Int32.Parse(sp.Slice(0, idx))
    let second = Int32.Parse(sp.Slice(idx + 1))
    struct(first, second)
[<MemoryDiagnoser>]
type ParsingBench() =
  let str = "123,456"
  let delim = ','
  [<Benchmark(Baseline=true)>]
  member __.GetNums() =
    Parsing.getNums str delim |> ignore
  [<Benchmark>]
  member __.GetNumsFaster() =
    Parsing.getNumsSpan str delim |> ignore
[<EntryPoint>]
let main _ =
  let summary = BenchmarkRunner.Run<ParsingBench>()
  printfn "%A" summary
  0 // Return an integer exit code

The module called Parsing contains two functions that split a string by a given delimiter, returning a tuple representing each half of the string. However, one called getNumsFaster uses both Span<'T> and struct tuples to eliminate allocations. As you’ll see, the results are quite profound.

I’ll run the benchmark to produce the results:

dotnet run -c release

This will produce results that can be shared as markdown, HTML or other formats.

I ran this benchmark on my laptop with the following hardware and runtime environment:

  • BenchmarkDotNet v0.11.5
  • macOS Mojave 10.14.5 (18F132) [Darwin 18.6.0]
  • Intel Core i7-7700HQ CPU 2.80GHz (Kaby Lake), 1 CPU, 8 logical and 4 physical cores
  • .NET Core SDK=3.0.100-preview5-011568 (64-bit)

The results are shown in Figure 3.

Figure 3 Benchmark Results

Method Mean Error StdDev Ratio Gen0 Gen1 Gen2 Allocated
GetNums 90.17 ns 0.6340 ns 0.5620 ns 1.00 0.5386 - - 88 B
GetNumsFaster 60.01 ns 0.2480 ns 0.2071 ns 0.67 - - - -

Impressive, right? The getNumsFaster routine not only allocated 0 additional bytes, it also ran 33 percent faster!

If you’re still not convinced that this matters, imagine a scenario where you need to perform 100 transformations on that data, and it all had to happen on the hot path for a highly trafficked Web service. If that service saw requests on the order of millions per second, you’d be looking at a pretty severe performance problem if you’re allocating in each transformation (or several of them). However, if you use types like Span<'T> and struct tuples, all of those allocations can often disappear. And, as the benchmark shows, it can also take significantly less wall-clock time to execute a given operation.

Wrapping up

As you can tell, there’s quite a lot you can do with F# on .NET Core! It’s very easy to get started, and easy to work your way into building Web applications, too. Moreover, the ability to use constructs like Span<'T> means that F# can be used for performance-sensitive work, as well.

F# is only getting better on .NET Core, and the community continues to grow. We’d love it if you’d join the community in building some of the next great things for F# and .NET!


Phillip Carter is a member of the .NET team at Microsoft. He works on the F# language and tools, F# documentation, the C# compiler and .NET project integration tooling for Visual Studio.

Thanks to the following technical expert for reviewing this article: Dustin Moris Gorski


Discuss this article in the MSDN Magazine forum