Dart and Functional Programming: applying Haskell concepts with dartz

juanbelieni

Juan Belieni

Posted on January 3, 2022

Dart and Functional Programming: applying Haskell concepts with dartz

Haskell is a purely functional programming, with a lot of powerful features. In the other hand, Dart is a language focused on creating fast multiplatform apps.

But how could we bring some good features and concepts from Haskell into the Dart language? This is exactly what dartz tries to do.

Maybe (or Option)

The first concept we will try to apply in Dart is Maybe, a data structure in Haskell that can hold Nothing or Just <value>. In Haskell, Maybe would be used in a context where the return value of a function will not necessarily be a meaningful value.

Example:

classifyAge :: Int -> Maybe String
classifyAge age | age < 0   = Nothing
                | age < 13  = Just "Child"
                | age < 20  = Just "Teenager"
                | age < 65  = Just "Adult"
                | otherwise = Just "Elderly"
Enter fullscreen mode Exit fullscreen mode

With dartz, we would use the Option type to have the same behavior:

Option<String> classifyAge(int age) {
  if (age < 0) return none();
  if (age < 13) return some('Child');
  if (age < 18) return some('Teenager');
  if (age < 65) return some('Adult');
  return some('Elderly');
}
Enter fullscreen mode Exit fullscreen mode

But why you would use such a type in Dart? Because mapping values and dealing with edge case turns to be much easier:

classifyAge(12)
  .map((classification) => 'This person is a $classification')
  .getOrElse(() => 'Invalid age');

// This person is a Child
Enter fullscreen mode Exit fullscreen mode

Either

Either is another very important data structure in Haskell, because it can express two different types in terms of Left and Right.

Example:

safeDiv :: Int -> Int -> Either String Int
safeDiv x y | y == 0    = Left "Division by zero"
            | otherwise = Right (x `div` y)
Enter fullscreen mode Exit fullscreen mode

Here, we used Either to express a type of value that can be either an error or an actual return value.

In Dart, we can use this to handle cases like an API call, where the left value will be an error message (or the error object itself) and the right value will be the data fetched:

Future<Either<String, Data>> getPost(int id) async {
  try {
    final response = await http.get('$url/$id');
    return right(response.data);
  } catch (e) {
    return left('Failed to fetch post');
  }
}

...

Widget build(BuildContext context) {
  return FutureBuilder<Either<String, Data>>(
    future: getPost(1),
    builder: (context, snapshot) {
      if (snapshot.hasData) {
        return snapshot.data.fold(
          (error) => Text(error),
          (post) => Text('Post: ${post.title}'),
        );
      }

      return CircularProgressIndicator();
    },
  );
}
Enter fullscreen mode Exit fullscreen mode

Traversable

Let's build a simple function that returns the RNA transcription of a DNA, but when a character is invalid, it returns just the character.

In Haskell, we can build this using the Traversable data structure, that implements the sequenceA function:

toRna :: String -> Either Char String
toRna dna = sequenceA $ map transcribe dna
 where
  dnaToRna = zip "GCTA" "CGAU"
  transcribe c = maybe (Left c) Right $ lookup c dnaToRna
Enter fullscreen mode Exit fullscreen mode

In this case, sequenceA receives a list of Eithers, and concatenate them in an Either of a list. But an interesting behavior of this function is that, when a Left value is found, it is returned immediately.

In Dart, we can do everything we did in Haskell, because dartz implements all the functions needed:

Either<String, String> toRna(String dna) {
  final dnaToRna = IMap.fromIterables(
    ['G', 'C', 'T', 'A'],
    ['C', 'G', 'A', 'U'],
    StringOrder,
  );

  Either<String, String> transcribe(String c) => dnaToRna.get(c).fold(
        () => left(c),
        (n) => right(n),
      );

  final transcribedDna = IList.from(dna.split('')).map(transcribe);

  return IList.sequenceEither(transcribedDna).map(
    (dna) => dna.toList().join(''),
  );
}

...

print(toRNA('GCTA')); // Right(CGAU)
print(toRNA('CGAU')); // Left(U)
Enter fullscreen mode Exit fullscreen mode

Conclusion

The dartz package offers a lot more functions and data structures than those mentioned here. If you have a functional programming background, programming in Dart can be much more intuitive with this package. But if you don't, data structures like Option and Either can be still very useful.

Some useful links:

💖 💪 🙅 🚩
juanbelieni
Juan Belieni

Posted on January 3, 2022

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related