I have wanted to dive into functional programming for a long time now. In my first job as a professional developer, I developed with C# and .NET, and I’m still a fan of C# and .NET. Especially after .NET officially set its sights on the Linux ecosystem in 2016. I also have a bias towards statically typed languages with type inference. With this in mind, it was natural for me to choose F# as the language for my first steps into functional programming.
To learn F# I rely heavily on the awesome blog of Scott Wlashin. I especially try to follow his recommended dos and don´ts.
To make it easier for me to code something in F#, I decided to set up a small project where I download some stats from my yoga app Downdog and plot them.
In this project, I didn’t want to deal with handling dependencies or structuring a large project. Also, the plots didn’t necessarily need to be meaningful. The code for the project can be found here.
In the next few sections, I will go over the setup and structure of the project and will cover the highlights and annoyances of my first steps with F#.
Setup and F# Interactive
First off all the setup under linux with Visual Studio Code and Ionide is a breeze. Nevertheless the smoothness of the installation of .NET depends on your linux distribution.
One of the don´ts Scott Wlashin recommends is to not use a debugger. Instead we should rely on the compiler to “debug” our F# code. For this any .NET SDK ships with F# Interactive as a REPL (Read, Evaluate, Print Loop) for the F# language. Within Visual Studio Code you then can add a F# script file and send the content of the script via Alt+Enter to the F# Interactive. The common development workflow then is to develop your code with a script file and F# Interactive and afterwards integrate it in your existing project. To allow scripting from existing projects Ionide comes with a feature to generate references for F# Interactive so that one can get access the code of your existing project. This is also how I developed my project. First start with a script. Write some F# code. Let the compiler check if everything is correct. Test it with F# Interactive. Eventually move the code parts of the script into my console application project.
Overall I can say that I like the used development workflow. It feels intuitive, but is completely different from my workflow at work. It also helps to develop code incrementally in (very) small steps.
Project description
The basic idea of the project is to download my yoga stats from the Downdog app as json, parse them with the F# json type provider, convert them into a easily plottable dataset and finally be able to plot two types of diagrams from the data. The first chart should show the frequency of the lessons I took, and the second chart should show the music charts of all my lessons.
Simple project workflow
Results
Plot 1: Downdog lessons taken (x = date [d] , y = duration [min])
Plot 2: Downdog top ten music charts
Dos and Don’ts
As I have said already in the beginning I wanted to take into account Scott Wlashins dos and don´ts for learning F#. In the next step we will have a closer look at all of them except for the don´t use the debugger advise, which we addressed above.
Don’t use the mutable keyword
This one was easy to achieve as I use Kotlin at work and Kotlin also dinstiguishes between mutable and immutable values. I had to use mutable when I was working with the AWS SDK for .NET where I needed to set a value after the creation of an mutable object.
let secretName = "secret";
let mutable secretValueRequest = GetSecretValueRequest()
secretValueRequest.SecretId <- secretName
Don’t use for loops or if-then-else
I didn`t! As recommeded I used pattern matching instead. Also here pattern matching is avaible in Kotlin.
let obtainSpotifyUri (downDogSpotifyUri: string option) =
match downDogSpotifyUri with
| None -> None
| Some s -> Some (new UriBuilder(s)).Uri
Don’t use “dot notation”
I tried to avoid dot notation, but had to use it in some parts, especially where I had to deal with external C# libraries that accessed the properties of the provided types.
Don’t create classes
I have not created a single class myself. 😄
Do create lots of “little types
I tried to use (little) types for each part of my “domain”.
type SongId = string
type Artist = string
type Title = string
type SpotifyUrl = Uri option
type Song =
{ id: SongId
artist: Artist
title: Title
spotifyUrl: SpotifyUrl }
Do understand the list and seq types
Since list and sequences are also available in Kotlin, this was easy to achieve.
let obtainYogaMusicCharts (historyItems: array<DownDogHistory.Item>) =
historyItems
|> Array.collect (fun element -> element.Songs)
|> Array.map obtainSong
|> countById
|> Seq.sortByDescending (fun (_, b) -> b)
|> Seq.toList
It was also nice to add a generic extension for sequences to add a count by id functionality.
let countById collection : seq<_> = collection |> Seq.countBy id
Do use pipe (|>) and composition (»)
As we saw in “Understanding the List and Seq Types”, I have used the pipe operator quite extensively. I also managed to use the composition operator once.
let obtainLessonDurationFromSelectors =
obtainSelectorValue "LENGTH"
>> obtainLessonDuration
Do understand how partial application works, and try to become comfortable with point-free (tacit) style
The above example also illustrates the use of a partial application where LENGTH
is partially applied to the obtainSelectorValue
function. But I have not yet managed to familiarize myself with the point-free (implicit) style.
Helpful libraries/services
During the development process, I found and used some nice libraries which helped me with various aspects of my application that I didn’t want to code myself.
Secret management
First of all, I don’t like to store secrets in my repositories. Not even encrypted ones. I’ve still managed to check in some secrets into my repos, even with encrypted secrets. You could prevent this with tools like talisman, but I prefer not to store secrets at all. Although this is probably not possible for all projects, I try to use secret management services. Before secrethub was bought out by 1password, they offered a free plan for developers that is now no longer available. Still, integration was relatively easy:
open SecretHub
let resolveSecret =
let secretHubClient = new Client()
secretHubClient.Resolve("secrethub://path/to/secret")
Due to the Secrethub acquisition, I moved to AWS where the API is more general and therefore more complex to integrate:
open Amazon
open Amazon.SecretsManager
open Amazon.SecretsManager.Model
open System.Threading
let resolveSecret =
let awsSecretManagerClient = new AmazonSecretsManagerClient(RegionEndpoint.EUCentral1)
let secretName = "/path/to/secret";
let mutable secretValueRequest = GetSecretValueRequest()
secretValueRequest.SecretId <- secretName
let asyncSecrets = async {
let! result = awsSecretManagerClient.GetSecretValueAsync(secretValueRequest, CancellationToken(false))
|> Async.AwaitTask
return result
}
let secretResolved = Async.RunSynchronously(asyncSecrets)
secretResolved.SecretString
Argument parsing
Since the application was to be a console application, I wanted to be able to parse command line arguments. For this I found Argu very helpful.
Date and time
No .NET project that works with date and time should do without nodatime. For those who need to work with date and time, I recommend this talk by John Skeet.
Plotting
Last but not least, I used XPlot, which is powered by Plotly and Google Charts, to create the charts for my Downdog statistics. In the future, I will upgrade to Plotly.NET as recommended by XPlot.
Conclusion
I finally took my first steps in functional programming with F#. Overall, it was a very enjoyable experience, even though there were some obstacles to overcome.
First of all, the documentation for F# doesn’t seem to be as extensive as for C#, especially when it comes to using .NET libraries that are not explicitly written for F#. As a result, I had a bit more trouble integrating these libraries with, for example, the AWS SDK. I also ran into a mysterious type initialization exception that disappeared as mysteriously as it appeared. I never did figure out what happened. In addition, I had a very naive idea of how the F# json type provider worked. I don’t want to go into details here. :flushed_face:
But let’s stop complaining at a high level. The overall experience was very nice. Starting with the ease of setup on Linux and the smooth integration with VS Code. Big kudos to Ionide. Also, the dos and don´ts were easy to follow, especially after working with Kotlin at work. I was also really convinced by the alternative development workflow with script files and the REPL. Especially when the type system and compiler provide an extra layer of confidence and feedback, so you can get by with a little less testing. Finally, I would like to mention another small detail that I really like about F#: files and code must be in dependency order.
TL;DR
First successful attempt to write F# and also wrote my first blog article about it. Great Success!