ribs_effect 1.0.0-dev.5 copy "ribs_effect: ^1.0.0-dev.5" to clipboard
ribs_effect: ^1.0.0-dev.5 copied to clipboard

Functional effect system with IO monad, safe concurrent state (Ref), and resource management and pure synchronziation primitive (Deferred).

ribs_effect #

ribs_effect is a high-performance, purely functional effect system for Dart, heavily inspired by Cats Effect. It provides a robust foundation for building asynchronous and resource-safe applications.

At the core of the library is IO, a data type that describes an effect as a value. This allows you to treat side-effects (like network calls, file I/O, or console output) as referentially transparent values that can be composed, transformed and passed around before being executed.

Key Features #

  • Purely Functional: Side-effects are deferred until the "end of the world," making your code easier to reason about and test.
  • Fiber-based Concurrency: Lightweight fibers provide efficient concurrency that's easier to manage than raw Futures.
  • Interruption/Cancelation: Built-in support for safely canceling running tasks.
  • Resource Safety: Guaranteed cleanup of resources (like file handles or sockets) even in the face of errors or cancelation.
  • Rich Standard Library: Includes concurrent primitives like Ref, Deferred, Semaphore, and Queue.

Documentation #

For more detailed information, guides, and API documentation, visit the Full Ribs Documentation Site.

Use Cases #

Composing Effects #

Easily sequence and transform effects using standard functional combinators.

final program = IO.delay(() => 42)
  .map((n) => n * 2)
  .flatMap((result) => IO.print('The result is $result'));

await program.unsafeRunFuture();

Safe Resource Management #

Ensure resources are always released, no matter what happens during execution.

final result = IO.print('Opening file...').bracket(
  (file) => processFile(file), // use
  (file) => IO.print('Closing file...'), // release
);

The Resource Type #

While bracket is great for simple cases, Resource is a first-class data type that wraps acquisition and release logic. It's a tool for managing resources, allowing you to:

  • Compose multiple resources easily.
  • Ensure resources are released in the correct (reverse) order.
  • Avoid "callback hell" of nested bracket calls.
final server = Resource.make(
  IO.print('Starting server...').as('localhost:8080'),
  (addr) => IO.print('Stopping server at $addr...'),
);

final connection = Resource.make(
  IO.print('Opening connection...').as('conn_123'),
  (id) => IO.print('Closing connection $id...'),
);

// Compose resources to get both values
final app = server.product(connection);

// Both are acquired, provided to the block, then released in reverse order.
await app.use((data) {
  final (server, connection) = data;
  return IO.print('App at $server and connection $connection');
}).unsafeRunFuture();

Coordination with Ref and Deferred #

Synchronize and share state between concurrent fibers.

final coordination = IO.deferred<int>().flatMap((deferred) {
  return ilist([
    IO.sleep(const Duration(seconds: 1)).flatMap((_) => deferred.complete(42)),
    deferred.get().flatMap((value) => IO.print('Received: $value')),
  ]).parSequence();
});

Concurrent Execution #

Run multiple independent tasks concurrently and gather their results safely.

final tasks = ilist([
  fetchData(1),
  fetchData(2),
  fetchData(3),
]);

// Runs all tasks concurrently and returns an IList of results
final results = await tasks.parSequence().unsafeRunFuture();

Fiber Cancelation #

Fibers can be canceled to stop ongoing work. This is handled safely, ensuring that any attached finalizers are executed.

final program = IO.print('Working...').delayBy(Duration(seconds: 10))
  .onCancel(IO.print('Cleanup done!'))
  .start()
  .flatMap((fiber) => fiber.cancel());

await program.unsafeRunFuture();

Fine Grained Cancelation #

Use uncancelable to prevent an effect from being interrupted. You can use the provided Poll to create specific regions where cancelation is allowed again.

final safeOperation = IO.uncancelable((poll) =>
  IO.print('Acquired').flatMap((_) =>
    poll(IO.print('Interruptible work...'))
      .guarantee(IO.print('Released'))
  )
);

Testing with Ticker #

ribs_effect provides a Ticker runtime for deterministic testing of time-based effects without actually waiting for real time to pass.

import 'package:ribs_effect/test.dart';

test('delayed effect', () async {
  final io = IO.sleep(Duration(seconds: 1)).as('Done');
  
  final ticker = io.ticked;
  
  ticker.advance(Duration(milliseconds: 500));
  expect(await ticker.outcome.isCompleted, isFalse);
  
  ticker.advanceAndTick(Duration(milliseconds: 500));
  expect(await ticker.outcome, ioSucceeded('Done'));
});

IO Tracing #

Tracing provides a high-level view of your IO execution history, which is much easier to debug than raw Dart stack traces.

Enabling Tracing

Tracing must be enabled globally before running any effects:

IOTracingConfig.tracingEnabled = true;

Manual Tracing #

You can add labels to specific points in your program to make traces more informative:

final tracked = IO.pure(42).traced('my-trace-point');

When an error occurs, the resulting StackTrace will be an IOFiberTrace containing the history of executed effect labels.

Example #

Check out the full example program in example/example.dart.

2
likes
150
points
116
downloads

Documentation

API reference

Publisher

unverified uploader

Weekly Downloads

Functional effect system with IO monad, safe concurrent state (Ref), and resource management and pure synchronziation primitive (Deferred).

Homepage
Repository (GitHub)
View/report issues

Topics

#functional-programming #io #resource #concurrency

License

unknown (license)

Dependencies

async, ribs_core, web

More

Packages that depend on ribs_effect