The focus of this article is to introduce the F# programming language, providing a description and overview of its building blocks. As we go, we will show some code examples to get to know the syntax of F#.
What is F#?
F# is a mature, open source and functional-first programming language. As a .NET language, it has access to the libraries in the .NET ecosystem and allows you to use the right tools for a specific problem.
What is functional programming?
If we want to talk about F# we have to first discuss Functional Programming.
Functional programming (FP) is a style of writing software that makes extensive use of mathematical functions, avoiding shared state, mutable data and side effects. It focuses on computer results rather than on performing actions. The distinction of a mathematical function is made to point out that they always give the same output value for a given input value and they have no side effects as opposed to functions in imperative programming that could depend on outer variables or the general state of a program.
Some defined concepts:
- Side effects: Any application state change that is visible outside the called function other than its return value
- Pure functions: Given the same inputs, always returns the same output with no side effects
- Shared state: Any variable, object, property or memory space that exists in a shared scope
- Mutating state: Modification after creation. In F# the term value instead of variable is preferred as you can’t change the immediate value after initialization unless the keyword mutable has been used.
Once a value is assigned to an identifier it never changes and the results that functions return are completely new values. F# is not a purely functional programming language, meaning that mutation is possible if you choose to use a more imperative style.
As mentioned previously, F# is a “functional-first” language. This means that the language tries to guide you to use a functional style, but lets you choose another paradigm if needed. You can choose whichever paradigm works best to solve problems in the most effective way.
First contact with F#
Some features of F# (more detail will be provided below) are:
- Strongly typed
- Whitespace instead of commas to separate parameters
- Indentation instead of brackets to delimit sections of code
- Pattern matching for the flow of control
- Recursion instead of loops if using a functional style
If you want to follow along with the code examples or try the language yourself, F# has a nice tool called F# interactive, or FSI. It lets you evaluate your code immediately without compiling an entire project. You can either write code directly into the console or highlight code and send it to the console by pressing Alt+Enter (Visual Studio). It compiles, executes and prints the results of F# code for you.
let function-name = fun parameter-list -> function-body
let function-name parameter-list = function-body
Here’s an example:
What’s going on in this example?
Line 1: We use the open keyword to avoid full qualification of Console.WriteLine
Line 3-5: We use the let keyword to create values and bind them to identifiers. The identifiers first and second were bound to the values 5 and 10, respectively. The numbers identifier was bound to a list of integers. numbers contains [1;2;3;4;5;6;7;8;9;10].
Line 7: We define a function that takes two numbers and returns the sum
Line 8: We define a function that says whether the number passed in is even or not
Line 10: We create a new list which contains only the even numbers, applying the isEven function to the original numbers list.
Finally, although you can use System.Console.WriteLine to send the text to the console, here it’s used to show that F# has access to BCL just like C# does. The different versions of printfn have popularity in F# as they’re considered to have a more functional style (printf, printfn, sprintf).
Another thing you may have noticed is that we didn’t specify types in this example. We let F# inferred them for us with a technique called Type inference. You do not have to specify the types of F# constructs except when the compiler cannot infer the type. If there is not enough information for the compiler, you must be explicit.
F#’s main paradigm: Functional programming
F# encourages you to program in the functional style using expressions and computations that return a result, rather than statements with side effects.
Here we’ll look over some of the constructs, keywords, and types F# offers to facilitate coding in a functional style (We already mention the keywords let and fun above).
“Currying” is the process made by the compiler behind the scenes of rewriting a function with multiple parameters as a series of new functions, each with only one parameter.
This could be read as a function that takes one integer parameter and returns another function which also takes an int parameter and returns int as a result.
In F# every function must have one input and one output. This can be a great way to encapsulate components and hide unnecessary implementation details and it also leads to a very powerful technique which is our next point.
Partial function application (PFA)
Function Composition and Pipelining
Functions in F# can be composed from other functions. The composition of two functions f1 and f2 is another function that represents the application of f1 followed by the application of f2.
- Forward composition operator >>:
Pipelining allows the creation of function chains with data passing from one function to the next one.
- Pipe operator |>:
You can continually reapply the operator to chain functions together as it lets you assign an intermediate result on to the next function concisely. One of the advantages of this operator is that we eliminate the need for type annotations as the last parameter is known earlier and as a result, the type inference can determine the types of a function sooner.
It could be seen as a super switch statement. It’s a general form of a conditional expression, a way of controlling the flow of the program. F# decomposes the input and is able to make computations or transformations.
The syntax is as follows:
match [something] with
| pattern1 -> expression1
| pattern2 -> expression2
| pattern3 -> expression3
We can have different types of patterns to match: constant, list, variable, cons, list, array, tuple, union, record and more. Below we have two examples where we use pattern matching first against a list and then against a tuple.
Matching on tuples
The wildcard ‘_’ represents a catch-all and the when keyword lets you add filters
A recursive function is simply one that can call itself as part of its own definition. Functions are not recursive by default. The rec keyword is used together with the let keyword to explicitly define them.
The example below computes the nth Fibonacci number:
We should be careful with recursion and make sure that the function is always approaching the base case.
Most of F#’s types have built-in immutability. They also have built-in pretty printing, structural equality and are automatically comparable so you don’t need to override toString(), implement IComparable and the like.
In addition to the primitive types (like bool, int, and float) and the common types (like tuples, lists, and arrays), F# also contains two other important built-in types: records and discriminated unions.
A record is similar to a tuple but with the advantage that it contains named fields. You can also add methods or properties using the member keyword if needed.
Facts about records:
- Immutable by default
- They cannot be inherited
- Structural equality semantics (two records are the same if their values are the same)
- They can be inferred by the fields
They represent a well-defined set of choices useful for building up complex data structures. Each value is called union case and they are usually written in PascalCase. Here’s an example of implementing a binary tree:
The label for a union case does not have to have any type after it (Empty in the previous example). Discriminated unions, records, and pattern matching combined allow a rapid domain modeling that helps to abstract away complexity.
F#’s fun does not end here. If you want to deepen your understanding of F#, below you will find a related group of links with more information. Some concepts were not covered here but are equally important, like units of measure and type providers.
If you’re already a .NET developer, it’s not difficult to make the transition to F# because you can concentrate on learning a new language and a new paradigm, while you can still use your existing knowledge of the .NET ecosystem.
Although some of us can’t use F# in our jobs on a daily basis as much as we would like to, using a more functional approach can give us a different perspective on how to solve problems and therefore can make us better programmers.
If you want to start thinking functionally and you are a .NET developer or you are familiarized with the .NET ecosystem, F# is the right language for you. It runs everywhere, it’s part of the .NET Core SDK and has OO capabilities if you need them. So, why not?
- F# for fun and profit, a website to introduce yourself to F#
- The SAFE stack
- Giraffe, ASP.NET Core web framework
- Saturn, F# MVC Framework
- Microsoft’s F# guide
Programming F#, by Chris Smith, 9780596153649
Expert F# 4.0, by Don Syme, Adam Granicz and Antonio Cisternino, 9781484207420
Team Lead at Cognizant Softvision Ukraine
Dev Team Lead at Cognizant Softvision Romania
Tech Lead at Cognizant Softvision Buenos Aires