staltz / xstream

An extremely intuitive, small, and fast functional reactive stream library for JavaScript
http://staltz.github.io/xstream/
MIT License
2.37k stars 137 forks source link

Static Land style API #285

Open DylanRJohnston opened 5 years ago

DylanRJohnston commented 5 years ago

Hey guys, fp-ts@2.0.0 moved away from a method chaining API to a more static land style API which means it doesn't gel very well with xstream anymore. Would you be opposed to exporting all of operator methods as static functions curried in their stream argument? Right now I have a little helper file that re-exports methods as needed.

e.g.

export const map = <A, B>(f: (a: A) => B)) => ($: Stream<A>): Stream<B> => $.map(F)
DylanRJohnston commented 5 years ago

I'm not sure how to get this to work with operators that work on Streams and MemoryStreams. I see a few different options.

  1. Namespace the operators, something like mapMemory or maybe have them as seperate modules so they can imported as import xs from 'xstream/static' and import xsm from 'xstream/static/memory or something like that.
  2. Use the higher kinded types method developed by fp-ts, but it's pretty difficult to understand for new users and would probably tie these two libraries too close together (Maybe this belongs as a seperate package?)
  3. Use a conditional type for the return. This is in a sense copying the higher kinded type method from fp-ts, but for a known finite domain of Functors. Something like this
type AnyStream<A> = Stream<A> | MemoryStream<A>
type StreamOf<S extends AnyStream<unknown>, B>
  = S extends MemoryStream<unknown> ? MemoryStream<B>
  : S extends Stream<unknown> ? Stream<B>
  : unknown

export const map = <A, B>(f: (a: A) => B) => <S extends Stream<A>>($: S): StreamOf<S, B> => ($.map as any)(f)

I think 3 should work fine given Stream<A> is invariant in A.

DylanRJohnston commented 5 years ago

Just realised Typescript already has pattern matching for types, function overloads!

type AnyStream<A> = Stream<A> | MemoryStream<A>

interface StreamOperator<A, B = A> {
  ($: MemoryStream<A>): MemoryStream<B>
  ($: Stream<A>): Stream<B>
}

export const map = <A, B>(f: (a: A) => B): StreamOperator<A, B> => ($: AnyStream<A>) =>
  ($ as any).map(f)