One of the things I don’t like about C# is how noisy the syntax looks. Here is some code from a recent library of mine binding the thecatapi.
The code is littered with lots of types, ?, <> everywhere and if it wasn’t for the relatively simple conditionals, would have about 16 more curly braces. So when I learned that one of the benefits of writing in F#, the functional younger sibling of C#, is conciseness, I was intrigued.
I came up with some challenges to help me get used to the language. And explore different areas of it. Here are some of the ones I did.
open System
open System.IO
open Microsoft.VisualBasic.FileIO
// Problem 1 print Hello World. Then create a variable with your name
// and print Hello <your name> with string interpolation
printfn "\n===Problem 1==="
let name = "Diego"
printfn "Hello World!"
printfn $"Hello {name}!"
Not bad, no need to do any public static main ceremony, and a simple call to printfn
instead of Console.Writeln
. String interpolation uses the same syntax as it does in C# so I didn’t even have to learn anything new. The comment syntax is also the same for single line comments with //
. Multi line comments are a little different using (**),
but I think this is inspired by Ocaml, a functional language that is seeing a bit of a resurgence lately.
(*
a multi line
comment
*)
The next mini challenge was to build some functions for a simple calculator.
printfn "\n===Problem 2==="
let MyAdd (num1, num2) = num1 + num2
let MySub (num1 : int) (num2: int) = num1 - num2
let MyMul (num1, num2) = num1 * num2
let MyDiv (num1, num2) =
// Guard against Divide by Zeros
match num2 with
| 0 -> None
| _ -> Some(num1 / num2)
let MyTypedAdd (num1: double, num2: double) = num1 + num2
printfn ($"MyAdd 1 + 2 = {MyAdd(1, 2)}")
printfn ($"MySub 1 - 2 = {MySub 1 2}")
printfn ($"MyMul 1 * 2 = {MyMul(1, 2)}")
printfn ($"MyDiv 1 / 2 = {MyAdd(1, 2)}")
printfn ($"MyDiv 1 / 0 = {MyAdd(1, 0)}")
// Example usage
let number1 = 10
let number2 = 0
match myDiv (number1, number2) with
| Some result -> printfn "Result: %d" result
| None -> printfn $"Cannot divide by {number1} by {number2}"
printfn ($"MyTypedAdd 1 + 2 = {MyTypedAdd(1, 2)}")
There is a lot going on here but let’s break it down…
let MyAdd (num1, num2) = num1 + num2
You bind functions and variables by prefixing them with let
. In the above example we are creating a function called MyAdd
which takes a tuple containing two values. This is an important distinction from just passing two arguments. In Python you would define the function as
def MyAdd(num1,num2):
return num1+num2
When you call MyAdd
in Python you are passing the function two seperate objects. In F# when you call MyAdd
you are passing a tuple with two values. You may ask “If I’m passing in a tuple, why can I use it as if I were passing in two separate numbers”. The answer is pattern matching. We will get into pattern matching a little bit later but as a sneak peak you can imagine that the function looks like this
let MyAdd numTuple =
match numTuple with
| (num1, num2) -> num1 + num2
With pattern matching you can match a tuple to its individual components, and then do something with it. There is one other way to define how arguments are passed into a function, and that is with Currying.
// Types are annotated with :
let MySub (num1 : int) (num2: int) = num1 - num2
For now you can think of Currying as working in the same way that you use functions in other languages by passing in separate values that correspond to individual arguments. We won’t touch on Currying here, but the trick with Currying is that a function doesn’t have to take all of its arguments at the same time. You can save the partially initialized function to a variable to use later.
F# has strong type inference so a lot of things don’t need to be explicitly typed. If you are writing simple functions, as often happen in a functional program, they can usually be left out.
Moving on to the division part of our calculator, we have to guard against divide by zero cases. We can do this simply with pattern matching.
let myDiv (num1, num2) =
// Guard against Divide by Zeros
match num2 with
| 0 -> None
| _ -> Some(num1 / num2)
In this case if the value is 0 we return None
, but if the value is some other number we return the Some
value of num1 / num2
. None
and Some()
indicate that the value returned by division is an option type, in this case Option<int>.
None
is very different from null and with the additional information an optional type provides, can help a compiler scrutinize and warn users when these values are being used incorrectly. It is also important to understand that Some(Value) is not the same as Value. So later down the line, if you want to use Some(Val) you will have to extract it using pattern matching
let maybeNumber = Some(5)
match maybeNumber with
| Some(number) -> printfn "The number is %d" number
| None -> printfn "There was no number"
For a more in depth description of option types and nulls, I would encourage you to visit the F# for Fun and Profit website.
Pattern Matching
This is now the second time that we’ve seen pattern matching so let’s take a closer look at it. In our div example we are using pattern matching two safely divide two numbers. A pattern match expression is like a switch or case statement on steroids. The structure of a match statement looks like this
match [something] with
| pattern1 -> expression1
| pattern2 -> expression2
| pattern... -> expression...
It allows us to match variables to individual values like in this example
type TrafficLight =
| Red
| Yellow
| Green
let action light =
match light with
| Red -> "Stop"
| Yellow -> "Caution"
| Green -> "Go"
But also allows us to destructure tuples like in this example
let compareNumbers (a, b) =
match (a, b) with
| (x, y) when x > y -> printfn "First is greater"
| (x, y) when x < y -> printfn "Second is greater"
| _ -> printfn "Both are equal"
You can match lists an arrays as well, and can use _ as a shorthand for the default case
let describeArray arr =
match arr with
| [| a; b; c |] -> printfn "Three elements: %d, %d, %d" a b c
| _ -> printfn "Other"
Finally dotnet languages contain a project file which contains flags for how to build a project. They end in .csproj for C# and .fsproj for F#. With a bit of fsproj shenanigary, we can add a line to make sure we match all cases in a match satement or it will give a compile time error.
<PropertyGroup>
<OtherFlags>--warnaserror:FS0025</OtherFlags>
</PropertyGroup>
This means that in an example like the one below
type TrafficLight =
| Red
| Yellow
| Green
let changeLight (tLight) =
match tLight with
| Red -> "Stop"
| Yellow -> "Caution"
printfn "%s" (changeLight TrafficLight.Green)
We can get an error at compile time instead of a runtime error.
0>Program.fs(7,11): Error FS0025 : Incomplete pattern matches on this expression. For example, the value 'Green' may indicate a case not covered by the pattern(s).
0>------- Finished building project: TrafficLight. Succeeded: False. Errors: 1. Warnings: 0
If you we were writing mission critical code like a traffic light, you’d want to make sure all the cases where handled correctly, before the program was deployed :)
Let’s pause for a moment. So far we’ve covered
How to print hello world and interpolate strings
How to write a function whose input is a tuple
How to write a curried function
Pattern matching
The option type
At least three out of the five topics might be new to you, so take a moment to reflect on them if you need to before moving on.
Calling functions on collections
In F#, values in Arrays and Lists are separated with ‘;’. Arrays are denoted by [||]
, while lists just use [ ]
like in many other languages.
One thing about functional programming languages is that they usually have special functions to work with Lists and Arrays, and F# is no different.
let myArray: int[] = [| 1; 2; 3; 4; 5; 6; 7; 8; 9; 10 |]
let myReduceArray = [|50; 20; 15; 15|]
let myList: int list = [ 10; 11; 12; 13; 14; 15 ]
let myArraySum = Array.fold (fun acc num -> acc + num) 0 myArray
let evenNumbers = Array.filter (fun n -> n % 2 = 0) myArray
let subtractArray arr = arr |> Array.reduce (fun acc x -> acc - x)
let reduceResult = subtractArray myReduceArray
let myListAvg = List.map float myList |> List.average
printfn($"Sum of myArray {myArraySum}")
printfn "Even Numbers of myArray %A" evenNumbers
printfn($"The result of subtracting all numbers is {reduceResult}")
printfn($"Average of myList {myListAvg}")
printfn($"Max of myList {List.max myList}")
You will get very good at using functions like fold, filter and reduce. Let’s break down some examples…
let myArraySum = Array.fold (fun acc num -> acc + num) 0 myArray
Fold
collapses a collection (array, list, etc) of values to a single number by applying a function to an accumulator and each element in the collection. In this case we are creating an anonymous function that fold will apply to our array. In the function, acc
is the accumulator that starts at 0
(the initial value), and num
is the current element being iterated on. The function acc + num
is applied to each element in the list, accumulating the result. You can think of this function like this for loop
in Python
count = 0
myList = [1,2,3,4,5,6,7,8,9,10]
for num in myList:
count += num
print(count)
Filter
has a similar set up to fold in that is takes an anonymous function and applies it to the collection
let evenNumbers = Array.filter (fun n -> n % 2 = 0) myArray
It returns a collection that contains only values from the original collection that returned true in the anonymous function. In this case we are dividing n by 2 and checking if the remainder is 0. If it is, then it’s an even number. If it isn’t, its odd and we “filter” it out of the array.
Next we have Reduce
. Reduce works exactly like fold, except you don’t have to supply an accumulator in the beginning. Since accumulating the values after applying a function starting at 0 is so common, reduce is a convenient function to use.
let subtractArray arr = arr |> Array.reduce (fun acc x -> acc - x)
Since reduce is a simple fold we will introduce one more thing. |>
is called the forward pipe operator. It passes the result on the left side of the function to the right side. This is also called the threading operator in other languages. This makes it very easy to chain functions together, and is very intuitive once you get the hang of it. As an example, here is a function in one of my code bases that uses three |> in row to get the longest strings a nested array of strings.
let getMaxColumnWidths (maxRows: string[][]) =
maxRows
|> Array.transpose
|> Array.map (Array.maxBy String.length)
|> Array.map String.length
Last but not least we have map.
let myListAvg = List.map float myList |> List.average
Map
applies a function to a list, and then returns a new list with the changed values. In this case we are changing all of our values in the list to floats. Our list will now look like this
//List.map float [ 10; 11; 12; 13; 14; 15 ]
[10.0; 11.0; 12.0; 13.0; 14.0; 15.0;]
We want to do this, because the average is not going to be a whole number. When we pipe it to the List.average
function, we get our answer of 12.5.
But these are pretty simple examples to use Fold, Map, Filter and Reduce for. Unsurprisingly since this functionality is so common in F#, F# has a lot of these functions built in. Here are the most common ones
List.sum / Array.sum
List.max / Array.max
List.min / Array.min
List.average / Array.average
List.length / Array.length
List.rev / Array.rev
List.isEmpty / Array.isEmpty
List.head
List.tail
etc.
C# interoperability
Languages that are tied to virtual machines like the JVM, Racket, and Dotnet, have great interoperatbility between other languages on that VM. In this respect F# is no different. F# is able to interface with libraries written in C# and Visual Basic without any problems. As an example here is code that uses the Gtksharp library to create the counter example in the seven guis challenge.
module Counter
open Gtk
open System
let mutable counter = 0
let mutable counterLabel : Label = null
let onButtonClicked (sender:obj) (args:EventArgs) =
counter <- counter + 1
counterLabel.Text <- counter.ToString()
let runCounter () =
Application.Init()
let window = new Window("Counter")
window.WindowPosition <- WindowPosition.Center
window.BorderWidth <- uint 10
window.SetDefaultSize(200, 100)
window.DeleteEvent.Add(fun _ -> Application.Quit() |> ignore)
let hbox = new HBox(false, 5)
counterLabel <- new Label("0")
let button = new Button("Count")
// Adjust the event handler to match the expected signature
button.Clicked.Add(fun _ -> onButtonClicked button EventArgs.Empty)
hbox.PackStart(counterLabel, false, false, 0u)
hbox.PackStart(button, true, true, 0u)
window.Add(hbox)
window.ShowAll()
Application.Run()
While F# is functional and immutable by default, it still supports multiple paradigms. This means that you can dip into a mutable style of programming when needed. This also means that if you are coming from a language that uses classes and mutability extensively, that you can still reach for those tools while you are learning more functional patterns.
In the above example my counter and the label widget are annotated with the mutable
keyword allowing me to change the values stored in them. Changing mutable variables requires you use the ← operator. By making mutability explicit F# makes programs more clear. But you don’t have to restrict yourself to C#, Visual Basic also has some useful libraries. Here is a function I wrote that uses the Microsoft.VisualBasic.FileIO library to parse csv files
let readCsvFile (filePath: string) =
use parser = new TextFieldParser(filePath)
parser.TextFieldType <- FieldType.Delimited
parser.SetDelimiters([| "," |])
let rec parseLines acc =
if parser.EndOfData then
acc
else
let fields = parser.ReadFields()
// Process fields here
parseLines (fields :: acc)
parseLines [] |> List.rev
This is also a great demonstration of a powerful functional paradigm… Recursion. In F# recursive functions are annotated with the rec keyword. This allows functions to call themselves within their own function body.
Because the Dotnet virtual machine also supports Tail call optimization (TCO), recursion is a good choice in F# programs. In languages without TCO, recursive calls can be a problem. Each call adds a new frame to the call stack, which holds information like local variables and the return address. If a function calls itself many times (deep recursion), this stack can grow large, leading to a stack overflow. When the last action of a function is a call to another function (including itself), the compiler can optimize the code to avoid adding a new stack frame. Instead, it reuses the current frame for the subsequent call. This optimization means:
Reduced Stack Usage: The call stack doesn't grow with each recursive call, preventing a stack overflow.
Improved Performance: Reusing stack frames is more efficient, leading to faster execution.
TCO is actually a unique difference between the C# and F# compiler. The F# compiler looks to optimize this while the C# one doesn’t. There are a lot of reasons why C# chose a different route, but for a functional first language like F# it makes a lot of sense.
All in all, the interoperability between F# and other dotnet languages is great, and means that people using F# have no shortage of top notch libraries to choose from.
Tooling
Let’s look at tooling next. F# have some great tooling by nature of it being on the Dotnet platform. It is well supported in Visual Studio, Visual Studio Code, and Dotnet Rider. It is old enough and loved enough by many that someone has probably written an F# mode for your favorite text editor. It’s even got a REPL allowing you paste in F# snippets to test them out. Many IDES support this feature too, allowing you to highlight an evaluate blocks of F# right in the editor.
Like we saw in our earlier example, the fsproj file allows you to tweak you F# projects to make errors more strict, and to control how the project is built. But besides what is provided by the language itself, there are also plenty of other projects out there to help you do a wide variety of things
FAKE: (F# Make) Cross platform build tool.
FSharpLint: A linter for F#, helping to enforce coding standards and identify potential issues.
Fantomas: An F# code formatter, ensuring consistent code style across your project.
Fable: F# to JavaScript, TypeScript, Python, Rust and Dart Compiler
Fabulous: Declarative UI framework for cross-platform mobile & desktop apps, using MVU and F# functional programming
Nu: F# game engine
Strict ordering
One thing that didn’t become apparent to me until I started writing larger F# projects is its strict ordering. The compiler reads left to right and top to bottom in a file. That means that you must declare functions and variables before they are used at all points in the program. This actual reminds me a lot of another programming language we covered, Pascal.
This strictness extends to the order of the files in the project themselves. For example. Here is a project that contains the files HereDoggy.fs and Program.fs. HereDoggy.fs contains all of the functions I’ve defined for this library, and they are being used in Program.fs. Lets see what happens when I try to compile and run this project
I get this error…
0>Program.fs(1,1): Error FS0222 : Files in libraries or multiple-file applications must begin with a namespace or module declaration, e.g. 'namespace SomeNamespace.SubNamespace' or 'module SomeNamespace.SomeModule'. Only the last source file of an application may omit such a declaration.
I’ve highlighted in bold the two parts of this message that are the most important. Lets start with the first part adding the module at the top of the HereDoggy file
If we run the program again, we get the same error. What gives!? Well how does F# actually figure out what the last file in project is? It turns out this is defined in our .fsproj file. Looking inside we see this…
<Project Sdk="Microsoft.NET.Sdk"> <PropertyGroup> <OutputType>Exe</OutputType> <TargetFramework>net8.0</TargetFramework> </PropertyGroup> <ItemGroup> <Compile Include="Program.fs"/> <Compile Include="HereDoggy.fs"/> </ItemGroup> <ItemGroup> <PackageReference Include="FSharp.Data" Version="6.3.0"/> <PackageReference Include="HttpUtils" Version="1.0.0"/> </ItemGroup> </Project>
I’ve highlighted the issue. Our HereDoggy.fs is “under” the Program.fs line. This means that it is the last file in the project. If you swap the order of HereDoggy.fs and Program.fs the program compiles. I can hear you already. “This is the dumbest thing I’ve ever seen a programming language do”. I admit this frustrated the heck out of me the first time I encountered it, because I wasn’t exactly sure what I needed to do to fix it. I don’t think any programmer coming from a different language would expect the order of the files to be the reason their program didn’t compile. But believe it or not there is a good reason for this. This means that files can only reference code that is higher up within the same file, or higher up within the ordering of files. The advantage is this is that it removes the issue with cyclic dependencies
Cyclic dependencies in programming occur when two or more components depend on each other directly or indirectly, creating a cycle. This situation can happen at various levels of software design, from low level code to high level module and package structures.
There are many different ways that cyclical dependencies can occur, here are some.
Function level: When two or more functions call each other directly or indirectly, creating a loop.
Class level: When a class
A
depends on classB
for some functionality, and classB
also depends on classA
, either directly or through other classes.Component/module level: When higher-level components or modules (which could consist of multiple classes or functions) are interdependent in such a way that they form a loop.
Package or library level: When two or more libraries or packages are interdependent, creating a loop that can make it difficult to perform actions like compilation, testing, or deployment independently.
This is a huge pain in the butt, and can spell doom for large project, so much so that there are whole suites of static analysis tools that help users resolve this issue. So while it is annoying to have to do, you quickly develop the muscle memory to organize the files as you are building your project. The advantage of this is you are always thinking about the structure of your project and how the code will flow. This also means that you will notice right away if code is out of place in the ordering of the files, allowing you to resolve the issue before your project turns into a big ball of mud. Finally, an unintended consequence is that this provides a new user to a project a top down structure of how the project is composed.
Wrapping it up
There are a lot of things to like about F# and I hope I’ve demonstrated a few. It’s a worthy language to look at, and one that shouldn’t scare you off if you’ve never done any functional programming. Due to its interoperability with C# and Visual Basic, it has plenty of OOP and mutable goodness to ease you in as your learning functional programming. It also benefits greatly from the entire Dotnet ecosystem, so while you might not find an F# library for everything you need, chances are you will find a Dotnet library that does exactly what you need.
While not as popular as some other languages, it has plenty of documentation to help learn. Since it is a Microsoft language, it is well supported by their ecosystem, and has resources to teach you how to do all manner of things with it. There is even an entire 12 part introductory series on the Dotnet YouTube channel for getting started, if you are a more visual learner.
All in all I’ve enjoyed my time in F# and have been programming in it quite a bit. It started to feel natural to me after only a few days, and I’m tackling quite a large project in it right now. It’s a fun language, and I hope you give it a try.
Call To Action 📣
Hi 👋 my name is Diego Crespo and I like to talk about technology, niche programming languages, and AI. I have a Twitter and a Mastodon, if you’d like to follow me on other social media platforms. If you liked the article, consider liking and subscribing. And if you haven’t why not check out another article of mine listed below! Thank you for reading and giving me a little of your valuable time. A.M.D.G
Hi! I’ve always had a soft spot for ML and its modern derivatives - I think the block commenting is inherited from the common Standard ML ancestor of both F# and OCaml. I keep meaning to pick up one of them as a contrast to all this LISP...