Get Started with F# as a C# developer
One of our previous posts, Why You Should Use F#, listed a few reasons why F# is worth trying out today. In this post, we’ll cover some of the basics you need to know to be successful. This post is intended for people who are coming from a C#, Java, or other object-oriented background. The concepts covered here should seem very familiar to existing F# programmers.
This post won’t make any attempt to show you how to "translate" C# code into F#. This is because C# and F# represent different programming paradigms, which makes each suited to its own purpose. We think you’ll find that it expands your mind and helps you be a better programmer to learn functional programming concepts. That value comes to you the fastest if you don’t think about trying to translate from one paradigm to another.
Now is the time to be curious, inquisitive, and ready to learn brand-new things. Let’s get going!
Immediate differences
Before we dive into concepts, let’s look at a small snippet of F# code and see a few areas where F# differs from C#. Here is some basic F# code with two functions and a printed result:
Note that there are no type definitions, semicolons, or braces. The only parentheses are used to call sumOfSquares
with 5
as input, printing its result. The pipeline operator (|>
) is used much like a unix-style pipe. square
is a function passed directly as a parameter to the List.map
function (this is known as first-class functions).
Although there are many more differences we could talk about, there are deeper things going on here, and they are key to understanding F#.
Mapping core C# concepts to core F# concepts
The following table provides a basic mapping of some of the core concepts from C# to F#. It’s intentionally short and non-exhaustive so that it’s easy to remember as you begin to learn F#.
C# and Object-Oriented Programming | F# and Functional Programming |
---|---|
Variables | Immutable values |
Statements | Expressions |
Objects with Methods | Types and functions |
Here’s a quick primer on some this terminology:
- Variables are values which can change in-place, or vary. It’s in the name!
- Immutable values are values which cannot change after they’re assigned.
- Statements are units of work, performed imperatively, by a running program.
- Expressions are units of code which evaluate to a value.
- Types are classifications of data in a program.
It’s worth noting that everything in the C# column is also possible in F# (and quite easy to accomplish). There are also things in the F# column which are possible in C#, though they’re more difficult to accomplish. It’s also worth noting that items in the left column are not "bad" for F#, either. Objects with methods are perfectly valid to use in F#, and can often be the best approach for F# depending on your scenario.
Immutable values instead of variables
One of the most transformative concepts in functional programming is immutability. It’s often underrated in the functional programming community. But if you’ve never used a language where immutability is the default behavior, it’s often the first and biggest hump to get over. Nearly all functional programming languages have immutability at their core.
In the previous statement, the value of 1
is bound to the name x
. x
now always refers to the value 1
for its lifetime, and cannot be modified. For example, the following code does not reassign the value of x
:
Instead, the second line is an equality comparison to see if x
is equal to x + 1
. Although there is a way to mutate x
by making it mutable
and using the <-
operator (see Mutable Variables for more), you’ll quickly find that it’s easier to think about how to solve problems without reassigning values. This allows you to play to the strengths of F#, rather than treat it as another imperative programming language.
We said that immutability was transformative, and that means that there are some very concrete differences in approaches to solving a problem. For example, for
loops and other basic imperative programming operations are not typically used in F#.
As a more concrete example, say you wish to compute the squares of an input list of numbers. Here is an approach to that in F#:
Notice that there isn’t a for
loop to be seen. At a conceptual level, this is very different from imperative code. We’re not squaring each item in the list. We are mapping the square
function over the input list to product a list of squared values. This distinction is subtle in concept, but in practice it can lead to dramatically different code. For starters, getSquares
actually produces a whole other list.
Immutability does more than just change the way you manipulate data in lists. The concept of Referential Transparency will come naturally in F#, and it is a driver in how systems are built and pieces of that system are composed. Execution characteristics of a system become more predictable, because values cannot change when you didn’t anticipate them to change.
Furthermore, when values are immutable, concurrent programming becomes simpler. Because values cannot be changed due to immutability, some of the more difficult concurrency problems you can encounter in C# are not a concern in F#. Although the use of F# does not magically solve all concurrency problems, it can make things easier.
Expressions instead of statements
As mentioned earlier, F# makes use of expressions. This is in contrast with C#, which uses statements for nearly everything. The difference between the two can initially seem subtle, but there is one thing to always keep in mind: an expression produces a value. Statements do not.
In the previous code sample, you can see a few things which are very different from imperative languages like C#:
if...then...else
is an expression, not a statement.- Each branch of the
if
expression produces a value, which in this case is the return value of thegetMessage
function. - Each invocation of
getMessage
is an expression which takes a string and produces a string.
Although this is very different from C#, you’ll most likely find that it feels natural when writing code in F#.
Diving a bit deeper, F# actually uses expressions to model statements. These return the unit
type. unit
is roughly analogous to void
in C#:
In the previous sample for
expression, everything is of type unit
. Unit expressions are expressions which return no value.
F# arrays, lists, and sequences
The previous code samples have used F# arrays and lists. This section explains them a bit more.
F# comes with a few collection types, and the most commonly used ones are arrays, lists, and sequences.
- F# arrays are .NET arrays. They are mutable, which means that their values can be changed in-place. They are evaluated eagerly.
- F# lists are immutable singly-linked lists. They can be used to form list patterns with F# pattern matching. They are evaluated eagerly.
- F# sequences are immutable
IEnumerable<T>
s under the covers. They are evaluated lazily.
F# arrays, lists, and sequences also have array, list, and sequence expression syntax. This is very convenient for different scenarios where you can generate one of these collections programmatically.
Mapping common LINQ methods to F# functions
If you are familiar with LINQ methods, the following table should help you understand analogous functions in F#.
LINQ | F# function |
---|---|
Where |
filter |
Select |
map |
GroupBy |
groupBy |
SelectMany |
collect |
Aggregate |
fold or reduce |
Sum |
sum |
You’ll also notice that the same set of functions exist for the Seq
module, List
module, and Array
module. The Seq
module functions can be used on F# sequences, lists, or arrays. The array and list functions can only be used on F# arrays and F# lists, respectively. Additionally, F# sequences are lazy, whereas F# lists and arrays use eager evaluation. Using Seq
functions on an F# list or F# array will incur lazy evaluation, and the type will then be an F# sequence.
Although the previous paragraph may be a lot to unpack, it should feel intuitive as you write more F#.
Functional pipelines
You may have noticed the |>
used in previous code samples. This operator is very similar to unix pipes: it takes something on the left-hand side, and makes that the input to something on the right. This operator (called "pipe", or "pipeline") is used to form a functional pipeline. Here’s an example:
Walking through this code sample, items
is passed as input to the Seq.filer
function. The output of Seq.filter
, a sequence, is then passed as input to the Seq.map
function. The output of Seq.map
is the output of getOddSquares
.
Use of the pipeline operator is so much fun to use that it’s rare to see F# code which doesn’t make use of it. It’s usually near the top of everyone’s favorite F# feature list!
F# Types
Because F# is a .NET language, it shares the same primitive types that C# does: string
, int
, etc. It also has .NET objects, and supports the four main pillars of object-oriented programming. It also has tuples. F# also has two primary types not found in C#: Records and Discriminated Unions.
A Record is a named, ordered grouping of values which have equality baked in. And by equality, we mean in the most literal sense. There is no need to distinguish between reference equality or some custom definition of value equality between two objects. Records are values, and values have equality. They are Product Types for the category theorists out there. They have a number of uses, but one of the most obvious ones is a replacement for POCOs or POJOs.
The other foundational F# type is a Discriminated Union, or DU. DUs are a type which could be one of a number of named cases. These are Sum Types for the category theorists out there. They can also be recursively-defined, which dramatically simplifies hierarchical data.
Ta-da! Armed with the power of Discriminated Unions and F#, you can pass any programming interview which requires you to flip a binary search tree.
You may have noticed a bit of funky syntax in the Node
case of the tree definition. This is actually a type signature for a tuple. That means that a BST, as we’ve defined it, can either be empty, or a tuple of (value, left subtree, right subtree)
. Read more about Signatures to learn more.
Putting it all together: F# Syntax in 60 seconds
The following snippet of code is presented with permission from Scott Wlaschin, an F# community hero who wrote the following great overview of F# syntax. You should be able to read through in about a minute. It has been edited slightly.
Additionally, there is the Tour of F# document in our official documentation for .NET and its languages.
What you can do next
This post covered a lot of things, but it only began to scratch the surface of F#. We hope that after reading this post, you’ll be able to dive further into F# and functional programming. Here are a few things we recommend building as a way to learn F# even further:
- Use F# to build beautiful fractal trees.
- Use F# to explore and analyze data on The Simpsons inside Azure Notebooks.
- Use F# and Suave to build a web app.
- Use F# to build a serverless app or component with Azure Functions.
There are many, many more things you can use F# for, so the previous list is by no means exhaustive. F# is used from things as simple as a build script to forming the backend of a a billion-dollar eCommerce site. There is no shortage of projects you can use F# for.
Additional resources
It’s worth noting that there is also a wealth of information about learning F#, including from a C# or Java background. The following links will be helpful as you dive deeper into learning F#:
There are also multiple documented ways to get started with F#.
Finally, the F# community is incredibly welcoming for beginners. There is a very active slack run by the F# Software Foundation, with rooms for beginners, which you can access by joining for free. We highly encourage you to do so!