effect icon indicating copy to clipboard operation
effect copied to clipboard

Stream.filterMapEffect but accepting Effect<Option<A>> instead of Option<Effect<A>>

Open Schniz opened this issue 1 year ago • 2 comments

What is the problem this feature would solve?

Stream.filterMapEffect is unique to the rest of the Stream.*Effect functions: it expects an effect wrapped in another data structure instead of an effect containing a data structure: (a: A) => Option<Effect<B>>

or in other words, if you want to filterMapEffect you can't filter map based on an effect value, but instead you can choose whether to return an effect or not

Let's say we want to map a stream of numbers into their square roots:

declare const sqrt = (number: number) => Effect.Effect<Option<number>>;

const numbers = Stream.fromIterable([-1,2,3])

// What we can do right now
const squareRoots_ = numbers.pipe(
  Stream.mapEffect(number => sqrt(number)),
  Stream.filterMap(opt => opt)
);

// With this filterMapEffectX (name tbd):
const squareRoots = numbers.pipe(
  Stream.filterMapEffectX(number => sqrt(number))
)

What is the feature you are proposing to solve the problem?

it seems counter intuitive that this is the signature IMO, and I know that we probably can't break this API as Effect 3 is stable -- so we'll have to think of a different way to call it.

This is obviously can be done in userland as I mentioned previously, which is how I implemented it on my projects:

import { Function, Stream, Effect, Option } from 'effect';

export const filterMapEffect = Function.dual<
  <A, A2, E2, R2>(
    fn: (a: A) => Effect.Effect<Option.Option<A2>, E2, R2>,
  ) => <E, R>(
    stream: Stream.Stream<A, E, R>,
  ) => Stream.Stream<A2, E2 | E, R2 | R>,
  <A, E, R, A2, E2, R2>(
    stream: Stream.Stream<A, E, R>,
    fn: (a: A) => Effect.Effect<Option.Option<A2>, E2, R2>,
  ) => Stream.Stream<A2, E2 | E, R2 | R>
>(2, (stream, fn) =>
  Stream.filterMap(Stream.mapEffect(stream, fn), Function.identity),
);

What alternatives have you considered?

The alternative is to simply keep that a userland implementation. But I think this API makes sense in the "standard library".

Schniz avatar Jun 19 '24 16:06 Schniz