DEV Community

loading...
Cover image for Functional Programming in Dart

Functional Programming in Dart

Marcos Sevilla
Flutter and Dart developer.
Updated on ・6 min read

Close your eyes for a moment.

You are in first year of your IT career, whichever it is, there is no discrimination here. You go to your first programming class and your teacher (let's identify him as a man) begins to explain what an algorithm is and how they exist everywhere even if they aren't noticeable with the naked eye.

Suddenly he introduces this word of "programming" and mentions to you that there are several types, styles or as it sounds more intellectual: paradigms. He tells you that they're going to learn the structured paradigm, because it's your first class. You hear that he also mentions the object-oriented paradigm, and calm down, you will see that a semester later.

There are several that he mentions that probably won't teach you in college, at least that's the way it happened to me. One of those was functional programming, or the functional paradigm. This paradigm is sometimes neglected because not all languages allow it to be implemented as it should.

As the title says, this article is focused on the Dart language and also on the dartz package. There are other packages for functionally programming in Dart but they do not have the breadth and use of dartz.

The functional paradigm

https://media.giphy.com/media/DHqth0hVQoIzS/giphy.gif

From that introduction you can intuit that I'm not an expert in this paradigm, but we are going to focus on the basics so that you can understand the great tricks that can be achieved with it.

Functional programming has the characteristics of being declarative and also treating functions as a primitive data type, or as it sounds nicer, first-class citizens.

Those who already know Dart, know that it fits very well with these characteristics. We can send a function to a class or another function as a property or argument.

void main() {
  final greeting = (String name) {
    return 'Hi $name';
  };

  saySomething(greeting);
}

void saySomething(Function(String) message) {
  print(message('Marcos'));
}

// Hi Marcos
Enter fullscreen mode Exit fullscreen mode

There we create an object that is a function and we can execute it if we pass it as an argument to another. Compatibility with the paradigm, checked.

In itself, functional programming has an origin closely linked to mathematics since it uses this scientific foundation to be able to process more computationally complex tasks thanks to the replacement of loops, which increase complexity many times, by functions that accept other functions as arguments or they return a function as a result. The latter are called higher order functions.

Advantages

The big question is: what is the advantage of incorporating this paradigm into our code?

I would summarize: it allows us to make code more concise, it reduces the complexity when reading it by not having several control statements in it. It makes testing it much easier as we have function specific results. The coolest thing, it can be easily combined with other paradigms.

Disadvantages

Not everything is good, there are some problems when we need a variable state since results are returned, this is where combining it with other paradigms is very useful. It can be slow when going into many recursions.

In general, it is not the best alternative for all tasks.

Dartz

This package contains many tools that make it easy for us to incorporate functional programming into our project. We go by parts.

First, as a basis, we are going to have a person entity to exemplify some use cases where we can use functional programming tools.

class Person {
  final int age;
  final String name;

  Person(this.age, this.name);
}
Enter fullscreen mode Exit fullscreen mode

Either

The first class that is super useful is Either. It's an abstract class that admits two types of data as return values, that is, it can return an integer and a string, for example.

Either<int, String> getPersonData(Person person) {
  final returnName = Random().nextBool();

  if (returnName) {
    return Right(person.name);
  } else {
    return Left(person.age);
  }
}
Enter fullscreen mode Exit fullscreen mode

In this case, we define a function that returns an Either that can contain a value of int or String. So our function receives a person object and based on a random value that we generate, we send a response.

If you notice, we don't return the name or age at once, we wrap it in a Right or Left class. The first value that we define in the Either must be returned wrapped in Right and the second value in Left. These are the classes that implement Either and the ways to return its value.

void main() {
  final me = Person(21, 'Marcos');

  final either = getPersonData(me);

  either.fold(
    (age) => print('Your age is $age'),
    (name) => print('Your name is $name'),
  );
}

// Your age is 21
// Your name is Marcos
Enter fullscreen mode Exit fullscreen mode

Now that we instantiate the class and pass it as an argument to our function, something interesting happens. Since our variable can adopt two possible values of Either, we have a very useful method called fold that gives us access to two functions that return a data type each to perform some specific action, such as assignments or the execution of another function that needs the argument that it returns.

If that sounds a bit confusing, see fold as an if-else for the two possible data types the function can return.

This class is very useful when we call a third-party API that we don't know very well in order to catch any unknown errors and return them in the function.

Option

Option<String> getOptional(Person person) {
  final returnName = Random().nextBool();

  if (returnName) {
    return None();
  } else {
    return Some(person.name);
  }
}
Enter fullscreen mode Exit fullscreen mode

One very similar to Either is Option. Sometimes we don't want to return two possible values, and here you will say "ah, that's a normal function" but Option allows us more than that.

First, it allows us to adapt if logic to decide what to do based on the result obtained. It can be used to return a validation of the execution of some operation.

void main() {
  final me = Person(21, 'Marcos');

    final option = getOptional(me);

  option.fold(
    () => print('None!'),
    (name) => print('Aha! Your name is $name'),
  );
}

// None!
// Aha! Your name is Marcos
Enter fullscreen mode Exit fullscreen mode

We have the new classes that implement it: None and Some. None allows us to return an object that represents that the expected action cannot be performed and Some contains the value that the operation performed was expected to return.

See None as a missing value that could not be returned. This helps us to handle general errors and the composition of our code in a more expressive way.

Oh right...

Either and Option are structures that are called monads. These can be applied to data types to follow a specific sequence of steps. Like when we obtain one of two specific values and execute some action based on what we receive.

There are much more complex monads such as Evaluation that comprise different types of monads and it is known as the Swiss army knife of these structures, but the ones we have seen I think are enough to keep our code simple.

Closing remarks

You can go much deeper into this package if you want, it contains a lot of functionality that comes in handy if you are into math and categorization theories. I consider myself a bit more pragmatic, I quite like this style and I share this small tips with you because I consider it to be useful in any type of project.

Likewise, the monads that we saw have more methods that can be useful to them. They remind me a lot of the methods we have in the classes that we do with Freezed but applied to functions -yeah, functional programming- and their return values.

Very useful to handle different scenarios in your projects, especially possible errors or unforeseen results.

https://media.giphy.com/media/3o7qDT9Yp5DdcN3qi4/giphy.gif

One more thing...

There are also immutable versions of existing data collections in Dart. If you are going to use data that doesn't change in this type of structure, in the functional paradigm they are very used since the changes of variables are made through the functions.

These include IList,IMap, IVector,IHashMap ... among other variants.

void main() {
  final aList = [3, 6, 7, 9];
  final aMap = {'name': 'Marcos', 'age': 21};

  final immutableList = ilist(aList.map((e) => e));
  final immutableMap = imap(aMap);
}
Enter fullscreen mode Exit fullscreen mode

https://media.giphy.com/media/5rnYD6hCSDBmXXP14U/giphy.gif

As always...

You can share this article to help another developer to continue improving their productivity when writing applications with Flutter.

There's a Spanish version of this article on Medium. You're welcome. 🇪🇸

Also if you liked this content, you can find even more and keep in contact with me on my socials:

Discussion (0)