The Book of F#: Breaking Free with Managed Functional Programming (2014)
Chapter 1. Meet F#
Originally developed at Microsoft Research, Cambridge, F# is a functional-first, multi-paradigm language. In plain terms, that means that while F#’s syntax and constructs emphasize writing code that applies functions to data, it’s also a full-featured, object-oriented language with a few imperative constructs tossed in for good measure.
F# dates back to 2002, but the first major release didn’t appear until Microsoft made version 1.0 available in 2005. F# is descended from the ML language and was heavily inspired by OCaml in particular. Early in its development, the F# team strived to maintain syntactic compatibility with ML, but over time the language has diverged a bit. Gradually, F# has found its place as a first-class citizen of Visual Studio, with project templates available out-of-the-box in every version starting with Visual Studio 2010. F#’s latest release accompanies Visual Studio 2013 and has been designated as version 3.1.
Despite its inclusion in Visual Studio, F# has developed an undeserved reputation as a niche language useful only in academia or highly specialized financial software. As a result, it has failed to secure widespread adoption, particularly in enterprise software, but that seems to be changing as developers are starting to understand the virtues of functional languages. The fact that F# is an open source language licensed under the Apache 2.0 license and there are compilers available on every major platform is also helping the language gain traction. Microsoft continues to contribute heavily to F#, but the language itself is managed by the independent F# Software Foundation.
The goal of this chapter is to give you an idea of how F# programs are organized at both the Visual Studio project and code levels. As you learn the language, you’ll find that F# truly is a general-purpose language capable of meeting the demands of most modern software development tasks.
Unless otherwise noted, the examples in this book were developed with F# 3.1 in Visual Studio 2013 (Professional and Ultimate editions). If, for any reason, you’re not using Visual Studio, don’t fret; the majority of the examples within this book are applicable no matter which platform you’re using.
Although I don’t specifically cover them, if you intend to follow along with a development environment other than Visual Studio, the F# Software Foundation has plenty of resources to help you get started on its website at http://fsharp.org/. You can also try F# in your browser at http://www.tryfsharp.org/.
F# in Visual Studio
Because this book is primarily intended for experienced .NET developers, I’ll assume you already know how to create projects in Visual Studio. I’ll go right into introducing the different F# project templates that are available to you and follow that with a brief discussion about file organization within an F# project.
Each of the Visual F# project templates is listed under the Visual F# category in the New Project dialog, but the category’s location within the list will vary according to your IDE settings. If the Visual F# category isn’t listed immediately under Installed Templates, check under the Other Languages node. If you still don’t see it, make sure the F# components are installed. Figure 1-1 shows each template as it would appear with the IDE configured for F# development and targeting .NET 4.0.
As you can see, there are five templates available. The template names are pretty intuitive, but here’s a rundown:
§ Console Application. Creates a new command-line application.
§ Library. Creates a new library you can reference from other applications or libraries.
Figure 1-1. F# project templates in Visual Studio 2013
§ Tutorial. Is a quick way to peek into what F# has to offer, but it’s not very useful for starting new projects.
§ Portable Library. Creates a portable class library that can be used by both .NET 4.5 and Windows Store applications.
§ Portable Library (Legacy). Creates a portable class library that can be used by both .NET 4.0 and Silverlight applications.
§ Silverlight Library. Creates a new library you can reference in a Silverlight application.
Once you’ve created a project with any of these templates, you should see the familiar Visual Studio interface with the text editor, Solution Explorer, and any other windows you may normally have open. Depending on whether you’ve previously experimented with F#, you may also see the F# Interactive window.
Among the templates that are conspicuously missing are those for Windows Forms applications, WPF applications, and ASP.NET applications. A key reason for the omission is that many of the designer tools haven’t been updated to support generating or understanding F# code. Despite the lack of built-in templates, you can still construct applications with F# using these technologies, but typically you have to do more manual work.
The F# Community Templates repository on GitHub hosts a number of additional templates. At the time of this writing, the repository contains only a handful of templates for Visual Studio, but over time it’s likely that templates for other editors, such as Xamarin Studio, will be added to the mix. You can find the repository athttps://github.com/fsharp/FSharpCommunityTemplates/.
When you first see Visual Studio’s project workspace after creating a project from one of the aforementioned templates, you may be tempted to think that an F# project is just like a C# or Visual Basic project. In some regards, it is. For instance, you can start executable projects by pressing F5, the Visual Studio debugger can step through F# code, and files are managed with Solution Explorer. However, project organization in F# is very different from that of the traditional .NET languages. In fact, you’ll probably find that F#’s code structure is almost as foreign as the language itself.
Traditional .NET projects generally follow the convention of one type per file; that is, individual data types are almost always stored in separate files and organized into a folder hierarchy that mirrors the project’s namespaces. Aside from avoiding circular assembly references, there are very few steadfast rules on how or when something can appear within a project. Barring any accessibility modifiers (public, private, and so on), types and members are free to reference each other and their members regardless of where they are defined in the project.
Some rules are meant to be broken, but in this case F# shredded the project organization rulebook and then burned the remains. It is incredibly prescriptive about how projects are organized, and for good reason: F# code is evaluated from top to bottom. This means that not only is the order of declarations within an individual code file significant, but the order of the files within your project is significant as well!
It’s common for new F# programmers to add a new file to the project, fill in some definitions, and then get compiler errors stating that the new definitions are missing. This is usually because the programmer forgot to move the newly created file above the files that will use the definitions. Fortunately, changing file order within an F# project is relatively painless because there are context menu items and hotkeys to move files up and down, as shown in Figure 1-2.
The other major implication of F#’s top-down evaluation order is that folders are not allowed. Folders wouldn’t necessarily break the evaluation order, but they certainly do complicate it, so there’s no option within the IDE to add them.
You might be wondering what advantage such an evaluation structure could possibly offer. The primary benefit is that the compiler can make more assumptions about your code and, as a result, give you type inference capabilities unrivaled by any other .NET language. Furthermore, this evaluation structure avoids inadvertent recursive definitions (when two or more types depend on each other). This makes you think a bit more about how and where your types are used, and it forces you to be explicit about recursive definitions where they’re appropriate.
Figure 1-2. Move and Add options in Solution Explorer’s context menu
Significance of Whitespace
Newcomers to F# are usually quick to notice the absence of braces or BEGIN and END delimiters. Rather than relying on syntactic tokens to denote code blocks, the designers of F# decided to make whitespace significant.
Code that is inside a block must be indented farther than the line that opens the block. For example, when you define a function, the lines belonging to the function’s body must begin to the right of the first character of the function declaration. It doesn’t really matter how far the lines are indented, only that they are indented and that the indentation level is consistent for each line in a block.
With most programming languages, this is the point where the age-old debate of tabs versus spaces would flare up, but this is not the case in F#. The F# compiler rules with an iron fist on this matter and expressly forbids tabs because the number of spaces that a given tab character represents is unknown. When you begin writing F#, you’ll probably want to configure the options for Visual Studio’s text editor to insert spaces in place of tabs.
ONE SYNTAX TO RULE THEM ALL
To say that F# requires consistent indentation or that it expressly forbids tabs isn’t completely accurate. F# actually has two syntax formats: verbose and lightweight. The verbose format requires you to be more explicit with your code but isn’t as sensitive to indentation. Under verbose syntax you denote the end of a code block not by decreasing the indentation level, but by using additional keywords like end and done.
In F#’s infancy, verbose format was the norm, but as the language has matured, the lightweight syntax has gained favor and is now the default. Of course, there are other differences between verbose and lightweight syntax, but they are beyond the scope of this book. None of the examples in this book use verbose syntax, but should you yearn to write more code, you can revert to verbose syntax by opening a code file with the #light off directive.
There are two primary ways to group code in F#: namespaces and modules. In single-file projects, declaring a namespace or module is optional, as the contents of the file will implicitly become a module with the same name as the file—for example, if your file is named Program.fs, the module will automatically be named Program. In all other cases, though, each file must begin with a namespace or module declaration.
F# namespaces are the same as in C# and Visual Basic in that they allow you to group related code by a name to reduce the likelihood of a naming conflict. Namespaces can include modules and type definitions but cannot directly include any values or functions.
You declare namespaces with the namespace keyword followed by an identifier. For example, a namespace for the code in this book might look like this:
You can also declare more granular namespaces by nesting them. Nested namespaces are declared with fully qualified names, with each level separated by a dot (.). For instance, we could group all the code for this chapter in a nested namespace like this:
Just as in the other .NET languages, you can split namespaces across files and assemblies. You can also declare multiple namespaces within a single file, but you cannot nest them inline; each namespace declaration must be a top-level block.
In the event that you want to place code in .NET’s global namespace, you can declare the namespace with the global keyword as follows:
Whenever you declare a namespace, other code already loaded into that namespace is immediately made available to your code. For all other cases, though, you must either fully qualify the type or module names or import them using the open keyword, as you would with a using directive in C# or an Imports statement in Visual Basic. The following snippet shows both approaches:
// Fully qualified name
let now = System.DateTime.Now
// Imported namespace
let today = DateTime.Now.Date
Modules are similar to namespaces in that they allow you to logically group code. Unlike namespaces, however, they can directly contain values and functions. In practice, modules are more closely related to classes containing only static members in other .NET languages; in fact, that’s how they’re represented in the compiled assembly.
Modules fall into one of two categories: top-level and local. Top-level modules contain all the code in a single implementation file. By contrast, local modules are used when multiple modules or types not belonging to a module are defined in the same file.
You declare modules with the module keyword followed by an identifier, like this:
Unlike namespaces, module definitions cannot span multiple files, but you can define multiple modules within a single file. You can also nest modules directly within a parent module like this:
module NestedModule =
When you want to use both a namespace and a top-level module, F# provides a convenient syntactic shortcut that combines them into a single declaration. To take advantage of this, simply include the fully qualified name before the module name, as shown here:
In the preceding snippet, we declare a module named QualifiedModule within the TheBookOfFSharp.Chapter1 namespace.
As a final note, you can import module members through the open keyword as though they belong to a namespace. For instance, to import any types defined in QualifiedModule, we could write:
To simplify this process for commonly used modules, you can decorate the module with the AutoOpen attribute like this:
By applying this attribute to a module, whenever you explicitly open the namespace containing the module, the module will also be opened.
Expressions Are Everywhere
One of F#’s distinguishing characteristics is that it is an expression-based language; that is, nearly everything that’s evaluated returns a result. As you learn F#, you’ll quickly discover that writing applications and libraries is an exercise in combining expressions to produce results. This is a stark contrast to languages like C#, where typically only methods (and operators) return a result. In F#, seemingly familiar constructs like if...else gain new life because, like all expressions, the if...else expression returns a result. Consider the following snippet, which uses C#’sif...else statement to print a string indicating whether a number is even or odd:
var testNumber = 10;
if (testNumber % 2 == 0)
evenOrOdd = "even";
evenOrOdd = "odd";
Now, compare that with this functionally equivalent code in F#, which uses the if...else expression instead:
let testNumber = 10
let evenOrOdd = if testNumber % 2 = 0 then "even" else "odd"
The first thing you probably noticed is that the F# version is more concise. What might not be immediately apparent, though, is that the F# version eliminates the mutable state that’s present in the C# version (evenOrOdd is uninitialized before it is assigned a value). This isn’t necessarily an issue in this simple example because the mutable state is isolated, but in larger applications, mutable state contributes to a fragile and often unpredictable code base.
You might argue (correctly) that we could write the C# code using C#’s conditional operator instead of the if...else statement to achieve the same effect as the F# code. But the main point of this example is that even seemingly familiar constructs return values in F#.
Application Entry Point
In an F# application, the initializations defined in the last file of the project are used as the application’s entry point by default. For more control over how your application starts, you can define a let bound function as the application’s entry point by decorating it with the EntryPointattribute. This allows you to use an arbitrary function for what would be the Main method or procedure in a C# or Visual Basic application, respectively. Accordingly, the decorated function must accept a string array and return an integer to be valid. Such a function would typically follow this pattern:
let main argv =
// initialization code
Implicit Return Values
Because F# is a language steeped in expressions, the F# compiler can make more assumptions about your code. Because all expressions return a value and all functions are expressions, it is implied that all functions will return a value. Therefore, the compiler can assume that the last expression evaluated within a function is the function’s return value; you don’t need to explicitly state it as such with a keyword like return.
As an example, consider the main function from the previous section. In that function, 0 is implicitly returned because it’s the final expression evaluated in the function. Similarly, consider this function, which simply adds two integers:
let add x y = x + y
Here, the add function accepts two parameters, x and y, and contains only a single expression: an addition operation. Because the addition operation is the last expression evaluated when add is invoked, add implicitly returns the result of that operation.
Your First F# Program
Now that you’ve learned how to structure an F# project, it’s time to see some “real” F# code that goes beyond basic syntax. Although the instant gratification of a traditional “Hello world”–type application is a nice confidence booster when you’re starting out with a new language, I’ve decided to forego that approach in favor of an example that both is useful and provides a nice sampling of many of F#’s capabilities: a Reverse Polish Notation (RPN) calculator.
RPN is a postfix notation for mathematical expressions; that is, it’s a manner of expressing computations where each operator immediately follows its operands. For example, to express computing the sum of 1 and 2, we’d normally write 1 + 2; when using RPN, however, we’d write 1 2 +.
You typically implement RPN calculators by iterating over a sequence of numbers and operators. Each item is inspected and numbers are pushed onto a stack, whereas operators pop the appropriate number of operands from the stack, evaluate, and push the result back onto the stack. At the end of the process, the sole item remaining in the stack should be the expression’s result. Figure 1-3 roughly illustrates how this process looks when applied to the expression 4 2 5 * +.
Figure 1-3. Application of Reverse Polish Notation
Working from left to right, you can see how items are added to and removed from the stack, ultimately producing 14 as the result. As you’re about to see, though, implementing a basic RPN calculator in F# takes only a few lines of code and doesn’t even require managing a mutable stack!
If you’d like to follow along with this example in Visual Studio, create a new project using the F# Application template. When you’re ready, replace the text editor’s contents with the following code (note that F# is case sensitive):
let evalRpnExpr (s : string) =
let solve items current =
match (current, items) with
| "+", y::x::t -> (x + y)::t
| "-", y::x::t -> (x - y)::t
| "*", y::x::t -> (x * y)::t
| "/", y::x::t -> (x / y)::t
| _ -> (float current)::items
(s.Split(' ') |> Seq.fold solve ).Head
let main argv =
[ "4 2 5 * + 1 3 2 * + /"
"5 4 6 + /"
"10 4 3 + 2 * -"
"2 3 +"
"90 34 12 33 55 66 + * - + -"
"90 3 -" ]
|> List.map (fun expr -> expr, evalRpnExpr expr)
|> List.iter (fun (expr, result) -> printfn "(%s) = %A" expr result)
Console.ReadLine() |> ignore
When you’ve finished entering the RPN calculator code, press F5 and observe the output. You should see the results depicted in Figure 1-4.
Figure 1-4. Reverse Polish Notation calculator results
Don’t be discouraged if the RPN calculator code doesn’t make much sense right now; that’s the point! For now it’s enough to recognize that the entire RPN calculation is contained within the evalRpnExpr function. I like starting with this example because it not only shows some idiomatic F# code, but it also demonstrates a number of important concepts, such as default immutability, functions as data, pattern matching, recursion, library functions, partial application, F# lists, and pipelining. These concepts work together to create highly expressive and predictable code. Throughout this book, you’ll explore each of these concepts and many more in detail. As you progress through the book, I encourage you to revisit this example periodically to see just how much functionality is contained within such a small program.
Despite a reputation as a niche language, F# is an expressive, functional-first, multiparadigm language rooted in ML and useful for most modern software development activities. As you’ll see in the coming chapters, writing F# effectively is about learning how to combine the types, functions, and values you’ll define in namespaces and modules into expressions. That said, traditional .NET developers will have to adjust to some of the language’s nuances like top-down evaluation, whitespace significance, and implicit returns. Once you get over the initial learning curve, however, you’ll see how F#’s simple yet expressive syntax will enable you to solve complex problems while producing code that is more stable and predictable.