Slide 1

Slide 1 text

The joy of Functional Programming in Dart Senior Software Engineer at talabat, Delivery Hero Co-organizer of Flutter Abu Dhabi & Dubai gerfalcon GerfalconVogel Csongor Vogel csongorvogel

Slide 2

Slide 2 text

About me ● Senior Software Engineer at talabat ● Lecturer at Budapest University of Technology and Economics ● Co-organizer of Flutter Abu Dhabi & Dubai

Slide 3

Slide 3 text

Introduction to functional programming Functional programming in Dart Useful packages Conclusion 1 Agenda

Slide 4

Slide 4 text

Functional programming ● It’s a programming paradigm based on mathematical functions ● Main goals ○ Purity ○ Immutability ○ High-order functions ○ Declarative programming style f(x)

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Mathematical functions f(x) = 2x Inputs Outputs 1 -1 3 2 -2 6 x f(x)

Slide 7

Slide 7 text

Mathematical functions f(x) = x2 Inputs Outputs 1 -1 3 1 9 x f(x)

Slide 8

Slide 8 text

Mathematical functions f(x) Inputs Outputs 1 -1 3 1 9 x f(x) -2 ❌

Slide 9

Slide 9 text

Purity Always produce the same output for the same input and has no side effects.

Slide 10

Slide 10 text

Area of a circle 𝐴 = 𝜋𝑟! 𝑟

Slide 11

Slide 11 text

Area of a circle 𝐴 = 𝜋𝑟! void main() { List inputs = [1.0, 2.0, 3.0]; for (int i = 0; i < inputs.length; ++i) { double radius = inputs[i]; double area = areaOfCircle(radius); print(area); } PI = 3.14159; double areaOfCircle(double r) { return PI * r * r; } // Prints 3.14159, 12.56636, 28.27431

Slide 12

Slide 12 text

Area of a circle 𝐴 = 𝜋𝑟! void main() { List inputs = [1.0, 2.0, 3.0]; for (int i = 0; i < inputs.length; ++i) { double radius = inputs[i]; double area = areaOfCircle(radius); print(area); } var PI = 3.14159; double areaOfCircle(double r) { return PI * r * r; }

Slide 13

Slide 13 text

Area of a circle 𝐴 = 𝜋𝑟! Not pure function ❌ // Prints 3.14159, 12.56636, 28.27431 // 3.0, 12.0, 27.0 var PI = 3; double areaOfCircle(double r) { return PI * r * r; } void main() { List inputs = [1.0, 2.0, 3.0]; for (int i = 0; i < inputs.length; ++i) { double radius = inputs[i]; double area = areaOfCircle(radius); print(area); }

Slide 14

Slide 14 text

Immutability Data cannot be changed once created, reducing the chance of unexpected mutations and making code more predictable.

Slide 15

Slide 15 text

Area of a circle 𝐴 = 𝜋𝑟! var PI = 3; double areaOfCircle(double r) { return PI * r * r; } void main() { List inputs = [1.0, 2.0, 3.0]; for (int i = 0; i < inputs.length; ++i) { double radius = inputs[i]; double area = areaOfCircle(radius); print(area); }

Slide 16

Slide 16 text

Area of a circle 𝐴 = 𝜋𝑟! pure function ✅ const PI = 3.14159; double areaOfCircle(double r) { return PI * r * r; } void main() { List inputs = [1.0, 2.0, 3.0]; for (int i = 0; i < inputs.length; ++i) { final radius = inputs[i]; final area = areaOfCircle(radius); print(area); } // Prints 3.14159, 12.56636, 28.27431

Slide 17

Slide 17 text

Area of a circle 𝐴 = 𝜋𝑟! void main() { List inputs = [-1.0, 1.0, 3.0]; for (int i = 0; i < inputs.length; ++i) { final radius = inputs[i]; final area = areaOfCircle(radius); print(area); } const PI = 3.14159; double areaOfCircle(double r) { if (r < 0) { throw ArgumentError(”error msg"); } return PI * r * r; } Avoid side-effects ❌

Slide 18

Slide 18 text

Area of a circle 𝐴 = 𝜋𝑟! void main() { List inputs = [-1.0, 1.0, 3.0]; for (int i = 0; i < inputs.length; ++i) { final radius = inputs[i]; final area = areaOfCircle(radius); print(area); } const PI = 3.14159; double areaOfCircle(double r) { if (r < 0) { throw ArgumentError(”error msg"); } } return PI * r * r;

Slide 19

Slide 19 text

Area of a circle 𝐴 = 𝜋𝑟! void main() { List inputs = [-1.0, 1.0, 3.0]; for (int i = 0; i < inputs.length; ++i) { final radius = inputs[i]; final area = areaOfCircle(radius); print(area); } const PI = 3.14159; double? areaOfCircle(double r) { if (r < 0) { return null; } } return PI * r * r;

Slide 20

Slide 20 text

Area of a circle 𝐴 = 𝜋𝑟! const PI = 3.14159; double areaOfCircle(double r) { } return PI * r * r; void main() { List inputs = [-1.0, 1.0, 3.0]; for (int i = 0; i < inputs.length; ++i) { final radius = inputs[i]; final area = areaOfCircle(radius); print(area); }

Slide 21

Slide 21 text

Area of a circle 𝐴 = 𝜋𝑟! const PI = 3.14159; double areaOfCircle(double r) { } return PI * r * r; void main() { List inputs = [-1.0, 1.0, 3.0]; for (int i = 0; i < inputs.length; ++i) { final radius = inputs[i]; if (radius > 0) { final area = areaOfCircle(radius); print(area); } }

Slide 22

Slide 22 text

Imperative style void main() { List inputs = [-1.0, 1.0, 3.0]; for (int i = 0; i < inputs.length; ++i) { final radius = inputs[i]; if (radius > 0) { final area = areaOfCircle(radius); print(area); } }

Slide 23

Slide 23 text

Imperative style Declarative style void main() { List inputs = [-1.0, 1.0, 3.0]; inputs .where((input) => input > 0) .map((radius) => areaOfCircle(radius)) .forEach((area) => print(area)); } void main() { List inputs = [-1.0, 1.0, 3.0]; for (int i = 0; i < inputs.length; ++i) { final radius = inputs[i]; if (radius > 0) { final area = areaOfCircle(radius); print(area); } }

Slide 24

Slide 24 text

Declarative style void main() { List inputs = [-1.0, 1.0, 3.0]; inputs .skip(1) .where((input) => input > 0) .map((radius) => areaOfCircle(radius)) .map((area) => area.toStringAsFixed(2)) .toList() .take(2) .forEach((area) => print(area)); } // Prints 3.14, 28.26

Slide 25

Slide 25 text

Higher-order function Function can be passed as arguments to another function or be returned by another function.

Slide 26

Slide 26 text

Higher-order function double areaOfCircle(double radius) { return PI * r * r; } void main() { List inputs = [-1.0, 1.0, 3.0]; inputs .where((input) => input > 0) .map((radius) => areaOfCircle(radius)) .forEach((area) => print(area)); }

Slide 27

Slide 27 text

Higher-order function void areaOfCircle(double r, Function printFunction) { double area = PI * r * r; printFunction('Area of circle is: $area'); } void main() { List inputs = [-1.0, 1.0, 3.0]; inputs .where((input) => input > 0) .map((radius) => areaOfCircle(radius, print)) .toList(); }

Slide 28

Slide 28 text

Functional programming ● It’s a programming paradigm based on mathematical functions ● Contrast to Object-Oriented Programming (OOP), ○ mutable state ○ imperative programming style ● Main goals ○ Purity ○ Immutability ○ High-order functions ○ Declarative programming style f(x)

Slide 29

Slide 29 text

? Functional programming

Slide 30

Slide 30 text

Functional programming ?

Slide 31

Slide 31 text

2 Agenda Introduction to functional programming Functional programming in Dart Useful packages Conclusion

Slide 32

Slide 32 text

Functional programming in Dart

Slide 33

Slide 33 text

Functional programming in Dart ● final, const keywords ○ constants /// Declaring variables as final and const void main() { final String name = "John"; print(name); // Prints John //name = "David"; // compile-time error const int age = 29; print(age); // Output: 25 //age = 30; // compile-time error }

Slide 34

Slide 34 text

Functional programming in Dart ● final, const keywords ○ constants ○ lists void main() { final List numbers = [1, 2, 3]; //numbers = [4, 5, 6]; // compile-time error numbers.add(4); // This is allowed print(numbers); // Output: [1, 2, 3, 4] const List constNumbers = [1, 2, 3]; //constNumbers = [4, 5, 6]; // compile-time error //constNumbers.add(4); // compile-time error print(constNumbers); // Output: [1, 2, 3] }

Slide 35

Slide 35 text

Functional programming in Dart ● final, const keywords ○ constants ○ lists ○ classes class IPerson { final String name; final int age; IPerson(this.name, this.age); } void main() { var john = IPerson('John', 25); print(john.name); // Prints: John print(john.age); // Prints: 25 //john.name = 'David'; // compile-time error //john.age = 30; // compile-time error }

Slide 36

Slide 36 text

Functional programming in Dart ● final, const keywords ○ constants ○ lists ○ classes class IPerson { final String name; final int age; IPerson(this.name, this.age); IPerson copyWith({String? name, int? age}) { return IPerson( name ?? this.name, age ?? this.age, ); } } void main() { var john = IPerson('John', 25); print(john.name); // Prints: John print(john.age); // Prints: 25 var olderJohn = john.copyWith(age: 26); print(olderJohn.name); // Prints: John print(olderJohn.age); // Prints: 26 }

Slide 37

Slide 37 text

Functional programming in Dart ● final, const keywords ● Top-level functions void main() { myFunction(); } void myFunction() { /* ... */ }

Slide 38

Slide 38 text

Functional programming in Dart ● final, const keywords ● Top-level functions ● Anonymus functions ○ Lambda void main() { var greet = (name) => 'Hello, $name!’; print(greet('Dash')); // Hello, Dash! }

Slide 39

Slide 39 text

Functional programming in Dart ● final, const keywords ● Top-level functions ● Anonymus functions ○ Lambda ○ Closures void main() { var myCounter = counter(start: 0); print(myCounter()); // Prints 0 print(myCounter()); // Prints 1 } Function counter({required int start}) { return () => start++; }

Slide 40

Slide 40 text

Functional programming in Dart ● final, const keywords ● Top-level functions ● Anonymus functions ● Extensions extension ExtensionList on List { // get the last item of the list T get lastItem => this[length - 1]; // get a reversed copy of the list List get reversedCopy => this.reversed.toList(); // repeat the list n times List repeat(int times) => [for(int i = 0; i < times; i++) ...this]; } void main() { var list = [1, 2, 3, 4, 5]; print(list.lastItem); // 5 print(list.reversedCopy); // [5, 4, 3, 2, 1] print(list.repeat(2)); // [1, 2, 3, 4, 5, 1, 2, 3, 4, 5, 1, 2] }

Slide 41

Slide 41 text

Functional programming in Dart ● final, const keywords ● Top-level functions ● Anonymus functions ● Extensions ● Records ● Patterns Dart v3 🎉

Slide 42

Slide 42 text

Functional programming in Dart ● final, const keywords ● Top-level functions ● Anonymus functions ● Extensions ● Records ● Patterns Dart v3 🎉 const year = (name: 'Best year ever', date: 2023); final nextYear = (name: year.name, date: year.date + 1);

Slide 43

Slide 43 text

Functional programming in Dart ● final, const keywords ● Top-level functions ● Anonymus functions ● Extensions ● Records ● Patterns Dart v3 🎉 const year = (name: 'Best year ever', 2023); final nextYear = (name: year.name, year.$1 + 1);

Slide 44

Slide 44 text

(String, String) getName() { var firstName = 'John'; var lastName = 'Smith'; return (firstName, lastName); } void main() { final name = getName(); print('${name.$1} ${name.$2}'); // John Smith } Functional programming in Dart ● final, const keywords ● Top-level functions ● Anonymus functions ● Extensions ● Records ○ Multiple return values ● Patterns Dart v3 🎉

Slide 45

Slide 45 text

(String, String) getName() { var firstName = 'John'; var lastName = 'Smith'; return (firstName, lastName); } void main() { final name = getName(); print('${name.$1} ${name.$2}’); // John Smith // Destructuring final (firstName, lastName) = getName(); print('$lastName $firstName'); // Smith John 🇭🇺 } Functional programming in Dart ● final, const keywords ● Top-level functions ● Anonymus functions ● Extensions ● Records ○ Multiple return values ○ Destructuring ● Patterns Dart v3 🎉

Slide 46

Slide 46 text

Functional programming in Dart ● final, const keywords ● Top-level functions ● Anonymus functions ● Extensions ● Records ○ Multiple return values ○ Destructuring ● Patterns ○ Exhaustiveness Dart v3 🎉 String describeBools(bool b1, bool b2) => switch ((b1, b2)) { (true, true) => 'both true', (false, false) => 'both false', (true, false) => 'one of each', (false, true) => 'one of each', };

Slide 47

Slide 47 text

Functional programming in Dart ● final, const keywords ● Top-level functions ● Anonymus functions ● Extensions ● Records ○ Multiple return values ○ Destructuring ● Patterns ○ Exhaustiveness Dart v3 🎉 String describeBools(bool b1, bool b2) => switch ((b1, b2)) { (true, true) => 'both true', (false, false) => 'both false', (true, false) => 'one of each', // (false, true) => 'one of each’, compile-time error };

Slide 48

Slide 48 text

Functional programming in Dart ● final, const keywords ● Top-level functions ● Anonymus functions ● Extensions ● Records ○ Multiple return values ○ Destructuring ● Patterns ○ Exhaustiveness ○ Algebraic data type Dart v3 🎉 sealed class Shape {} class Square implements Shape { final double length; Square(this.length); } class Circle implements Shape { final double radius; Circle(this.radius); } double calculateArea(Shape shape) => switch (shape) { Square(length: var l) => l * l, Circle(radius: var r) => PI * r * r };

Slide 49

Slide 49 text

𝑟 getArea() areaOfCircle() Area of the delivery

Slide 50

Slide 50 text

Try-catch ● Server ● No Internet ● Authentication ● Parsing ● Unknown Future getArea() async { try { final uri = Uri.parse(Constants.getAreaApi); final response = await http.get(uri); switch (response.statusCode) { case 200: final data = json.decode(response.body); return AreaModel.fromMap(data); default: throw Exception(response.reasonPhrase); } } on ParsingException catch (_) { rethrow; } on TypeError catch (_) { rethrow; } on Exception catch (_) { rethrow; } catch (_) { rethrow; } }

Slide 51

Slide 51 text

Try-catch ● Server ● No Internet ● Authentication ● Parsing ● Unknown Future getArea() async { /// ...} void main() async { try { final result = await getArea(); } on Exception catch (_) { print(Exception while getting area’); } catch (e) { print('Unexpected error while getting area'); } }

Slide 52

Slide 52 text

Result sealed class Result { const Result(); } final class Success extends Result { const Success(this.value); final S value; } final class Failure extends Result { const Failure(this.exception); final E exception; }

Slide 53

Slide 53 text

Result Future getArea() async { try { final uri = Uri.parse(Constants.getAreaApi); final response = await http.get(uri); switch (response.statusCode) { case 200: final data = json.decode(response.body); return AreaModel.fromMap(data); default: throw Exception(response.reasonPhrase); } } on ParsingException catch (_) { rethrow; } on TypeError catch (_) { rethrow; } on Exception catch (_) { rethrow; } catch (_) { rethrow; } }

Slide 54

Slide 54 text

Result Future> getArea() async { try { final uri = Uri.parse(Constants.getAreaApi); final response = await http.get(uri); switch (response.statusCode) { case 200: final data = json.decode(response.body); return Success(AreaModel.fromMap(data)); default: throw Exception(response.reasonPhrase); } } on Exception catch (e) { return Failure(e); } on TypeError catch (error) { return Failure(Exception(error.toString())); } catch (error) { return Failure(Exception(error.toString())); } }

Slide 55

Slide 55 text

Try-catch void main() async { try { final result = await getArea(); print(result); } on Exception catch (_) { print(Exception while getting area’); } catch (e) { print('Unexpected error while getting area'); } } Future getArea() async {}

Slide 56

Slide 56 text

Result void main() async { final Result result = await getArea(); final String value = switch (result) { Success(value: final area) => area.toString(), Failure(exception: final exception) => exception.toString(), }; } Future> getArea() async {}

Slide 57

Slide 57 text

Next specification ○ getArea() ○ getRestaturantByAreaId() ○ getBestDealByRestaurantId()

Slide 58

Slide 58 text

Results 😱 Future> getBestDeal() async { final areaResult = await getArea(); if (areaResult case Success(value: final area)) { final restaurantResult = await getRestaurantByAreaId(area); return switch (restaurantResult) { Success(value: final restaurant) => await getBestDealByRestaurantId(restaurant.id), Failure(exception: final _) => Failure(Exception('')), }; } else { return Failure(Exception('Error while getting best deal')); } } Future> getArea() async {} Future> getRestaurantByAreaId(int) async {} Future> getBestDealByRestaurantId(int) async {}

Slide 59

Slide 59 text

Results 😱 Future> getBestDeal() async { final areaResult = await getArea(); if (areaResult case Success(value: final area)) { final restaurantResult = await getRestaurantByAreaId(area); return switch (restaurantResult) { Success(value: final restaurant) => await getBestDealByRestaurantId(restaurant.id), Failure(exception: final _) => Failure(Exception('')), }; } else { return Failure(Exception('Error while getting best deal')); } } Future> getArea() async {} Future> getRestaurantByAreaId(int) async {} Future> getBestDealByRestaurantId(int) async {}

Slide 60

Slide 60 text

3 Agenda Introduction to functional programming Functional programming in Dart Useful packages Conclusion

Slide 61

Slide 61 text

Useful packages ● functional programming ○ dartz ○ fpdart ● immutability ○ fast_immutable_collections ○ kt_dart ○ built_collection ○ equatable ○ freezed ● before Dart 3 ○ tuple ○ sealed_unions ○ multiple_result

Slide 62

Slide 62 text

fpdart ● Well-documented ● Extensions ● Types ○ Unit ○ Option ○ Either ○ Task ■ TaskOption ■ TaskEither ○ State ■ StateAsync ○ IO ■ IOOption ■ IOEither ■ IORef ○ Reader Sandro Maglione

Slide 63

Slide 63 text

Either /// Create an instance of [Right] final right = Either.of(10); /// Create an instance of [Left] final left = Either.left('none'); /// 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)'), ); ● Error handling ● Try-catch constructor ● Extraction ● Chaining ● Pattern matching

Slide 64

Slide 64 text

Result Future> getArea() async { try { final uri = Uri.parse(Constants.getAreaApi); final response = await http.get(uri); switch (response.statusCode) { case 200: final data = json.decode(response.body); return Success(AreaModel.fromMap(data)); default: throw Exception(response.reasonPhrase); } } on Exception catch (e) { return Failure(e); } on TypeError catch (error) { return Failure(Exception(error.toString())); } catch (error) { return Failure(Exception(error.toString())); } }

Slide 65

Slide 65 text

Either Future> getArea() async { try { final uri = Uri.parse(Constants.getAreaApi); final response = await http.get(uri); switch (response.statusCode) { case 200: final data = json.decode(response.body); return Either.right(AreaModel.fromMap(data)); default: throw Exception(response.reasonPhrase); } } on ParsingException catch (e) { return Either.left(e.toString()); } on Exception catch (e) { return Either.left(e.toString()); } on TypeError catch (error) { return Either.left(error.toString()); } catch (error) { return Either.left(error.toString()); } }

Slide 66

Slide 66 text

Result void main() async { final result = await getArea(); final String value = switch (result) { Success(value: final area) => area.toString(), Failure(exception: final exception) => exception.toString(), }; } Future> getArea() async {}

Slide 67

Slide 67 text

Either Future getArea() async {} void main() async { final result = await getArea(); /// Pattern matching result.match( (String error) => print(error), (AreaModel area) => print('$area'), ); }

Slide 68

Slide 68 text

Task /// Create instance of [Task] from a value final Task 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 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)); ● Wrapper around Future ● lazy evaluation ● Composing async functions

Slide 69

Slide 69 text

TaskEither TaskEither getAreaTaskEither() => TaskEither.tryCatch(() async { final uri = Uri.parse(Constants.getAreaApi); final response = await http.get(uri); switch (response.statusCode) { case 200: final data = json.decode(response.body); return AreaModel.fromMap(data); default: throw Exception(response.reasonPhrase); } }, (e, s) => '$e');

Slide 70

Slide 70 text

Result Future> getArea() async {} void main() async { final result = await getArea(); /// Pattern matching result.match( (l) => print(l), (r) => print(r), ); }

Slide 71

Slide 71 text

TaskEither TaskEither getArea() async {} void main() async { final task = getAreaTaskEither(); final Either result = await task.run(); /// Pattern matching result.match( (l) => print(l), (r) => print(r), ); }

Slide 72

Slide 72 text

Results Future> getBestDeal() async { final areaResult = await getArea(); if (areaResult case Success(value: final area)) { final restaurantResult = await getRestaurantByAreaId(area); return switch (restaurantResult) { Success(value: final restaurant) => await getBestDealByRestaurantId(restaurant.id), Failure(exception: final _) => Failure(Exception('')), }; } else { return Failure(Exception('Error while getting best deal')); } } Future> getArea() async {} Future> getRestaurantByAreaId(int) async {} Future> getBestDealByRestaurantId(int) async {}

Slide 73

Slide 73 text

TaskEither void main() async { final task = getAreaTaskEither() .flatMap((area) => getRestaurantByAreaIdTaskEither(area.id)) .flatMap((restaurant) => getBestDealByRestaurantId(restaurant.id)); final Either result = await task.run(); result.fold( (l) => print(l), (r) => print(r), ); } TaskEither getArea() {} TaskEither getRestaurantByAreaId(int areaId) {} TaskEither getBestDealByRestaurantId(int id) {}

Slide 74

Slide 74 text

4 Agenda Introduction to functional programming Functional programming in Dart Useful packages Conclusion

Slide 75

Slide 75 text

Conclusion ● Understand the key concepts of functional programming ● Be aware about the Dart features ● Start small ○ Focus one aspects of the functional programming ○ Adopt it together with your colleagues

Slide 76

Slide 76 text

Resources ● https://fsharpforfunandprofit.com/ by Scott Wlaschin ● https://github.com/spebbe/dartz by Björn Sperber ● https://github.com/SandroMaglione/fpdart by Sandro Maglione ● https://www.sandromaglione.com/ by Sandro Maglione ● https://codewithandrea.com/articles/functional-error-handling-either-fpdart/ by Andrea Bizotto ● https://github.com/marcglasberg/fast_immutable_collections by Marcelo Glasberg and Philippe Fanaro ● https://www.droidcon.com/2022/11/15/getting-started-with-functional-programming/ by Pascal Welsch ● https://resocoder.com/2019/12/14/functional-error-handling-in-flutter-dart-2-either-task- fp/ by Matt Rešetár (Reso Coder)

Slide 77

Slide 77 text

Resources https://github.com/gerfalcon/fp_playground

Slide 78

Slide 78 text

Senior Software Engineer at talabat, Delivery Hero Co-organizer of Flutter Abu Dhabi & Dubai gerfalcon GerfalconVogel Csongor Vogel csongorvogel Thank you