Skip to content

Commit

Permalink
v1.0.0 (#124)
Browse files Browse the repository at this point in the history
  • Loading branch information
SandroMaglione authored Jul 26, 2023
2 parents 194e146 + 9e90b42 commit a4fad89
Show file tree
Hide file tree
Showing 116 changed files with 6,615 additions and 3,665 deletions.
66 changes: 66 additions & 0 deletions examples/hello_world/bin/fpdart_hello_world.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import 'package:fpdart/fpdart.dart';

void helloWorld(String message) {
print("Hello World: $message");
}

/// 1️⃣ Pure function (Thunk)
void Function() helloWorld1(String message) => () {
print("Hello World: $message");
};

/// A thunk with no error is called [IO] in `fpdart`
IO<void> helloWorld1Fpdart(String message) => IO(() {
print("Hello World: $message");
});

/// 2️⃣ Explicit error
/// Understand from the return type if and how the function may fail
Either<Never, void> Function() helloWorld2(String message) => () {
print("Hello World: $message");
return Either.of(null);
};

/// A thunk with explicit error [Either] is called [IOEither] in `fpdart`
IOEither<Never, void> helloWorld2Fpdart1(String message) => IOEither(() {
print("Hello World: $message");
return Either.of(null);
});

/// ...or using the `right` constructor
IOEither<Never, void> helloWorld2Fpdart2(String message) => IOEither.right(() {
print("Hello World: $message");
});

/// 3️⃣ Explicit dependency
/// Provide the `print` method as a dependency instead of implicit global function
abstract class Console {
void log(Object? object);
}

class ConsoleImpl implements Console {
@override
void log(Object? object) {
print(object);
}
}

Either<Never, void> Function() Function(Console) helloWorld3(String message) =>
(console) => () {
console.log("Hello World: $message");
return Either.of(null);
};

/// Thunk (async) + error + dependency is called [ReaderTaskEither] in `fpdart`
ReaderTaskEither<Console, Never, void> helloWorld3Fpdart(String message) =>
ReaderTaskEither((console) async {
console.log("Hello World: $message");
return Either.of(null);
});

void main(List<String> args) {
final definition = helloWorld3("Sandro");
final thunk = definition(ConsoleImpl());
thunk();
}
7 changes: 7 additions & 0 deletions examples/hello_world/bin/hello_world.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
void helloWorld(String message) {
print("Hello World: $message");
}

void main(List<String> args) {
helloWorld("Sandro");
}
19 changes: 19 additions & 0 deletions examples/hello_world/pubspec.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
name: fpdart_hello_world
publish_to: none
version: 0.1.0
homepage: https://www.sandromaglione.com/
repository: https://github.com/SandroMaglione/fpdart
description: Example of Functional programming in Dart and Flutter using fpdart. Write a simple "Hello World" using fpdart.
author: Maglione Sandro <lass.maglio@gmail.com>

environment:
sdk: ">=3.0.0 <4.0.0"

dependencies:
fpdart:
path: ../../packages/fpdart

dev_dependencies:
lint: ^2.1.2
test: ^1.24.3
mocktail: ^0.3.0
22 changes: 22 additions & 0 deletions examples/hello_world/test/fpdart_hello_world_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:mocktail/mocktail.dart';
import 'package:test/test.dart';

import '../bin/fpdart_hello_world.dart';

class ConsoleTest extends Mock implements Console {}

void main() {
group('helloWorld3Fpdart', () {
test(
'should call "log" from the "Console" dependency with the correct input',
() async {
final console = ConsoleTest();
const input = "test";

when(() => console.log(any)).thenReturn(null);
await helloWorld3Fpdart(input).run(console);
verify(() => console.log("Hello World: $input")).called(1);
},
);
});
}
13 changes: 13 additions & 0 deletions examples/hello_world/test/hello_world_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import 'package:test/test.dart';

void main() {
group('helloWorld', () {
/// `'should call "print" with the correct input'`
///
/// This is difficult to test, since `print` is an implicit dependency 🙌
///
/// Furthermore, `print` will be executed at every test. Imagine having a
/// request to update a production database instead of `print` (both are side-effects),
/// you do not want to interact with a real database with tests ⚠️
});
}
18 changes: 6 additions & 12 deletions examples/managing_imports/README.md
Original file line number Diff line number Diff line change
@@ -1,19 +1,13 @@
# Managing Imports

Naming things is hard. Sometimes, the same name gets used for different things. In Dart, naming conflicts can be mitigated through the use of import prefixes, as well as show and hide operations. This is particularly important when using a package like `fpdart` that provides a lot of classes with common names.
Naming things is hard. Sometimes, the same name gets used for different things. In Dart, naming conflicts can be mitigated through the use of [import prefixes](https://dart.dev/language/libraries#specifying-a-library-prefix), as well as [show and hide operations](https://dart.dev/language/libraries#importing-only-part-of-a-library).

Suppose you decide to use `fpdart` with your Flutter program. You'll quickly discover that `fpdart` uses `State` as a class name, which conflicts with the `State` class in Flutter.
This is particularly important when using a package like `fpdart` that provides a lot of classes with common names.

That's problem 1.
As an example, suppose you decide to use `fpdart` with your Flutter program. You'll quickly discover that `fpdart` uses `State` as a class name, which conflicts with the `State` class in Flutter.

Now also suppose you also choose to use `fpdart`'s `Tuple2` class. That's a lot less likely to conflict with anything, but it's still possible. However, you also decide you need a `Tuple3`. `fpdart` doesn't have one. (And likely never will, thanks to the upcoming records feature.)
The solution is to create an import shim that solves both of these problems. We'll call it `functional.dart`. This shim will import `fpdart`, and re-export the classes we want to use. We can rename `fpdart`'s `State` to `FpState` to avoid the conflict. We can then import `functional.dart` instead of `fpdart`.

However, you found one in the [tuple](https://pub.dev/packages/tuple) package, along with `Tuple4` and `Tuple5`, even though it doesn't have much more than element accessors. Close enough for your application.
`functional.dart` can also hold any other functional programming utilities we want to use. It can also be used to provide or import class extensions and mapping functions between our types and the functional types.

But now, you decide to import `tuple` as well, and you get a naming conflict for `Tuple2`.

That's problem 2.

The solution is to create an import shim that solves both of these problems. We'll call it `functional.dart`. This shim will import `fpdart` and `tuple`, and re-export the classes we want to use. And, we can rename `fpdart`'s `State` to `FpState` to avoid the conflict. We can then import `functional.dart` instead of `fpdart` and `tuple`.

`functional.dart` can also hold any other functional programming utilities we want to use. It can also be used to provide or import class extensions and mapping functions between our types and the functional types. A one-stop shop for functional programming in Dart!
A one-stop shop for functional programming in Dart!
27 changes: 6 additions & 21 deletions examples/managing_imports/bin/managing_imports.dart
Original file line number Diff line number Diff line change
@@ -1,32 +1,17 @@
import 'package:managing_imports/functional.dart';
import 'package:test/test.dart';

void main(List<String> arguments) {
// borrow the flatMap test from state_test.dart
void main() {
/// Borrow the `flatMap` test from `state_test.dart`
test('flatMap', () {
final state = FpState<List<int>, int>((s) => Tuple2(s.first, s.sublist(1)));
final state = FpState<List<int>, int>((s) => (s.first, s.sublist(1)));
final ap = state.flatMap<double>(
(a) => FpState(
(s) => Tuple2(a / 2, s.sublist(1)),
(s) => (a / 2, s.sublist(1)),
),
);
final result = ap.run([1, 2, 3, 4, 5]);
expect(result.first, 0.5);
expect(result.second, [3, 4, 5]);
});

test('Tuple2', () {
const tuple = Tuple2(1, 2);
// this is the item access syntax for the fpdart package
expect(tuple.first, 1);
expect(tuple.second, 2);
});

test('Tuple3', () {
const tuple = Tuple3(1, 2, 3);
// this is the item access syntax for the tuple package
expect(tuple.item1, 1);
expect(tuple.item2, 2);
expect(tuple.item3, 3);
expect(result.$1, 0.5);
expect(result.$2, [3, 4, 5]);
});
}
3 changes: 0 additions & 3 deletions examples/managing_imports/lib/functional.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,6 @@ import 'package:fpdart/fpdart.dart' as fpdart show State;

// The `fpdart` library is used to create functional programming constructs.
export 'package:fpdart/fpdart.dart' hide State;
// The `tuple` library is used to create tuples, which are immutable lists of
// fixed length.
export 'package:tuple/tuple.dart' show Tuple3, Tuple4, Tuple5, Tuple6, Tuple7;

/// A type alias for the `State` class from the `fpdart` library.
typedef FpState<S, A> = fpdart.State<S, A>;
3 changes: 1 addition & 2 deletions examples/managing_imports/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,12 @@ version: 1.0.0
publish_to: none

environment:
sdk: ">=2.18.6 <3.0.0"
sdk: ">=3.0.0 <4.0.0"

dependencies:
fpdart:
path: ../../packages/fpdart
test:
tuple:
very_good_analysis:

dev_dependencies:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,44 +29,44 @@ class OpenMeteoApiClientFpdart {
),
LocationHttpRequestFpdartFailure.new,
).chainEither(
(response) => Either.Do(($) {
final body = $(
(response) => Either.Do((_) {
final body = _(
_validResponseBody(response, LocationRequestFpdartFailure.new),
);

final json = $(
final json = _(
Either.tryCatch(
() => jsonDecode(body),
(_, __) => LocationInvalidJsonDecodeFpdartFailure(body),
),
);

final data = $(
final data = _(
Either<OpenMeteoApiFpdartLocationFailure,
Map<dynamic, dynamic>>.safeCast(
json,
LocationInvalidMapFpdartFailure.new,
),
);

final currentWeather = $(
final currentWeather = _(
data
.lookup('results')
.toEither(LocationKeyNotFoundFpdartFailure.new),
);

final results = $(
final results = _(
Either<OpenMeteoApiFpdartLocationFailure, List<dynamic>>.safeCast(
currentWeather,
LocationInvalidListFpdartFailure.new,
),
);

final weather = $(
final weather = _(
results.head.toEither(LocationDataNotFoundFpdartFailure.new),
);

return $(
return _(
Either.tryCatch(
() => Location.fromJson(weather as Map<String, dynamic>),
LocationFormattingFpdartFailure.new,
Expand Down
12 changes: 6 additions & 6 deletions examples/pokeapi_functional/lib/api/fetch_pokemon.dart
Original file line number Diff line number Diff line change
Expand Up @@ -51,17 +51,17 @@ TaskEither<String, Pokemon> fetchPokemon(int pokemonId) => TaskEither.tryCatch(
///
/// All the functions are simply chained together following the principle of composability.
TaskEither<String, Pokemon> fetchPokemonFromUserInput(String pokemonId) =>
TaskEither.Do(($) async {
final validPokemonId = await $(_validateUserPokemonId(
TaskEither.Do((_) async {
final validPokemonId = await _(_validateUserPokemonId(
pokemonId,
).toTaskEither());
return $(fetchPokemon(validPokemonId));
return _(fetchPokemon(validPokemonId));
});

TaskEither<String, Pokemon> fetchRandomPokemon = TaskEither.Do(($) async {
final pokemonId = await $(randomInt(
TaskEither<String, Pokemon> fetchRandomPokemon = TaskEither.Do((_) async {
final pokemonId = await _(randomInt(
Constants.minimumPokemonId,
Constants.maximumPokemonId + 1,
).toTaskEither());
return $(fetchPokemon(pokemonId));
return _(fetchPokemon(pokemonId));
});
16 changes: 8 additions & 8 deletions examples/read_write_file/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import 'package:fpdart/fpdart.dart';
* Read lines from a `.txt` file using [TaskEither] of fpdart.
*
* This application reads from two files containing english and italian sentences.
* It then uses `zip` to join the two resulting lists together in a `List<Tuple2<String, String>>`.
* It then uses `zip` to join the two resulting lists together in a `List<(String, String)>`.
*
* Finally, it uses `flatMap` and `foldLeft` to iterate over the sentences and search words from a predefined list (`searchWords`).
* At the end, we have a list of [FoundWord] containing all the sentences and words matched.
Expand All @@ -32,21 +32,21 @@ class FoundWord {
const searchWords = ['that', 'and', 'for'];

Iterable<FoundWord> collectFoundWords(
Iterable<Tuple2<String, String>> iterable,
Iterable<(String, String)> iterable,
) =>
iterable.flatMapWithIndex(
(tuple, index) => searchWords.foldLeftWithIndex<List<FoundWord>>(
[],
(acc, word, wordIndex) =>
tuple.second.toLowerCase().split(' ').contains(word)
tuple.$2.toLowerCase().split(' ').contains(word)
? [
...acc,
FoundWord(
index,
word,
wordIndex,
tuple.second.replaceAll(word, '<\$>'),
tuple.first,
tuple.$2.replaceAll(word, '<\$>'),
tuple.$1,
),
]
: acc,
Expand All @@ -55,9 +55,9 @@ Iterable<FoundWord> collectFoundWords(

void main() async {
final collectDoNotation = TaskEither<String, Iterable<FoundWord>>.Do(
($) async {
final linesIta = await $(readFileAsync('./assets/source_ita.txt'));
final linesEng = await $(readFileAsync('./assets/source_eng.txt'));
(_) async {
final linesIta = await _(readFileAsync('./assets/source_ita.txt'));
final linesEng = await _(readFileAsync('./assets/source_eng.txt'));
final linesZip = linesIta.zip(linesEng);
return collectFoundWords(linesZip);
},
Expand Down
2 changes: 1 addition & 1 deletion examples/read_write_file/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: Example of Functional programming in Dart and Flutter using fpdart.
author: Maglione Sandro <lass.maglio@gmail.com>

environment:
sdk: ">=2.13.0 <3.0.0"
sdk: ">=3.0.0 <4.0.0"

dependencies:
fpdart:
Expand Down
Loading

0 comments on commit a4fad89

Please sign in to comment.