Open raveclassic opened 5 years ago
Can you please give an example of
type Sink<A> = Observable<A>
where effect is bundled into the stream
@igoralekseev Assuming we're using rxjs
:
const withEffect = <A>(source: Observable<A>, f: (a: A) => Observable<unknown>): Observable<A> =>
merge(
source,
pipe(
source,
switchMap(f),
ignoreElements(),
),
);
declare function save(data: string): Observable<void>;
declare const source: Observable<string>;
const data = withEffect(source, save);
@igoralekseev Or if we're creating an instance of effectful viewmodel (typical usage):
interface ViewModel {
a: Observable<string>;
b: Observable<string>;
remove(): void;
}
declare function remove(id: string): Observable<void>;
const createViewModel = (id: string): Observable<ViewModel> => {
const removeHandler = createHandler<void>();
const vm = {
a: of('123'),
b: of('456'),
remove: removeHandler.handle,
};
const removeEffect = pipe(
removeHandler.value$,
switchMap(() => remove(id)),
share(),
);
return withEffect(of(vm), () => removeEffect);
};
We could go even shorter with using Proxy for creating handlers:
const withEffect = <A>(source: Observable<A>, effect: Observable<unknown>): Observable<A> =>
merge(
source,
pipe(
source,
switchMapTo(effect),
ignoreElements(),
share(),
),
);
interface Handler<A> extends Observable<A> {
(a: A): void;
}
interface VoidHandler<A> extends Observable<A> {
(): void;
}
const functionKeys: PropertyKey[] = Object.getOwnPropertyNames(Object.getPrototypeOf(constVoid));
const createHandler = <A = never>(): A extends void ? VoidHandler<A> : Handler<A> => {
const s = new Subject<A>();
const shared = pipe(
s,
share(),
);
const next = (a: A) => s.next(a);
return new Proxy(next, {
get(target, key) {
return (functionKeys.includes(key) ? next : shared)[key];
},
}) as any;
};
interface ViewModel {
a: Observable<string>;
b: Observable<string>;
remove(): void;
}
declare function remoteRemove(id: string): Observable<void>;
const createViewModel = (id: string): Observable<ViewModel> => {
const remove = createHandler<void>();
const vm: ViewModel = {
a: of('123'),
b: of('456'),
remove,
};
const removeEffect = pipe(
remove,
switchMap(() => remoteRemove(id)),
);
return withEffect(of(vm), removeEffect);
};
Wow, createHandler
looks expressive and impressive. But I would say that having both Observable
and Function
at the same object (can I still call this thing an object
?) is not quite FP way of making things though 😅
Why not, it's just an object that implements both Observable
and Function
interfaces.
Anyway it's not related to Sink/Context
Historically
Sink
andContext
were a wrong abstraction and we should consider removing them in future versions of platform.Sink
exposes unsafe access to inner value when you can forget about subscribing to effect. On the other hand we could replacetype Sink<A> = { value: A, effect: Observable<unknown>> }
with just atype Sink<A> = Observable<A>
where effect is bundled into the stream and you don't have direct unsafe access to sink's value, only in subscriber.Keeping that in mind,
type Context<E, A>
becomes just aReader<E, Observable<A>>
and can be dropped as well.Still we have to deliver
LiveData
and operators and that's why we should think about #171