What About F#?
This week, we’re going to talk about the brief experience we had with F# in the last days of 2017.
A few days before the holidays, I was tasked to write a short data cruncher program, and it was decided (with a fully disclosed excitation on my part) that this program would be written in F#. The project consisted of reading data from a CSV file and an SQL database, matching the data and producing an aggregate output into another CSV file.
At BesLogic, we’re already quite used to functional programming in our main projects, as we integrate parts of the concepts in our daily programming, but we did not yet have the chance to write some code in a functional language.
Since we are very familiar with the .NET ecosystem, F# was a natural choice as a functional programming language: indeed, it is part of the .NET family of languages, so it has great support from Microsoft, and can interact natively with the .NET framework (we chose .NET Core 2 for the project).
We’ll probably have more articles on F# in the future, but since it was our first program written in the language, this article will be more of an overview of our first impressions.
What we liked
Functional composition is a way to create a single, new function, from smaller, separate functions.
It works differently from creating a new function as one would in C#, by adding a new method. Instead, you think more in terms of inputs and outputs than in terms of variables and intermediary steps. Since there’s also a special operator to compose functions, it’s also incredibly easy to assemble small parts into a bigger process.
Because I want to show that functional languages are useful outside of the fields of learning and science, I prepared an example that could be the base for a DateTime utility module. We will deal with parsing, Utc and Local conversions as well as formatting.
You can see in the example that I do not have to type in the parameters when composing functions. That’s because the composition creates a new function, and all I need is binding it to a symbol, so I can reference it later in the code.
The Pipe Operator
The pipe operator allows us to write code that is more similar to the typical dot-chaining as one would do with methods and extensions in C#, for instance, which means it helps make the code look more similar to more familiar languages. It can help reduce the learning curve. It’s also a nice way to avoid having a lot of wrapped parentheses everywhere, in a language that does not use parentheses for normal function calls.
I did not show it in this example, but there is also a reversed pipe operator. It works the same way as the pipe operator, except it will pass the right value to the left function, instead of passing the left value to the right function. It’s sometimes useful when you try to preserve readability. I used it in the first example, to make the Utc and Local constructors appear in the right order, even though I needed to wrap the value of the method call first. Parenthesis would have worked just as fine, but I wanted a bit of syntactic diversity in my examples.
It’s nice that C# 7 started to bring pattern matching features, but there’s still no match (get it?) with what is possible in the F# world. You saw me use it in the previous examples: we would use it to match on the parse result of DateTime.TryParse, where we could ignore the parsed value in the false case using the _ symbol. We also used it to distinguish between the Utc and Local cases, collecting the inner value of the union case (and the same goes for the Option type). The fact that you can actually store data with union types is really helpful, when compared with union types in TypeScript, for example.
We also use the pattern matching to distinguish between “null” and “non-null” values (the Option type you saw in the first example above). You can read more about ways to use nulls to your advantage in one of our previous articles.
Ask anyone: I’m passionate about type inference. Anything that can help me reduce boilerplate code in my daily work, I’ll take it, and telling the compiler what is the type of the element I’m already assigning to my variable fits right into that description. But I also like my static type safety. I’ll always prefer compile-time issues to runtime issues!
Out of the 19 (!) values I declared in the composition example (some of which were functions, some of which were not), I only had to annotate 5 functions, when I used a DateTime struct. F# has a harder time inferring the type of objects than it has inferring the type of records, unions and functions. This is not a bad thing: it’s still a great amount less type typing, and it encourages a functional style by rewarding you with less noise and simpler code.
It’s important to note that, just because I didn’t write most of the type annotations doesn’t mean that my code is not type checked. As I said, F# is a statically typed language. It’s just that the compiler was able to figure out which types I meant to use by itself, and make sure I used everything only as I am allowed to.
Now, it may seem like it is more difficult to know what you can do with the values you have in your hands, but I don’t think it matches the actual experience you have with F#. First of all, by keeping functions small and simple, you can focus on the code you are writing, and it’s easier to have the types of what you’re typing in mind. If you make a mistake, the compiler will tell you anyway! If you made a mistake, you just need to go back and fix the compiler error. If you are using an editor such as Visual Studio Code, you can also download the Ionide plugin, which adds type annotations automatically above your functions and values in the global scope, adds IntelliSense to your editor and will show you the compiler errors directly in the editor. If, at any time, you are unsure of the inferred type of something, you can just hover over it, and you will see a very readable type annotation appear in a tooltip.
What surprised us
Well, it’s mostly one thing: the way F# compilation works.
This means that when you create new files, you have to go and edit the project file, in order to place the file at the right place in the list. Now, this can be fine for small to medium projects and, granted, you don’t need as many files as you do in the C# world, but still, that’s a lot of overhead we did not foresee.
Maybe the design changes it pushes are for the best, and we would see the benefits in the long run! Maybe the problem can be mitigated by creating multiple projects, instead of having a bigger project for your whole app!
In any case, this was a surprised to us, and we hope that the people working on F# will eventually figure out a way to make the projects work no matter the order the files are declared in.
And this does not stop us from loving F# all the way!
You can find more info about F# on the language’s webiste: https://fsharp.org/.
On that note, that’s all we have for this week! There are a lot of things that I didn’t mention in this article, like the type providers, because they are broad topics. The type providers made working with CSV and SQL so easy! I will probably write more articles about these in the future, so, stay tuned!
See you next time!