Functional Programming Option type - Introduction

Sandro Maglione

Sandro Maglione

Functional programming

Functional Programming Option type is your best friend against null and null-related errors (Have you ever heard of NullPointerException?).

Option represents some value, using Some, or the absence of it, using None

In this article, I am going to introduce you to the Option type: what is it, why would you want to use it, and how it works. The examples are written in Dart using the fpdart package, Functional Programming in dart and Flutter.


Option type, why not using null instead?

The problem with null is that it can be assigned to any type.

When you assign a type to a variable, for example String, it's because you want to help yourself by saying: "This value is a String, and, from now on, I can use it and access methods only where a String is accepted!".

This contract makes your life as a developer much easier; you set a limit for yourself that the compiler will ensure you will not be able to cross.

But, guess what, null is the exception! null is the only value that is not a String but it is still allowed to enter places where only a String is allowed to enter.

This breaks a huge assumption in your code. Wherever you expect String and only String, you may find yourself dealing with a null instead! What happens if you try to access a method that only String has from a null? Yes, NullPointerException!

Option type to the rescue

Option makes explicit the fact that a value may be missing. Moreover, it allows to update and chain methods as if the value was actually present.

Practically speaking, Option itself can assume two values:

  1. Some, a type that contains the value of the Option when it is present
  2. None, a type that tells us that the Option does not contain any value

How to initialize an Option

The Option type can be initialized directly using Some and None.

const some = Some(10);
const none = None<int>();

Option can also be initialized using specific constructors that automatically build an instance of Option from different functions:

// --- Initialize an Option 👇 --- //
const someInit = Some(10);
const noneInit = None<int>();
 
final someInit2 = some(10);
final noneInit2 = none<int>();
 
/// Create an instance of [Some]
final option = Option.of(10);
 
/// Create an instance of [None]
final noneInit3 = Option<int>.none();
 
/// If the predicate is `true`, then [Some], otherwise [None]
final predicate = Option<int>.fromPredicate(10, (a) => a > 5);
 
/// If no exception, then [Some], otherwise [None]
final tryCatch = Option<int>.tryCatch(() => int.parse('10'));
 
/// When the value is not `null`, then [Some], otherwise [None]
final nullable = Option<int>.fromNullable(10);

Option type example

Let's say for example that you have a catalog of products in your application.

Your function takes the name of the product and returns its price.

In our example, the price of the product is simply its name's length. Nonetheless, we know that our catalog does not accept products with names longer than 6 characters.

For example, the price of 'milk' is 4, while the price of chocolate is not defined.

If we use null to encode a product that does not have a price, our function will look like this:

int? getPrice(String productName) {
  if (productName.length > 6) {
    return null;
  }
 
  return productName.length;
}
 
void main() {
  final price = getPrice('my product name');
  if (price == null) {
    print('Sorry, no product found!');
  } else {
    print('Total price is: $price');
  }
}

We need to use an if-else statement to handle the case when the value is missing.

If we use Option instead the function will be the following:

import 'package:fpdart/fpdart.dart';
 
Option<int> getPrice(String productName) {
  if (productName.length > 6) {
    return none();
  }
 
  return some(productName.length);
}
 
void main() {
  final price = getPrice('my product name');
  price.match(
    (a) {
      print('Total price is: $price');
    },
    () {
      print('Sorry, no product found!');
    },
  );
}

The Option type has a match function that allows us to handle both when the value is present and when the value is missing.

Recap: What we learned in this article

  • Why using Option instead of null: Option allows us to explicitly handle missing values.
  • How to initialize an Option type: There are many different constructors that build an instance of Option from a normal value of any type.
  • match method: Pattern matching on an Option value to handle both the presence and absence of the value inside the Option.

Do you like these short articles on Functional Programming? Let me know on Twitter at @SandroMaglione. Follow me for daily updates on Functional Programming, dart, Flutter, mobile, and web development. If you are interested in more tips and guides about these topics, subscribe to my newsletter here below 👇

👋・Interested in learning more, every week?

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.