Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better type safety possible for accumCombine? #65

Open
deklanw opened this issue Jun 8, 2019 · 1 comment
Open

Better type safety possible for accumCombine? #65

deklanw opened this issue Jun 8, 2019 · 1 comment

Comments

@deklanw
Copy link

deklanw commented Jun 8, 2019

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?

@paldepind
Copy link
Member

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 😄.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants