Functional programming in Dart and Flutter
All the main functional programming types and patterns fully documented, tested, and with examples
fpdart is fully documented. You do not need to have any previous experience with functional programming to start using
fpdart
. Give it a try!
fpdart is inspired by fp-ts, cats, and dartz.
Follow my Twitter for updates, or subscribe to the newsletter
functional programming
and fpdart
functional programming
and fpdart
Would you like to know more about functional programming, fpdart, and how to use the package?
đ Collection of tutorials on fpdart
Are you new to fpdart
and functional programming?
đ¨âđť Getting started with fpdart
complete guide
Interested in what fpdart
is and how it came to be?
đś Full history of fpdart
and functional programming in dart
# pubspec.yaml
dependencies:
fpdart: ^1.1.0
fpdart
+ riverpod
Step by step course on how to build a safe, maintainable, and testable Flutter app using fpdart
and riverpod
.
Flutter app that lets you search and view your favorite Pokemon:
Re-implementation using fpdart
and functional programming of the Open Meteo API from the flutter_weather app example in the bloc package.
A 2 parts series explains step by step the Open Meteo API code:
Example of how to read and write a local file using functional programming.
Using fpdart
with other libraries and noticing naming conflicts? Learn how to rename the classes that conflict with other SDK or third-party packages.
Used when a return value can be missing.
For example, when parsing a
String
toint
, since not allString
can be converted toint
/// Create an instance of [Some]
final option = Option.of(10);
/// Create an instance of [None]
final none = Option<int>.none();
/// Map [int] to [String]
final map = option.map((a) => '$a');
/// Extract the value from [Option]
final value = option.getOrElse(() => -1);
/// Pattern matching
final match = option.match(
() => print('None'),
(a) => print('Some($a)'),
);
/// or use Dart's pattern matching as well đ¤
final dartMatch = switch (option) {
None() => 'None',
Some(value: final a) => 'Some($a)',
};
/// Convert to [Either]
final either = option.toEither(() => 'missing');
/// Chain computations
final flatMap = option.flatMap((a) => Option.of(a + 10));
/// Return [None] if the function throws an error
final tryCatch = Option.tryCatch(() => int.parse('invalid'));
Used to handle errors (instead of Exception
s).
Either<L, R>
:L
is the type of the error (for example aString
explaining the problem),R
is the return type when the computation is successful
/// Create an instance of [Right]
final right = Either<String, int>.of(10);
/// Create an instance of [Left]
final left = Either<String, int>.left('none');
/// Map the right value to a [String]
final mapRight = right.map((a) => '$a');
/// Map the left value to a [int]
final mapLeft = right.mapLeft((a) => a.length);
/// Return [Left] if the function throws an error.
/// Otherwise return [Right].
final tryCatch = Either.tryCatch(
() => int.parse('invalid'),
(e, s) => 'Error: $e',
);
/// Extract the value from [Either]
final value = right.getOrElse((l) => -1);
/// Chain computations
final flatMap = right.flatMap((a) => Either.of(a + 10));
/// Pattern matching
final match = right.match(
(l) => print('Left($l)'),
(r) => print('Right($r)'),
);
/// or use Dart's pattern matching as well đ¤
final dartMatch = switch (right) {
Left(value: final l) => 'Left($l)',
Right(value: final r) => 'Right($r)',
};
/// Convert to [Option]
final option = right.toOption();
Wrapper around an sync function. Allows to compose synchronous functions that never fail.
/// Create instance of [IO] from a value
final IO<int> io = IO.of(10);
/// Create instance of [IO] from a sync function
final ioRun = IO(() => 10);
/// Map [int] to [String]
final IO<String> map = io.map((a) => '$a');
/// Extract the value inside [IO] by running its function
final int value = io.run();
/// Chain another [IO] based on the value of the current [IO]
final flatMap = io.flatMap((a) => IO.of(a + 10));
Wrapper around an async function (Future
). Allows to compose asynchronous functions that never fail.
If you look closely, it's the same as
IO
but for async functions đĄ
/// Create instance of [Task] from a value
final Task<int> task = Task.of(10);
/// Create instance of [Task] from an async function
final taskRun1 = Task(() async => 10);
final taskRun2 = Task(() => Future.value(10));
/// Map [int] to [String]
final Task<String> map = task.map((a) => '$a');
/// Extract the value inside [Task] by running its async function
final int value = await task.run();
/// Chain another [Task] based on the value of the current [Task]
final flatMap = task.flatMap((a) => Task.of(a + 10));
These types compose together the 4 above (Option
, Either
, IO
, Task
) to join together their functionalities:
IOOption
: sync function (IO
) that may may miss the return value (Option
)IOEither
: sync function (IO
) that may fail (Either
)TaskOption
: async function (Task
) that may miss the return value (Option
)TaskEither
: async function (Task
) that may fail (Either
)Read values from a context without explicitly passing the dependency between multiple nested function calls. View the example folder for an explained usecase example.
Combine the Reader
type (dependecy) with Task
(asynchronous).
Combine the Reader
type (dependecy) with Task
(asynchronous) and Either
(error handling).
Used to store, update, and extract state in a functional way. View the example folder for an explained usecase example.
Version v0.6.0
introduced the Do notation in fpdart
. Using the Do notation makes chaining functions easier.
For example, a typical chain of methods in fpdart
looks as follows:
/// Without the Do notation
String goShopping() => goToShoppingCenter()
.alt(goToLocalMarket)
.flatMap(
(market) => market.buyBanana().flatMap(
(banana) => market.buyApple().flatMap(
(apple) => market.buyPear().flatMap(
(pear) => Option.of('Shopping: $banana, $apple, $pear'),
),
),
),
)
.getOrElse(
() => 'I did not find đ or đ or đ, so I did not buy anything đ¤ˇââď¸',
);
Notice how you need to call flatMap
multiple times to collect multiple variables and use them together (market
, banana
, apple
, pear
).
Everything looks more linear and simple by using the Do notation:
/// Using the Do notation
String goShoppingDo() => Option.Do(
($) {
final market = $(goToShoppingCenter().alt(goToLocalMarket));
final amount = $(market.buyAmount());
final banana = $(market.buyBanana());
final apple = $(market.buyApple());
final pear = $(market.buyPear());
return 'Shopping: $banana, $apple, $pear';
},
).getOrElse(
() => 'I did not find đ or đ or đ, so I did not buy anything đ¤ˇââď¸',
);
You initialize the Do notation using the Do()
constructor.
You have access to a $
function, that you can use to extract and use the value inside each Option
, without using flatMap
.
Note: We recommend using the Do notation whenever possible to improve the legibility of your code đ¤
â ď¸ Warning: Pay attention to avoid the following mistakes when using the Do notation:
- Do not
throw
inside theDo()
constructor- Do not
await
without executing the$
function- Do not use a nested
Do()
constructor inside another one- Do not call the
$
function inside another callback in theDo()
constructorUsing the Do notation in these cases may fail at runtime or may produce unexpected behavior. For more information take a look at the
Do Notation
discussion or at this issue. Examples for each of the pitfalls can be viewed here: do-constructor-pitfalls
If you are going to use
fpdart
in your project, make sure to use immutable collections as well âď¸
Immutability is at the core of functional programming (alongside pure functions đ¤).
fpdart
does not provide immutable collections. Instead, we strongly suggest to use the fast_immutable_collections
package.
fast_immutable_collections
provides all the necessary immutable collections (List
, Set
, Map
and more) with an extensive API and top-class performance.
fpdart
instead provides some extension methods on Iterable
, List
, and Map
. These are designed to extend the native Dart API with immutable methods, as well as providing many handy additional functions:
/// Dart: `1`
[1, 2, 3, 4].first;
/// fpdart: `Some(1)`
[1, 2, 3, 4].head;
/// Dart: Throws a [StateError] â ď¸
[].first;
/// fpdart: `None()`
[].head;
final map = {'a': 1, 'b': 2};
/// Dart: mutable â ď¸
map.updateAll((key, value) => value + 10);
/// fpdart: immutable equivalent đ¤
final newMap = map.mapValue((value) => value + 10);
You can use these extension methods on any native Dart collection and also in combination with fast_immutable_collections
immutable collections.
Many more examples are coming soon. Check out my website and my Twitter for daily updates.
Option
Either
Unit
IO
IORef
IOOption
IOEither
Task
TaskOption
TaskEither
Reader
ReaderTask
ReaderTaskEither
State
StateAsync
Iterable
(List
) extension
Map
extension
Predicate
ReaderTaskOption
ReaderIO
ReaderIOEither
ReaderIOOption
ReaderEither
ReaderOption
StateReaderTaskEither
Lens
Writer
đ Read the full story of the origin and motivation for fpdart
Functional programming is becoming more and more popular, and for good reasons.
Many non-functional languages are slowly adopting patterns from functional languages, dart included. Dart already supports higher-order functions, generic types, type inference. Since Dart 3, the language supports also pattern matching, destructuring, multiple return values (Read more about these new features here).
Other functional programming features are coming to the language, like higher-order types.
Many packages are bringing functional patterns to dart, like the amazing freezed for unions/pattern matching.
fpdart aims to provide all the main types found in functional languages to dart. Types like Option
(handle missing values without null
), Either
(handle errors and error messages), Task
(composable async computations), and more.
Differently from many other functional programming packages, fpdart
aims to introduce functional programming to every developer. For this reason, every type and method is commented and documented directly in the code.
You do not need to have any previous experience with functional programming to start using
fpdart
.
fpdart also provides real-world examples of why a type is useful and how it can be used in your application. Check out my website for blog posts and articles.
dartz
One of the major pain points of dartz has always been is lack of documentation. This is a huge issue for people new to functional programming to attempt using the package.
dartz
was released in 2016, initially targeting Dart 1.
dartz
is also missing some features and types (Reader
, TaskEither
, and others).
fpdart
is a rewrite based on fp-ts and cats. The main differences are:
ISet
, IMap
, IHashMap
, AVLTree
).Being documentation and stability important goals of the package, every type will go through an implementation-documentation-testing cycle before being considered as 'stable'.
The current work on fpdart
aims to:
Note: There is also an experimental research in progress to implement
ZIO
infpdart
, stay tuned đ
The long-term goal is to provide all the main types and typeclasses available in other functional programming languages and packages. All the types should be completely documented and fully tested.
A well explained documentation is the key for the long-term success of the project. Any article, blog post, or contribution is welcome.
In general, any contribution or feedback is welcome (and encouraged!).
If you are interested in my work you can subscribe to my newsletter.
I share tutorials, guides, and code snippets about fpdart
and functional programming: Subscribe to the Newsletter here đ§
For more frequent updates you can also follow me on my Twitter.
MIT License, see the LICENSE.md file for details.