Better type safety possible for accumCombine?
I noticed while looking at TodoMVC that the following isn't fully typesafe,
return accumCombine(
[
[prependItemS, (item, list) => [item].concat(list)],
[
removeKeyListS,
(keys, list) => list.filter((item) => !includes(itemToKey(item), keys))
]
],
initial
);
where prependItemS: Stream<A> and removeKeyListS: Stream<B[]>, but item: any and keys: any.
I looked at the types,
export declare function accumFrom<A, B>(f: (a: A, b: B) => B, initial: B, source: Stream<A>): Behavior<Behavior<B>>;
export declare function accum<A, B>(f: (a: A, b: B) => B, initial: B, source: Stream<A>): Now<Behavior<B>>;
export declare type AccumPair<A> = [Stream<any>, (a: any, b: A) => A];
export declare function accumCombineFrom<B>(pairs: AccumPair<B>[], initial: B): Behavior<Behavior<B>>;
export declare function accumCombine<B>(pairs: AccumPair<B>[], initial: B): Now<Behavior<B>>;
I see that if you do this
export declare type AccumPair<A, C> = [Stream<C>, (a: C, b: A) => A];
It won't work because C will get bound once to the first element of the first element of pairs.
Is rank-n polymorphism what is needed here? I've read about it a bit. Does TS support it?
I'm no expert but I think the problem is related to the array of pairs not having the same type. If all the pairs operated on a stream of the same type then the AccumPair that you propose
export declare type AccumPair<A, C> = [Stream<C>, (a: C, b: A) => A];
would actually work.
Maybe one could may it type safe in a language having both rank-n polymorphism and existential types. The type of accumCombine would then be:
accumCombine : forall a. Array (exists b. (Stream b, b -> a -> a)) -> Stream a
I'm not sure if it makes sense, but I'm saying that all the pairs have the type exists b. (Stream b, b -> a -> a). I.e. that there exists a type b such that the first element in pair has type Stream b and the second element has type b -> a -> a. That holds true for all the pairs so now they have the same type since the exists quantifier binds the varying type for each of the pairs.
We actually could make accumCombine type-safe in TypeScript up to some maxium number of elements in the array by overloading it. For instance, this overloaded type is type-safe for up to 3 arguments:
type AccumPair<A, R> = [Stream<B>, (a: B, b: R) => R];
function accumCombine<R, A>(pairs: [AccumPair<A, R>], initial: B): Now<Behavior<R>>;
function accumCombine<R, A, B>(pairs: [AccumPair<A, R>, AccumPair<B, R>], initial: B): Now<Behavior<R>>;
function accumCombine<R, A, B, C>(pairs: [AccumPair<A, R>, AccumPair<B, R>, AccumPair<C, R>], initial: B): Now<Behavior<R>>;
I've used that trick/hack every now and then. It does add type-safety but it also kinda sucks :smile:.