A few weeks ago I gave F# a shot, I have my personal reasons for doing this. But I can imagine
you are confused about this sudden move. Why should you even try this weird language? Almost nobody uses it!
You are right, not a lot of people use F# in their projects. But there are a few reasons why I
think you should give it a shot for a hobby project.
When the focus is on functions, use a language that does so too
Most of the applications you and I build are actually big data pipelines. We receive a HTTP
request containing some data. We transform the incoming data into a database query. Then we transform the results of this query back into an object. And finally we transform this object into a HTTP response.
This doesn't sound interesting, since you do this all the time. But do yourself a favor,
take a close look at the code you wrote. How many methods actually use instance variables
from the class their defined in. And how many of those variables are actually about the state
of the object?
I bet a lot of code you wrote actually isn't about object orientation. Most of the code will function without the class it is defined in. And when it does use private fields of the class it is not about managing state.
Most code in our web services you can split into two parts:
- Data transfer objects
- Transformation methods
You grab a HTTP request and transform it into an object. Usually a class that contains only fields. We call this a Data Transfer Object (DTO). You use DTOs and methods together to build a pipeline model for processing data.
You generally focus on the methods and not on object orientation. So why not use a language that helps you do that?
F# focuses on functions, but also has the ability to work with classes. So when you need to use object orientation you can still do so.
So next time you build a micro service, build one in F#. . You will be surprised how well things fit together.
Functional programming changes your perspective
Functional programming in F# asks you to think differently about your code.
Functional programming is more about mathematics than anything else. Functions in mathematics provide
a way to express complex constructions in a simpler format. In math you abstract complex formulas
in functions so that the thing you are expressing is easier to understand.
F# and functional programming languages in general force you to think in this manner. They also force you to be more precise about what your function is going to express.
For example, when you build a validation function. What should it return?
Should it raise an exception when a field is invalid? I guess not, it's not that exceptional.
Yet that is what we tend to do in regular programs.
In all honesty, I think you should avoid exceptions in your code unless it's something
that you can't possibly fix. In that case, raise the exception and quit the program altogether.
For proper validation you want a function that returns a validation error or a result. When I want to define this in C# I have to introduce an Either<TLeft,TRight> class and
return Left
This looks like this:
abstract class Either<TL,TR>
{
public abstract bool IsLeft { get; }
public abstract bool IsRight { get; }
}
class Left<TL,TR>: Either<TL,TR>
{
public TL Value {get;}
public override bool IsLeft { get { return true; } }
public override bool IsRight { get { return false; } }
}
class Right<TL,TR>: Either<TL,TR>
{
public TR Value { get; }
public override bool IsLeft { get { return false; } }
public override bool IsRight { get { return true; } }
}
public Either<Error,T> Validate(T input, List<Validator<T>> validators)
{
foreach(var validator in validators)
{
if(!validator.Validate(input)) {
return new Left<Error,T>(new Error(validator.ErrorMessage));
}
}
return new Right<Error,T>(input);
}
This is a huge amount of code for something as simple as validation logic. On top of that if I want to discover whether I got an error or valid result I need to check for types using if(result is Right<Error,Person>)
or
similar constructions.
In F# the same is much simpler:
type Either<'L,'R> = Left of 'L | Right of 'R
let validate item validators =
let collectValidationErrors validator item errors =
match (validator item) with
| Some(error) -> error :: errors
| None -> errors
let rec validateRecursive results item validators =
match validators with
| [] -> results
| validator :: remainingValidators -> validateRecursive (collectValidationErrors validator item results) item remainingValidators
let results = validateRecursive [] item validators
match results with
| [] -> Right(item)
| _ -> Left(results)
First I define that there's an either type that has a Left of my defined type
and a Right for another type. This is a union type. In C# this is equal to a base class with two subclasses.
Next I define a function called validate. This function I can feed an item to validate and a set of validations that need to be performed on the specified item.
Within the validate function I define a collectValidationErrors function. When this function finds an error this error gets added to the list of existing validation errors. A validator can return a Some(error)
or simply nothing. I can perform pattern matching on this to get the right results back.
The validation error collection function is used by the recursive validation function. The recursive validation function works like this. First I grab the last of validators. If it's empty I'm done and return the collected errors collection. However when it contains a validator followed by a list of other validators I want to continue validation with the next validator. I use the collector function here so that the error found by the validator is added to the results collection. The function is recursive. I call it again using the results of the current validator action and the remaining validators
The recursive validation function here is tail recursive, I added the recursive call last. F# is smart enough to make the code so that it doesn't cause a stack overflow exception. It skips stackframes so that the algorithm runs in constant memory space.
The rest of the validate function is a call to the recursive function and a pattern
match on the results. The results in this case is either a Left containing validation errors
or a Right containing the data itself.
Because of the pattern match syntax and things like union types F# allows you to build much more precise code. It forces you to think about what you actually meant to do. This reduces the amount of bugs you produce.
The fact that I must be more precise in F# forced me to rethink my C# code. I now look through
different eyes when reading my own C# code. I ask myself constantly: What did I mean to say with
this method? Should it raise an exception? Maybe not, so why not use a different construction
to let the caller know he did something wrong?
I think that once you've learned F# you too will think differently about your code.
It's still good old .NET
Thinking differently about your program comes at a price when you start to learn F#. The syntax is very different from what you're used to in C#. I still think it's a good idea
to learn it.
And I can assure that things will be easier when you have a good book. Speaking of which, there's a free book that you can read to learn proper F#:
https://en.wikibooks.org/wiki/F_Sharp_Programming
Read it from start to end and you will discover that functional programming is easier than you think.
On top of that, it's good to know that you're on familiar grounds. The .NET framework is accessible
to you from F# and works just as before. Building something like a web application is different in F#
but not that different.
F# uses the .NET framework and adds a few things to it. In some ways this makes F# better than C#. For example, F# features a native implementation of the actor model. Something that I find a necessary
thing if you're working on concurrent software.
Should I use F# for production then?
Although F# runs on .NET and you can use it for a lot of good things I don't think you should use it on production. F# is only used in 0.2% of the projects on Github. It means that there isn't much support
if you run into problems. Also, Microsoft provides no templates for this language other than
basic console application stuff. If you have a lot of experience building web applications you
can manually set things up. But when you don't have a lot of experience in ASP.NET you will find it harder to work in F#.
I think that you should try F# because I think you will learn something about that C# program you have been building. When you should apply functional thinking when working on pure data transformations. And when to apply functional types to make it more explicit about what a caller can expect.
Give it a try!
Go ahead, take a few hours, learn the language and give it a shot. F# is available in Visual Studio 2015 and you can read my guide to get started on a Mac.