We've taken a different approach this week and asked Callum to go through his journey with F#. Callum is a personal friend of mine and just so happens to be the mentor that showed me the wonders of F#. He is a super talented engineer and has always pushed me to improve so I'm really excited to get to introduce him!
Callum's been developing in .NET and Javascript for the last 17 years. Currently a Principal Engineer for ClearBank who are the 1st new clearing bank in last 250 years. He focuses on high performance, functional practices and DDD. Outside of development, if there can be, he loves Rugby whether thats watching or playing!
If you're interested in writing an F# blog reach out on twitter we'd love to share more voices from the community
The rest of the blog is written by Callum!
Intro
I've been a JavaScript developer for the best part of 17 years, and professionally for the best part of 8 years. Although my chosen language of choice used to be C# which I've been doing for the same time. However, F# has crept into my life, and I've loved every second with it... I've coded things I never thought I would have because of the versatility of F#, like a data science driven lottery numbers predictor. I really resonate with what JavaScript devs love about their language, but also really resonate with what C# devs love about theirs. I feel like I'm in a great place to help convince JavaScript devs that F# is a great logical next step.
I've also been teaching a lot of people how to write code in both JavaScript and C# and keep my ear close to the ground on this. It always seems to be the fact that, people love developing in JavaScript because of its flexibility. But they often move towards TypeScript in the "enterprise" world because... safety...
What if I told you F# can fill both of those gaps and be used everywhere both JavaScript and C# can be used.
Who's this for?
If you love JavaScript and are a die-hard fan, feel free to peruse but you're not my target audience.
If you love looking at comparisons and alternative ways of thinking - go ahead.
If you're a JavaScript dev and are reluctant to learn a new language and want some guidance, please read, this is for you.
Why JavaScript?
JavaScript is the ubiquitous language for the web, but the arrival of NodeJs has made it a great all-rounder. From backend services to front end websites, or even desktop apps.
I think one of the biggest draws to JavaScript is its dynamism. Consider this:
var person = {};
person.name = "Callum";
In all statically typed languages, the compiler would throw a fit. name
has not been defined as a member of that person object.
JavaScript is like, nah it's cool.
Consider this as well:
function doTheThing(a, b) {
return a + b;
}
What type is a
or b
and what is the return type of doTheThing
... who knows, this will pretty much work on all types in JavaScript.
This can lead to some interesting things to happen which everyone has a good joke and laugh about.
But this leads people to be able to write JavaScript very quickly with very little fuss and start to see their code do something.
The power of the fast feedback loop
In many other talks that I've done and during consultations with software teams, I always talk about the Fast Feedback Loop.
It basically comes down to the quicker you can see your change working the happier you are. This includes feedback through CI/CD around PR deployments or Production Releases.
Along with JavaScripts ability to be easily written in anything, including notepad, the low barrier to entry and fast feedback loop makes you feel super productive right from the get-go. This is a powerful feeling and makes you feel awesome!
How can F# be great for JavaScript Developers?
Let's walk through this example written in C#:
public void IncrementEveryonesAge(List<Person> people, int ageIncrement)
{
foreach (var person in people)
{
person.Age += ageIncrement;
}
}
void
is a return type, and means nothing is returned. List<Person>
is a type container describing a bunch of things, these things are Person
objects. int ageIncrement
is specifying that we will increment the person's age by an Integer quantity (whole number between two bounds, 32-bit bounds).
The only bit of inference is var person
where C# knows that you're iterating through a list of Person
, therefor a single item is a Person
.
The initial reaction is, gee whiz, there's a lot of gaff there, modifiers, type annotations and curly braces. The equivalent in JavaScript is as follows:
function incrementEveryonesAge(people, ageIncrement) {
for (var person in people) {
person.age += ageIncrement;
}
}
On initial inspection, you'll notice there is a whole lot less to deal with, you feel like you can see the wood through the trees - it's walnut.
Another thing you may be thinking is, yeah but braces are on different lines and indents are different which helps with brevity - and yes, you're right. Both of those are pretty much the default styling you would encounter in the wild which does make somewhat of a difference!
So, what about F#:
let incrementEveryonesAge people ageIncrement =
people
|> List.map (fun p -> { p with Age = p.Age + ageIncrement })
Oh damnnnnnnnnnnnnnnnnnnnn, this is vastly different and way more succinct.
There are no visible type annotations, there are no curly braces and there is no for loop...
In F# mutations are hard to do, for a reason! So, the idiomatic way is immutability, so rather than changing each objects age property but keeping the reference the same, in F# we return a whole new object with the update value. This reduces any side effects and makes this function a pure function (can't be affected by external factors).
Now, the astute developers of JS and C# will be shouting saying that this can also be as succinct in their given languages, so lets do that:
public List<Person> IncrementEveryonesAge(List<Person> people, int ageIncrement)
=> people.Select(x => x with { Age = x.Age + ageIncrement }).ToList();
So, C# still looks terrible 🙂 . Type annotations, semi-colons, braces and the only way to make this immutable was to use records
brought to us in C# 9, otherwise... the code to support would be a lot of work, or you could stick with a horrible mutable problem.
const incrementEveryonesAge = (people, ageIncrement) =>
people.map((p) => {
p.Age += ageIncrement;
return p;
});
This is how I would write it, that's my style when it comes to small functions like these. Take note, this is the horrible mutable problem because there's no real immutability in JS again without writing all the code.
But we can agree now that the F# and JavaScript are very similar now.
let incrementEveryonesAge people ageIncrement =
people
|> List.map (fun p -> { p with Age = p.Age + ageIncrement })
const incrementEveryonesAge = (people, ageIncrement) =>
people.map((p) => {
p.age += ageIncrement;
return p;
});
Both can be one liners, both are relatively easy to read. So, what are the differences?
Type Safety
There is no type safety in JavaScript, if you didn't pass an object with an age property, you've now just given that object one... pretty sure that's not the intention. What's worse is that we didn't even have to give it an array of Person
, we could have passed an object for the people
argument... now how would that function react? ageIncrement
could also be absolutely anything, so what will be the result? Who knows... (better run it and find out >.< )
In F# you'll be thinking, "well, where is the type safety in that? I don't see any type annotations...", you'll note that earlier I said that C# "inferred" a type, because it read a list that it new the types of - known as type inference. C# is okay at that, but due to the implementation of its type system can never be that good.
F# on the other hand has an algebraic type system which means it will always be able to work back figuring out the types for you, if it can't you have to add the annotation (but you'll notice this becomes the exception to the rule).
So, when you look at the F# version, to the rest of your application they'll be seeing this:
let incrementEveryonesAge (people: Person list) (ageIncrement: int) : Person list =
people
|> List.map (fun p -> { p with Age = p.Age + ageIncrement })
Which arguably isn't any less readable. So how did it know?
people |> List.map
this is the first hint, we're running a function List.map
which takes a list as its last argument (see partial application and currying for more info, but out of this scope) so it can infer that people
must be a List
of something.
Secondly, see {p with Age = ...}
, this is a special syntax that allows a record to immutably updated and basically says "copy all values of properties from p with the Age property being ...". Because we've specified Age
F# knows to look for a type in the outer scope with an age property, it will then look at its type and infer that. So, if ageIncrement
is an int
then we can infer that Age
is a property of type int
because that's what the compiler is emitting.
So, what's the difference with this over the JavaScript snippet then?
The F# snippet is fully type safe and compile time (when you the write code) you'll get immediate feedback that you can only use this function on a List
of Person
and no other type.
Immutability
F# supports immutability out of the box. When we call let newFamily = incrementEveryonsAge family 1
we'll get a new list back of everyone in family
having a new age. There is now no equivalence with newFamily
and family
. As previously discussed about the with
syntax, we create a whole new record on each iteration, meaning a whole new list at the end.
Why is this good?
When I scan my code to see what it is doing, I have full confidence in what is happening. If I have a pipeline of functions running off my family
list I can guarantee the output because the function is a pure function!
Consider this:
let sum a b = a + b
let times a b = a * b
10
|> sum 20
|> times 5 // result is going to 150, every single time
Why is it going to be 150 every single time, because we always return a brand-new value, in fact you don't even think about it when you do it with numbers, but F# supports this for objects as well through records
and with
syntax.
In our previous discussion about a pipeline around family, we can guarantee and predict future values without having to run it because our code is immutable. Even if we re-order the pipeline, we'll be fully sure of what is going on.
Knowing what is going to happen is always better than not and that is a core difference between F# and JavaScript (and even C#)
Okay okay okay, so the code is roughly the same, still as succinct, I don't have to use those stupid semi-colons and I can see the walnut and the oak.
That's just the language, what about the eco-system?
Eco System
Yeah, let's discuss that.
What runs JavaScript? Browser and NodeJs
What runs F#? dotnet
Frameworks
Framework Type | JavaScript | F# Equiv |
---|---|---|
Web Server | ExpressJs, Koa, NextJs | ASP.NET, Giraffe, Saturn, Bolero |
UI | React, Vue, Angular | Fable, Elmish, Bolero |
Server Side | Node | .NET |
Database | SQL, Mongo, Redis | the same |
ML & AI | Tensorflow.js, ML5.js | Tensorflow.NET, NumSharp, ML.NET, FsLab |
IaC | Pulumi | Pulumi, Farmer |
As a special mention, Fable is a F# to JS compiler, so, everything you write in F# can be compiled to JavaScript, which means you can write ReactJs in F#... which is awesome.
Cross Platform Compatibility
Linux | Mac | Windows | ARM | Docker | |
---|---|---|---|---|---|
Javascript | yes | yes | yes | yes | yes |
F# | yes | yes | yes | yes | yes |
Supported IDEs
VS Code | Rider | Vim | Visual Studio | |
---|---|---|---|---|
Javascript | yes | yes | yes | yes |
F# | yes (ionide) | yes | yes | yes |
Package Managers
JavaScript has NPM
F# has Nuget
One nice thing in F# scripts that you can't do with JavaScript is
#r "nuget: Newtonsoft.Json"
open Newtonsoft.Json
#r "nuget: DiffSharp-lite, 1.0.0-preview-328097867"
open DiffSharp
#load "Script1.fsx" // this is the same as in JavaSciprt
open Script1
You can just reference the source without having to have it on disk to begin with.
Another difference is that JavaScript brings down a whole load of JavaScript files and we all know the meme about how heavy node_modules is. With .NET, you just get the release version of the dll, so more likely to be only 1 or 2 files per library, rather than 10,000.
Scriptability:
Javascript
$ node
> 1 + 1
2
>
F#
$ dotnet fsi
> 1 + 1;;
val it : int = 2
>
and the rest...
F# can be used for writing script files, running in place of Powershell or Bash. It can be used to define Infrastructure as code. Used in CI/CD, backend services, cloud services (function apps, lambda functions) and drive website UIs, desktop UIs etc.
Due to F# sitting on the .NET library, you have access to a plethora of 20 year old battle hardened and production tested APIs. Not only this, in .NET 5 and .NET 6 there have been vast vast performance improvements allowing some .NET code to outperform C++ but mostly be near performance.
So, if you want to write once, run anywhere with near bare metal performance then F# can do that.
Conclusion
So why should I learn F# or even use it instead of JavaScript?
Let's address the first part, why should I learn f#?
F# is a function first language which is a completely different paradigm to JavaScript. JavaScript has been going in a very functional direction with its fat arrows, Promise monads and map, reduce, flatten
methods. There has even been a proposal for the pipe forward operator |>
. So, from this perspective it'll be a very natural progression.
Having the type safety will introduce a whole new realm of patterns and practices that you can do and along with learning to think "functionally" may just change the way you think and write JavaScript.
I can't remember who said it, but "you should only learn a new language if it changes the way you think" - meaning, if you know JavaScript, then learning C# won't be much benefit, it's mostly just syntactic differences (although the type system again will introduce a whole new realm... etc. etc.). People who know C# won't benefit from learning Java because they're so similar again... You should learn something that expands your knowledge and comfort zone.
The functional concepts which really improve your code are Referential Transparency (Immutability), Pure Functions, Composability, First-class and higher-order functions and Data Structures.
Let's address the second part, use instead of JavaScript?
I've come to live a polyglot language life. I write JavaScript, F# and C# on the daily for all sorts of different tasks. My main go to is always F# apart from UI, where I always use create-react-app
. For my job, I may have to crack out some C# every now and again to help with our products.
So, no, don't use it instead of, but use it alongside with. Getting comfortable with having an arsenal of programming languages behind you is powerful, become the unicorn you know you can be 🦄