Today you are going to learn how to practically use fpdart Functional Programming in your Dart and Flutter app.
Many Functional Programming tutorials are impossible to understand because they don't show you practically how can you use Functional Programming in your code. Not this one! In this article, we are going to see a concrete and fully explained example of Functional code inspired by a real dart package.
My goal is to show you (not simply tell) why you should consider Functional Programming and fpdart for your next (and current) application. We will walk through the main classes provided by fpdart and how to use them.
The example code of this article is taken from the logger package. I choose a popular dart and flutter package to show you that Functional Programming can be applied to real applications and packages.
We are going to focus on a single function called log. The source code is this:
We don't really care what this function does. We are going to see how to convert this simple function from Imperative to Functional.
1. Functional Programming is about Pure Functions
When trying to understand the function in isolation you may find some issues. What is _active? Where does it come from? What about _printer and _output?
That is the problem with Impure Functions! The function is using some external variables, which may change unpredictably. We say that impure functions have side effects that change or access something outside of their scope. This makes the code less testable, less readable, and a real pain for every developer!
Functional Programming instead is all about Pure Functions. All the variables that we are going to use must be passed as input to the function. In this way, we are sure that the function will have the same output when given the same input!
2. Forget using void, Functional Programming uses Unit instead
A function that returns void is by definition impure. If the function returns void (nothing), and it does not have side effects (pure), then the function does nothing and there is no reason to call it!
Functional Programming uses the Unit type instead! Unit is basically a singleton. It has only one instance which is always the same.
3. Please, do not throw errors. Functional Programming uses Either instead
If we look at the function, we see that it throws some ArgumentError. Throwing errors means breaking your code in unexpected ways at runtime!
You must be always in control over errors and how to display them to the user (instead of crashing the app). Functional Programming achieve this using Either.
Either can be Left or Right (not both!). When Either is Left, it means that the function returned an error. When Either is Right, it means that the function was successful.
4. Always in control with the IO type!
When you normally call a function, it executes and returns the result. In Functional Programming, we often want complete control to decide when the function is executed. We are going to use the IO type to achieve that!
The IO type is simply a type that pauses the actual execution of the function until you call the run() method. Since we want to also keep Either, we use the IOEither type of fpdart that easily combines IO and Either together:
5. How to use Either instead of throw
Instead of throwing errors, we return a Left containing the error message. It is a simple as that! We construct a Left by using the IOEither.left constructor:
6. Immutability, const and final instead of var
Something that is mutable is unpredictable. Immutability means that every variable cannot be reassigned or changed. If you need to compute a new variable, you create a new instance that leaves the previous instance unmodified:
That's way we are going to change var with const and final:
7. If? What happens when if is false? Handling all cases using Option
Using if has an intrinsic issue: what happen when the if check does not pass and you forgot to add an else case?
We want to be reminded by the type system to handle all possible cases, to handle exceptions and edge cases. We use the Option type for that! Option is similar to Either. Instead of having Left and Right, it has None and Some. In the None case, we don't specify any type (differently from Left of Either).
We build an instance of Option from the shouldLog function by using Option.fromPredicate:
Now we are required to handle both the true and false case. We use the match function of Option to do that:
We do the same for the second if check for isNotEmpty:
8. Try catch? No thanks, Either will save us!
We said no throw allowed. Therefore, try/catch statements are useless!
We use a special constructor of Either (IOEither) which automatically catches and handles errors: IOEither.tryCatch! You specify the function to execute and what to do in case of errors. The method constructs an IOEither that we return:
9. Running the function using IOEither
Finally, we can call the function and get back our IOEither!
As I told you before, IO will not execute the function yet. You are in control! It means that you must explicitly call the run() method when you want to execute the function and access the Either inside it:
Comparison
And that's it! We successfully converted imperative code used in a real package to functional programming!
You can compare the two final result here below:
I know it is a lot of new concepts to grasp. I hope I did a good job in introducing you to the world of Functional Programming and fpdart.
Don't worry to much on the details, everything will become clear as you get more used to this new paradigm.
If you are interested to learn more, follow me on Twitter at @SandroMaglione and subscribe to my newsletter here below. Thanks for reading!
Every week I build a new open source project, with a new language or library, and teach you how I did it, what I learned, and how you can do the same. Join me and other 600+ readers.