28  comments

While Dart is in its core an object-oriented programming language, that doesn’t mean that you’re stuck only with that paradigm. In fact, Dart is something called a multi-paradigm language! Functional programming (FP) makes your code easier to test and reuse, and also makes it less error prone.

With Dart, it’s easy to start introducing practical functional concepts into your code in a reasonable amount. This way, you reap the benefits of FP while not confusing others (and yourself) with how your Dart code is written.

Functional programming is a style of software development emphasizing functions that don't depend on program state. Functional code is easier to test and reuse, simpler to parallelize, and less prone to bugs than other code.
P. Chiusano & R. Bjarnason, Functional Programming in Scala

Imperative vs Functional Programming

Before we can start looking into any of the more advanced concepts of FP, let's get the basics out of the way so that we're all on the same page. Let's look at a very simple example where we want to sum the numbers inside a List<int>. First up, the imperative approach:

main.dart

void imperative() {
  /// Imperatively sum elements of a list
  const List<int> list = [1, 2, 3, 4];

  int sum = 0;
  for (int i = 0; i < list.length; ++i) {
    sum = sum + list[i];
  }
}

These are absolutely simple few lines of code where we sum the integers inside of the List using a for loop.

In order to accomplish this though, we need to create a mutable int sum, then another int i that we don't even really care about, it's just there so that the for-loop can run. Inside of the loop, we mutate the sum variable by adding to it the integer value present in the list at the index we're currently iterating over.

This is all simple, of course, but we're really managing the workings of our program by ourselves and telling it what to do step by step. Should this bit of code do something more complex than just summing a list of integers, we could have trouble reading and maintaining it.

Let's now look at how we can do the exact same thing in FP:

main.dart

void functional() {
  /// Functionally sum elements of a list
  const List<int> list = [1, 2, 3, 4];

  final sum = list.fold<int>(
    0, // initial value
    (previous, current) => previous + current,
  );
}

Instead of creating a for-loop, we call fold on the list. We can declare the sum variable to be final. It is no longer mutable thus making the code less error prone from us accidentally mutating this variable from another place in the codebase.

We also don't need to worry about managing a for-loop using some sort of an iterative variable. The fold method operates on a higher level of abstraction and just provides us with the previous sum and the current element value that we can add to the previous sum. No need to worry about indexes or any other implementation details. 

Overall, the functional approach has less moving parts and is less error-prone.

More Complex Example

In another just a little bit more complex code, we have a List<String> from which we want to create a new List that holds both the original String and also its length inside of a record (String, int). Imperative code looks like the following:

main.dart

void imperative() {
  /// Assign character count to strings
  const List<String> strings = ['dart', 'is', 'awesome'];

  final List<(String, int)> charCounts = [];
  for (int i = 0; i < strings.length; ++i) {
    charCounts.add((strings[i], strings[i].length));
  }
}

If we now decide that the charCounts list should only contain strings that are longer than two characters, we need to write an if-statement inside of the for-loop.

main.dart

void imperative() {
  /// Assign character count to strings and leave only
  /// the ones longer than 2 characters
  const List<String> strings = ['dart', 'is', 'awesome'];

  final List<(String, int)> charCounts = [];
  for (int i = 0; i < strings.length; ++i) {
    if (strings[i].length > 2) {
      charCounts.add((strings[i], strings[i].length));
    }
  }
}

We again need to get into the already existing code, find out its implementation details (e.g. that the iteration variable is int i ) and only then can we add this new functionality.

If we implement the exact same thing in a functional approach (at first without filtering the strings by length), we get:

main.dart

void functional() {
  /// Assign character count to strings
  const List<String> strings = ['dart', 'is', 'awesome'];

  final charCounts = strings.map((string) => (string, string.length));
}
This tutorial assumes you're not a complete beginner. If you need a refresher on the dart:core library methods such as map, please read the official docs.

The code is looking much cleaner without the for-loop already. However, the functional approach really shines once we try to filter the strings by length.

main.dart

void functional() {
  /// Assign character count to strings and leave only
  /// the ones longer than 2 characters
  const List<String> strings = ['dart', 'is', 'awesome'];

  final charCounts = strings
      .where((string) => string.length > 2)
      .map((string) => (string, string.length));
}

We don't need to get inside a previously written code and study its implementation details before we add the new functionality. All we need to do is to chain method calls that are separate from each other, yet can work together as well to create a brand new functionality.

It's something like pieces of Lego that look good separately but you can also join them in various ways. If we were to fit the imperative approach with the for-loop and an if-statement into this Lego analogy, it's as if we were not simply joining individual pieces together, but rather cutting things out with a scalpel and then melting these odd cut-up pieces together with a blowtorch (not so nice).

This is not to say that imperative programming is somehow wrong. You simply have to choose the correct approach for your particular needs and preferences. Since Dart is a multi-paradigm language, you can write code however you like.

OOP vs FP Class Hierarchies

Dart is a strongly object-oriented programming language where everything is a class. Even all the functions you write are in the end understood as classes with a method, more specifically, subclasses of the Function abstract class from the dart:core library.

This, however, doesn't mean that you are stuck with only the OOP paradigm! Dart has all the features needed to support a FP approach to class hierarchies by allowing you to create algebraic data types.

First, let's take a look at a regular class hierarchy. All of the following code has been taken from an official Dart article on Medium but I've modified the pseudocode to actually be able to compile and run.

main.dart

abstract class Recipe {
  final int time;
  final int temp;
  final List<String> ingredients;

  Recipe({
    required this.time,
    required this.temp,
    required this.ingredients,
  });
  
  void bake();
}

class Cake extends Recipe {
  Cake() : super(
    time: 40,
    temp: 325,
    ingredients: ['Flour', 'Eggs', 'Milk'];
  );

  @override  
  void bake() => time * temp;
}

class Cookies extends Recipe {
  Cookies() : super(
    time: 25,
    temp: 350,
    ingredients: ['Flour', 'Butter', 'Sugar'];
  );

  @override
  void bake() {
    (time / 2) * temp;
    //TODO: Rotate cookies
    (time / 2) * (temp - 15);
  }
}

The abstract base class Recipe has three fields and a single method. All of the subclassed Cake and Cookies then assign their own values for these fields and implement the bake method.

This is a standard example of a class hierarchy in OOP but let's now write the exact same thing with an FP approach.

main.dart

sealed class Recipe {
  final int time;
  final int temp;
  final List<String> ingredients;

  Recipe({
    required this.time,
    required this.temp,
    required this.ingredients,
  });
}

class Cake extends Recipe {
  Cake()
      : super(
          time: 40,
          temp: 325,
          ingredients: ['Flour', 'Eggs', 'Milk'],
        );
}

class Cookies extends Recipe {
  Cookies()
      : super(
          time: 25,
          temp: 350,
          ingredients: ['Flour', 'Butter', 'Sugar'],
        );
}

void bake(Recipe recipe) {
  switch (recipe) {
    case Cake():
      recipe.time * recipe.temp;
    case Cookies():
      (recipe.time / 2) * recipe.temp;
      //TODO: Rotate cookies
      (recipe.time / 2) * (recipe.temp - 15);
  }
}

Notice that the class Recipe is no longer marked abstract but rather sealed. All the fields remain the same and the Cake and Cookies subclasses populate those fields with their own values through the super-constructor just as in the OOP approach but notice what's missing. There is no bake method in any of the classes. Instead, it's now a top-level function.

Algebraic data types separate data from logic. The code run by the bake function is exactly the same now as it was when it was a member method inside of the classes but now we're using a switch statement to execute the proper code for any Recipe subclass that was passed in.

Because the Recipe base class is sealed, the switch statement will not compile unless we switch through all the existing subclasses of Recipe, so the FP code is just as safe to write as the OOP version.

Pure Functions

Separating data from the operation performed with that data resulted in having a top-level bake function which is only dependent on its parameters and can "communicate with the outside" only by returning a value. Here it returns void but the point still stands. This makes bake a pure function.

main.dart

void bake(Recipe recipe) {
  switch (recipe) {
    case Cake():
      recipe.time * recipe.temp;
    case Cookies():
      (recipe.time / 2) * recipe.temp;
      //TODO: Rotate cookies
      (recipe.time / 2) * (recipe.temp - 15);
  }
}

With the OOP approach, the bake function was a member of a class (a method), and accessed the fields of the class. Although in our case this didn't happen, such a method can also mutate fields of the class, thus making the return value not the only point of communication with the outside.

main.dart

class Cake extends Recipe {
  Cake() : super(
    time: 40,
    temp: 325,
    ingredients: ['Flour', 'Eggs', 'Milk'];
  );

  @override  
  void bake() => time * temp;
}

The advantages of a pure function as opposed to an unpure method are that it's easier to test and maintain since EVERYTHING a function does is contained within it. There are no possible side effects somewhere else in the codebase and you, for example, can't forget to mock an object while testing because every dependency of that function is passed in as an argument.

Error Handling

Another area where FP principles shine is error handling. Enough of those exceptions and try-catch statements all over the place! While we could write all the functional error handling code from scratch, we're going to use the fpdart package which is a must-have in your project if you want to move more into the FP paradigm.

pubspec.yaml

dependencies:
  fpdart: ^1.1.0

Let's say we have the following enum implementation for a US-style grade that can be A, B, C, D or F and we also have a method for parsing strings. If the string contains something else that a valid grade letter character, we throw an ArgumentError.

main.dart

enum Grade {
  A, B, C, D, F;

  static Grade parse(String grade) => switch (grade.toUpperCase()) {
        'A' => Grade.A,
        'B' => Grade.B,
        'C' => Grade.C,
        'D' => Grade.D,
        'F' => Grade.F,
        _ => throw ArgumentError('Invalid grade: $grade'),
      };
}

The problem with throwing errors like this is that the following will compile without an issue.

main.dart

void main() {
  final grade = Grade.parse('definitely not valid string');
  somehowUseGrade(grade);
}

You see that? We're missing a try-catch block but we're going to find about this only at runtime when our app crashes because of an uncaught ArgumentError.

It's easy enough to remember to wrap the grade parsing with a try-catch in this small example code but in complex apps its incredibly hard, if not impossible, to remember which method throws which kind of an exception and to catch everything properly.

Sure, you can write documentation and comments all over the place but still, you're relying on yourself to remember things. It would be much better to let the Dart compiler let you know that you're not handling errors correctly. Failing at compile time is what we want, not at run time.

Using Either

The fpdart package provides us with a type called Either. In its principle it is a generic sealed class with two subclasses - Left and Right. The left "side" of Either holds the error while the right side holds the right (correct) value.

The principle of using Either is to unify the exception flow and data flow into the return type of a function. In our simple case, the left side will hold only the error message string.

main.dart

enum Grade {
  A, B, C, D, F;

  static Either<String, Grade> parse(String grade) =>
      switch (grade.toUpperCase()) {
        // Instantiating a Right subclass of Either directly
        'A' => Right(Grade.A),
        // The convention is to use a helper function though (lower case "r")
        'C' => right(Grade.C),
        'B' => right(Grade.B),
        'D' => right(Grade.D),
        'F' => right(Grade.F),
        // Error goes into the Left subclass of Either
        _ => left('Invalid grade: $grade'),
      };
}

Since we've eliminated the separate flow of exceptions the following code will not compile.

main.dart

void main() {
  final grade = Grade.parse('A');
  // Error: Argument type 'Either' can't be assigned to parameter type 'Grade'.
  somehowUseGrade(grade);
}

We're now forced to handle the error and that's a good thing! Either is just a regular sealed class so we can technically use the regular switch statement...

main.dart

void main() {
  final gradeEither = Grade.parse('A');
  switch (gradeEither) {
    case Left(value: final l):
      handleError(l);
    case Right(value: final r):
      somehowUseGrade(r);
  }
}

But the convention is to use the helper match method on the Either class to do the same thing in less lines of code.

main.dart

void main() {
  final gradeEither = Grade.parse('A');
  gradeEither.match(
    (l) => handleError(l),
    (r) => somehowUseGrade(r),
  );
}

Conclusion

In this tutorial you've learned the basics of practical functional programming principles and also error handling using the Either type. Understanding these foundations allows you to venture further into functional programming with the fpdart package. I will cover some of the more advanced techniques of writing FP code in an upcoming tutorial so make sure you're subscribed not to miss it!

About the author 

Matt Rešetár

Matt is an app developer with a knack for teaching others. Working as a freelancer and most importantly developer educator, he is set on helping other people succeed in their Flutter app development career.

You may also like

  • What i do not realize is in fact how you are no longer actually much more well-favored than you might be right now. You’re very intelligent. You recognize thus considerably in relation to this topic, made me in my view believe it from numerous numerous angles. Its like men and women are not fascinated until it is one thing to do with Lady gaga! Your own stuffs excellent. All the time handle it up!

  • Wonderful beat ! I wish to apprentice while you amend your web site, how could i subscribe for a blog web site? The account aided me a acceptable deal. I had been a little bit acquainted of this your broadcast provided bright clear idea

  • What i do not understood is in truth how you are not actually a lot more smartly-liked than you may be now. You are very intelligent. You realize therefore significantly in the case of this topic, produced me individually imagine it from numerous numerous angles. Its like men and women don’t seem to be fascinated until it is one thing to do with Woman gaga! Your own stuffs nice. All the time care for it up!

  • Hello, Neat post. There’s an issue together with your site in internet explorer, would check this텶E still is the marketplace chief and a large element of other folks will leave out your magnificent writing due to this problem.

  • Thanks, I have recently been looking for info about this subject for a while and yours is the greatest I have discovered so far. However, what in regards to the bottom line? Are you certain in regards to the supply?

  • I am not sure where you’re getting your info, but good topic. I needs to spend some time learning much more or understanding more. Thanks for magnificent info I was looking for this information for my mission.

  • I do agree with all the ideas you have introduced on your post. They are very convincing and will definitely work. Still, the posts are very short for newbies. May just you please prolong them a little from subsequent time? Thank you for the post.

  • Fantastic beat I would like to apprentice while you amend your web site how could i subscribe for a blog site The account helped me a acceptable deal I had been a little bit acquainted of this your broadcast offered bright clear concept

  • Hey,

    The moment we’ve all been waiting for is finally here – GoBuildr is now LIVE! 🎉

    🌐 Create ultra-lightning-fast websites, sales funnels, eCommerce stores, and more in less than 60 seconds, with just a keyword!

    🚀 Say goodbye to the limitations of traditional page builders. GoBuildr combines the functionality of 16 different tools into one powerful app, supercharged with AI-assisted technology.

    ⇒ Click Here To Checkout Demo https://ext-opp.com/GoBuildr

  • He Got 256,354 Free Views With AI…

    Can you believe it?

    People spend thousands of dollars to get that kind of result…

    My friend Kundan just did it for free…

    He only used his new app… AI ScreenSnap…

    It’s the world’s first AI app that can generate videos with the power of Video-Exclusive AI Engine…

    That can edit, record, and generate videos with just a few clicks… with zero experience…

    Click here now and watch AI ScreenSnap in action https://ext-opp.com/AIScreenSnap

  • Narin ve tatlı, aynı zamanda escort sakarya sakarya arkadaş canlısı ve eğlenceyi seven bir insanım. Her zaman gülümseyen ve çok açık fikirli güzel bir hanımefendiyim. Doğal bir vücudum var, inanılmaz deneyim arayışında iseniz o halde ne arzuladığınızı bana iletebilirsiniz.

  • {"email":"Email address invalid","url":"Website address invalid","required":"Required field missing"}
    >