Dart and Functional Programming: applying Haskell concepts with dartz
Juan Belieni
Posted on January 3, 2022
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"
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');
}
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
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)
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();
},
);
}
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
In this case, sequenceA
receives a list of Either
s, 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)
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:
Posted on January 3, 2022
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.