Skip to content

Commit

Permalink
IOEither and logger example
Browse files Browse the repository at this point in the history
  • Loading branch information
SandroMaglione committed Jun 29, 2021
1 parent 97fcba7 commit 4db2610
Show file tree
Hide file tree
Showing 9 changed files with 728 additions and 12 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

- Released Part 1 of [**Fpdart, Functional Programming in Dart and Flutter**](https://www.sandromaglione.com/fpdart-functional-programming-in-dart-and-flutter/)
- Added functional extension methods on `Iterable` (`List`)
- Completed `IOEither` type implementation, documentation, and testing
- Added `constF` function
- Added `option` and `optionOf` (same as dartz)
- Added `Either.right(r)` factory constructor to `Either` class (same as `Either.of(r)`) ([#3](https://github.com/SandroMaglione/fpdart/issues/3))
Expand Down
19 changes: 9 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,14 @@ Would you like to know more about functional programming, fpdart, and how to use
- [x] `Tuple`
- [x] `IO`
- [x] `Iterable` (`List`) `extension`
- [x] `IOEither`
- [ ] `TaskOption`
- [ ] `IOEither`
- [ ] `ReaderEither`
- [ ] `ReaderTask`
- [ ] `ReaderTaskEither`
- [ ] `StateReaderTaskEither`
- [ ] `Lens`
- [ ] `Writer`
- ~~`IList`~~

## 💻 Installation

Expand Down Expand Up @@ -228,11 +227,11 @@ The roadmap for types development is highlighted below (breaking changes to _'st
- ~~Implementation~~
- ~~Documentation~~
- ~~Testing~~
10. `TaskOption`
- Implementation
- Documentation
- Testing
11. `IOEither`
10. ~~`IOEither`~~
- ~~Implementation~~
- ~~Documentation~~
- ~~Testing~~
11. `TaskOption`
- Implementation
- Documentation
- Testing
Expand Down Expand Up @@ -261,13 +260,13 @@ The roadmap for types development is highlighted below (breaking changes to _'st
- Documentation
- Testing

**Note**: Integrations with immutable collections (`IList`, `ISet`, `IMap`, etc.) will be developed after the main functional programming types above are complete (as _'main'_ types I consider all until `IOEither`).
**Note**: Integrations for immutable collections (`IList`, `ISet`, `IMap`, etc.) are still being discussed with the community. `fpdart` does not want to be another immutable collection solution in the ecosystem. That is why we are working to integrate `fpdart` with other more mature packages that already implements immutable collections. 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.
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!).
In general, **any contribution or feedback is welcome** (and encouraged!).

## 📃 Versioning

Expand Down
78 changes: 78 additions & 0 deletions example/from_imperative_to_functional/logger/logger.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
enum Level {
verbose,
debug,
info,
warning,
error,
wtf,
nothing,
}

class LogEvent {
final Level level;
final dynamic message;
final dynamic error;
final StackTrace? stackTrace;

LogEvent(this.level, this.message, this.error, this.stackTrace);
}

class OutputEvent {
final Level level;
final List<String> lines;

OutputEvent(this.level, this.lines);
}

abstract class LogFilter {
bool shouldLog(LogEvent logEvent);
}

abstract class LogPrinter {
List<String> log(LogEvent logEvent);
}

abstract class LogOutput {
void output(OutputEvent outputEvent);
}

class Logger {
static Level level = Level.verbose;
bool _active = true;
final LogFilter _filter;
final LogPrinter _printer;
final LogOutput _output;

Logger(this._filter, this._printer, this._output);

/// Log a message with [level].
void log(
Level level,
dynamic message, [
dynamic error,
StackTrace? stackTrace,
]) {
if (!_active) {
throw ArgumentError('Logger has already been closed.');
} else if (error != null && error is StackTrace) {
throw ArgumentError('Error parameter cannot take a StackTrace!');
} else if (level == Level.nothing) {
throw ArgumentError('Log events cannot have Level.nothing');
}
var logEvent = LogEvent(level, message, error, stackTrace);

if (_filter.shouldLog(logEvent)) {
var output = _printer.log(logEvent);

if (output.isNotEmpty) {
var outputEvent = OutputEvent(level, output);
try {
_output.output(outputEvent);
} catch (e, s) {
print(e);
print(s);
}
}
}
}
}
121 changes: 121 additions & 0 deletions example/from_imperative_to_functional/logger/main.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/// Convert `log` function from `logger` package
/// from Imperative to Functional code using `fpdart`
///
/// Repository: https://github.com/leisim/logger
import 'package:fpdart/fpdart.dart';
import 'logger.dart';

class Logger {
static Level level = Level.verbose;
bool _active = true;
final LogFilter _filter;
final LogPrinter _printer;
final LogOutput _output;
Logger(this._filter, this._printer, this._output);

/// Imperative (not-functional) code
///
/// From https://github.com/leisim/logger/blob/6832ee0f5c430321f6a74dce99338b242861161d/lib/src/logger.dart#L104
void log(
Level level,
dynamic message, [
dynamic error,
StackTrace? stackTrace,
]) {
if (!_active) {
throw ArgumentError('Logger has already been closed.');
} else if (error != null && error is StackTrace) {
throw ArgumentError('Error parameter cannot take a StackTrace!');
} else if (level == Level.nothing) {
throw ArgumentError('Log events cannot have Level.nothing');
}
var logEvent = LogEvent(level, message, error, stackTrace);

if (_filter.shouldLog(logEvent)) {
var output = _printer.log(logEvent);

if (output.isNotEmpty) {
var outputEvent = OutputEvent(level, output);
try {
_output.output(outputEvent);
} catch (e, s) {
print(e);
print(s);
}
}
}
}

/// Functional approach 💪
/// ----------------------------------------------------------------
/// Use [IOEither] to handle errors and avoid throwing expections 🔨
///
/// Use [Unit] instead of `void` to represent a function that returns nothing 🎭
IOEither<String, Unit> logFunctional({
required Level level,
required dynamic message,
required dynamic error,
StackTrace? stackTrace,

/// Add all external dependencies as input to make the function pure 🥼
required bool active,
required LogFilter filter,
required LogPrinter printer,
required LogOutput output,
}) {
/// Handle errors using [Either] instead of throwing errors 💥
if (!active) {
return IOEither.left('Logger has already been closed.');
} else if (error != null && error is StackTrace) {
return IOEither.left('Error parameter cannot take a StackTrace!');
} else if (level == Level.nothing) {
return IOEither.left('Log events cannot have Level.nothing');
}

/// Declare all the variables as `const` or `final` 🧱
final logEvent = LogEvent(level, message, error, stackTrace);

/// Make sure to handle all the cases using [Option] 🎉
///
/// Use the `identity` function to return the input parameter as it is
final shouldLogOption = Option.fromPredicate(
filter.shouldLog(logEvent),
identity,
);

/// Using [Option], you must specify both `true` and `false` cases 🌎
return shouldLogOption.match(
/// Use another [Option] to evaluate `printer.log`
(_) => Option<List<String>>.fromPredicate(
printer.log(logEvent),
(v) => v.isNotEmpty,
).match(
(lines) {
/// All variables are `final` 🧱
final outputEvent = OutputEvent(level, lines);
return IOEither<String, Unit>.tryCatch(
() {
output.output(outputEvent);

/// Return [Unit] 🎁
return unit;
},
(e, s) {
/// Return an error message 🔨
///
/// Do not `print`, it would make the function impure! 🤯
return 'An error occurred: $e';
},
);
},

/// Simply return a [Unit] in all other cases 🎁
() => IOEither.of(unit),
),

/// Simply return a [Unit] in all other cases 🎁
() => IOEither.of(unit),
);
}
}
1 change: 1 addition & 0 deletions lib/fpdart.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ export 'src/either.dart';
export 'src/function.dart';
export 'src/ilist.dart';
export 'src/io.dart';
export 'src/io_either.dart';
export 'src/list_extension.dart';
export 'src/option.dart';
export 'src/reader.dart';
Expand Down
5 changes: 4 additions & 1 deletion lib/src/io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ abstract class _IOHKT {}
/// `IO<A>` represents a **non-deterministic synchronous** computation that
/// can **cause side effects**, yields a value of type `A` and **never fails**.
///
/// If you want to represent a synchronous computation that may fail, see [IOEither] (coming soon).
/// If you want to represent a synchronous computation that may fail, see [IOEither].
class IO<A> extends HKT<_IOHKT, A> with Monad<_IOHKT, A> {
final A Function() _run;

Expand All @@ -16,6 +16,9 @@ class IO<A> extends HKT<_IOHKT, A> with Monad<_IOHKT, A> {
/// Flat a [IO] contained inside another [IO] to be a single [IO].
factory IO.flatten(IO<IO<A>> io) => io.flatMap(identity);

/// Build a [IO] that returns `a`.
factory IO.of(A a) => IO(() => a);

/// Used to chain multiple functions that return a [IO].
@override
IO<B> flatMap<B>(covariant IO<B> Function(A a) f) => IO(() => f(run()).run());
Expand Down
Loading

0 comments on commit 4db2610

Please sign in to comment.