Skip to content

Commit

Permalink
scope methods in Effect
Browse files Browse the repository at this point in the history
  • Loading branch information
SandroMaglione committed Apr 4, 2024
1 parent 2f80240 commit f691c1e
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 32 deletions.
65 changes: 39 additions & 26 deletions packages/fpdart/lib/src/effect.dart
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,10 @@ final class Effect<E, L, R> extends IEffect<E, L, R> {
static Effect<E, L, Iterable<R>> all<E, L, R>(
Iterable<Effect<E, L, R>> iterable,
) =>
Effect.forEach(iterable, (a, _) => a);
Effect.forEach(
iterable,
(effect, _) => effect,
);

/// {@category zipping}
Effect<E, L, C> zipWith<B, C>(
Expand Down Expand Up @@ -283,22 +286,33 @@ final class Effect<E, L, R> extends IEffect<E, L, R> {
),
);

Effect<E, L, R> _provideEnvCloseScope(E env) =>
env is ScopeMixin && !env.scopeClosable
? Effect<E, L, R>.from(
(context) => _unsafeRun(context).then(
(exit) => switch (exit) {
Left(value: final value) => Left(value),
Right(value: final value) =>
env.closeScope<E, L>()._unsafeRun(context).then(
(exit) => switch (exit) {
Left(value: final value) => Left(value),
Right() => Right(value),
},
),
},
),
)
: this;

/// {@category context}
Effect<Null, L, R> provide(Context<E> context) {
final env = context.env;
final effect = env is ScopeMixin && !env.scopeClosable
? alwaysIgnore(env.closeScope())
: this;
return Effect.from((_) => effect._unsafeRun(context));
}
Effect<Null, L, R> provide(Context<E> context) => Effect.from(
(_) => _provideEnvCloseScope(context.env)._unsafeRun(context),
);

/// {@category context}
Effect<Null, L, R> provideEnv(E env) {
final effect = env is ScopeMixin && !env.scopeClosable
? alwaysIgnore(env.closeScope())
: this;
return Effect.from((_) => effect._unsafeRun(Context.env(env)));
}
Effect<Null, L, R> provideEnv(E env) => Effect.from(
(_) => _provideEnvCloseScope(env)._unsafeRun(Context.env(env)),
);

/// {@category context}
Effect<V, L, R> provideEffect<V>(Effect<V, L, E> effect) => Effect.from(
Expand Down Expand Up @@ -682,33 +696,32 @@ extension EffectWithScopeFinalizer<E extends ScopeMixin, L, R>
}

extension EffectNoScopeFinalizer<E, L, R> on Effect<E, L, R> {
/// {@category scoping}
Effect<Scope<E>, L, R> get withScope => Effect<Scope<E>, L, R>.from(
(ctx) => _unsafeRun(ctx.withEnv(ctx.env.env)),
);

/// {@category scoping}
Effect<Scope<E>, L, R> addFinalizer(Effect<Null, Never, Unit> release) =>
withScope.tapEnv(
Effect<Scope<E>, L, R>.from(
(context) => _unsafeRun(context.withEnv(context.env.env)),
).tapEnv(
(_, env) => env.addScopeFinalizer(release),
);

/// {@category scoping}
Effect<Scope<E>, L, R> acquireRelease(
Effect<Null, Never, Unit> Function(R r) release,
) =>
withScope.tapEnv(
(r, _) => _.addScopeFinalizer(release(r)),
Effect<Scope<E>, L, R>.from(
(context) => _unsafeRun(context.withEnv(context.env.env)),
).tapEnv(
(r, env) => env.addScopeFinalizer(
release(r),
),
);
}

extension EffectWithScope<E, L, R> on Effect<Scope<E>, L, R> {
/// {@category scoping}
/// {@category context}
Effect<E, L, R> get provideScope => Effect.from((context) {
final scope = Scope.withEnv(context.env);
return alwaysIgnore(scope.closeScope())._unsafeRun(
context.withEnv(scope),
);
return _provideEnvCloseScope(scope)._unsafeRun(context.withEnv(scope));
});
}

Expand Down
File renamed without changes.
83 changes: 83 additions & 0 deletions packages/fpdart/test/src/effect/effect_context_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import "package:fpdart/fpdart.dart";
import "package:test/test.dart";

import "../test_extension.dart";

class CustomError implements Exception {
const CustomError();
}

void main() {
group(
"Effect context",
() {
group('provideEnv', () {
test('handle throw in closing scope', () async {
final main = Effect<Null, String, int>.succeed(10)
.addFinalizer(Effect.succeedLazy(
() => throw const CustomError(),
))
.provideEnv(Scope.withEnv(null));

final result = await main.runFutureExit();

result.expectLeft((value) {
expect(value, isA<Die>());
if (value is Die) {
expect(value.error, isA<CustomError>());
}
});
});

test('handle failure in closing scope', () async {
final main = Effect<Null, String, int>.succeed(10)
.addFinalizer(Effect.die(const CustomError()))
.provideEnv(Scope.withEnv(null));

final result = await main.runFutureExit();

result.expectLeft((value) {
expect(value, isA<Die>());
if (value is Die) {
expect(value.error, isA<CustomError>());
}
});
});
});

group('provideScope', () {
test('handle throw in closing scope', () async {
final main = Effect<Null, String, int>.succeed(10)
.addFinalizer(Effect.succeedLazy(
() => throw const CustomError(),
))
.provideScope;

final result = await main.runFutureExit();

result.expectLeft((value) {
expect(value, isA<Die>());
if (value is Die) {
expect(value.error, isA<CustomError>());
}
});
});

test('handle failure in closing scope', () async {
final main = Effect<Null, String, int>.succeed(10)
.addFinalizer(Effect.die(const CustomError()))
.provideScope;

final result = await main.runFutureExit();

result.expectLeft((value) {
expect(value, isA<Die>());
if (value is Die) {
expect(value.error, isA<CustomError>());
}
});
});
});
},
);
}
12 changes: 6 additions & 6 deletions packages/fpdart/test/src/effect/effect_interruption_test.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import "package:fpdart/fpdart.dart";
import "package:test/test.dart";

import "../test_extension.dart";

void main() {
group(
"Effect interruption",
Expand All @@ -12,12 +14,10 @@ void main() {
);

final result = main.runSyncExit();
switch (result) {
case Right():
fail("Either expected to be Left: $result");
case Left(value: final value):
expect(value, isA<Interrupted>());
}

result.expectLeft((value) {
expect(value, isA<Interrupted>());
});
});
});
},
Expand Down
22 changes: 22 additions & 0 deletions packages/fpdart/test/src/test_extension.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:fpdart/fpdart.dart';
import 'package:test/test.dart';

extension EitherTest<L, R> on Either<L, R> {
void expectLeft(Function(L value) onLeft) {
switch (this) {
case Right(value: final value):
fail("Either expected to be Left, Right instead: $value");
case Left(value: final value):
onLeft(value);
}
}

void expectRight(Function(R value) onRight) {
switch (this) {
case Right(value: final value):
onRight(value);
case Left(value: final value):
fail("Either expected to be Right, Left instead: $value");
}
}
}

0 comments on commit f691c1e

Please sign in to comment.