Why you should consider F#

Raphaël 2024-11-26
This post is sharing why I love developing in F#, and why you should consider it. It is particularly adapted to rapidly evolving environments, typically found in startups, but its solidity makes it relevant in more established companies. Read on to know why!

First, some words about the origins of this post and why F# was used. You can skip it if you’re not interested.

Asfaload develops a solution to help secure and authenticate downloads from the internet. We decided to progress step by step, and release early. The first step we took was to publish a tool that automatically checks a downloaded file’s integrity using a checksum published alongside it. We thought this would be seen as useful, but feedback we got why rather negative, focusing on the fact that it does not increase security as it uses a checksums file from the same origin.

So we decided to mirror checksums files published by Github Releases in a repo of ours. As this is an intermediate step in our journey, and being unsure about the reaction about this approach and its limitation (if a malicious release were published, we would still copy the checksums published in it), we decided that I would develop it in the language I’m most proficient with: F#. As I was once again seduced by the language’s strengths, I decided to share why. And to be totally honest, I’ll also share the downsides I encountered in this project.

Pros

Start quickly with a script

F# can be used to write scripts that you run with dotnet fsi myScript.fsx. This lets you start right away without any boiler-plate usually required when compiling a project, eg with its .fsproj file, and lets you quickly iterate. You sill can load libraries from Nuget, the Dotnet libraries repository, with a line of this kind: #r "nuget: FsHttp" You also have the support of the LSP in your scripts, no compromise to do there.

This is how I went to work in this case, and I stayed with .fsx scripts until now. Once the project grows or becomes more established, migrating to a compiled project makes sense, and should involve limited work as it’s already all fsharp code. But fsx scripts can get you a long way, some handling projects with multiple scripts totalling more than 10k lines.

Type inference

F# is statically typed, but writing explicit types is often optional. I’ve found it useful to explicitly write types of function arguments, but not (always) its return type.

Fearless refactoring

Some people criticise statically typed languages as requiring to fight the type checker for nearly no benefits. Although the type system, including the type inference, will often point me at errors I make during development, its real power is in enabling fearless refactorings. When you develop a new solution, you know refactors will be part of the process.

Refactoring with the help of the type system usually goes like this for me:

  1. Introduce the central change of the refactoring. That can for example be adding an parameter to a function
  2. Keep fixing the errors reported by the type system

This might seem dumb, but this simple approach leverages the type system and there’s no need to wonder if all required changes were done. The type system will tell.

Similarly, when adding a consequent feature to a F# project, I have the feeling of working on a puzzle, building and working on multiple separate smaller parts at the same time, until they all fit in as one picture. While you manipulate the pieces, you know you’ll get a coherent picture, but you don’t see it yet. But you trust the puzzle make to have provided pieces that all fit together. You focus on one part of the puzzle, and think about how it needs to fit in the greater picture. Similarly with F#, you can trust the type checker to provide you with the right information so that all pieces of software you modify will work well together once it compiles. It’s a great feeling when at the end all the parts beautifully fall together, giving the expected result, usually at the first try after a successful compilation.

Extensive Dotnet libraries

F# is a language on the Dotnet platform, and as such can use all Dotnet libraries. Of course, most of them are developed with C# usage in mind. Some complain that their object-orientation doesn’t fit F#‘s functional nature. If their object-orientation often have an impact on the code using them, I don’t remember having been much annoyed by it. I’m usually very happy to have my needs covered by a library, and I’m happy to adapt the code I write to interact with it. And its impact on the code is usually very local, letting me write functional code in the rest of the program.

Some gems are available

The F# ecosystem proposes some interesting gems. Those I’ve used in this case are:

  • FsHttp, a hackable http client. It uses computation expressions, a unique F# feature.
  • Fli, which lets you execute CLI commands from F#, also using computation expressions.
  • Suave, an F# library providing a light-weight http server.
  • DbFun, a library to interact with databases.

I also dicovered FsPacker and fflat, letting you package F# scripts as executables, though I didn’t use them.

Downsides

For full transparency, I’ll share the downsides I see when going with F#. To be clear, in my opinion they don’t deny the advantages outlined above.

Tooling

Although the tooling is continuously improving, it is far from the quality you can find in other ecosystems. I’m using neovim and compared to the Rust lsp, there’s a quality gap. Problems I encountered:

  • autoformatting of the code screwed up alignment and code runnability, sometimes duplicating lines at the end of the file
  • crashes of the lsp server
  • requiring a restart of the lsp server

Microsoft ecosystem

F# originated as Ocaml of dotnet and was a Microsoft research project. F# graduated to a fully supported language on Dotnet, but is rather an afterthought for the team developing the dotnet platform. Although Microsoft tries to appear as an open source actor, it still can act suspiciously (examples: 1,2, 3) and try to lock down Dotnet.

All this might make F# an uncomfortable choice.

Community management

Probably due to its corporate origins, the F# community management is bizarre. When I started with F# a couple of years ago, the place to get support was the F# Slack channels. But before joining this Slack , you had to become a member of the F# foundation! I tried to explain this is a barrier to entry which should be removed if you want to grow your community, but this didn’t lead anywhere. Sadly things don’t seem to have improved….

As alternatives which don’t require to be a member of the F# software foundation, theres the forum, which is low activity but where posts usually get answered. And there’s a F# Discord server

Unwarranted criticism

Syntax

There’s often criticism of F#‘s unfamiliar syntax for programmers. I find this a real pitty. I don’t think that a majority of capable programmers should refrain from using F# due to its syntax. I had to learn it and it’s not a big deal. It makes me think of learning complex numbers at school. The first years the complex part was notated with i. Later on, it became notated with j. Did it require some time to adapt? Yes. Was it a blocker? No way.

When being used to this:

enum Answer {
    Yes,
    No
  }

you should be able to read and write this too:

  type Answer = |Yes |No

Or when matching with this syntax:

  match answer {
  Yes  => println "Positive answer"
  No => println "Negative answer"
  }

the following should also be approachable:

  match answer with
  | Yes -> printfn "Positive answer"
  | No -> printfn "Negative answer"

Programmers need the flexibility to learn new technologies, approaches, algorithms, etc… Learning to use a different syntax should really not be a big deal. Don’t be like some kids refusing to taste something because it is unknown to them. Try it. You might not like it at first but get used to it. Or it might be something not for you. But at least you’ll have tried!