mostjs / core

Most.js core event stream
http://mostcore.rtfd.io
MIT License
402 stars 36 forks source link

Add Fantasy Land support #239

Open gabejohnson opened 6 years ago

gabejohnson commented 6 years ago

Continuing the conversation from https://github.com/cujojs/most/pull/347

briancavalier commented 6 years ago

Hey @gabejohnson, thanks for opening this. I've been watching https://github.com/fantasyland/fantasy-land/pull/286 closely. It'd currently be much simpler for @most/core to implement static-land (it mostly already does, but of is named now) than fantasy-land 3.x, since @most/core streams don't share a common prototype (they only share an interface).

Hence my interest in https://github.com/fantasyland/fantasy-land/pull/286. We'll continue keeping an eye on it.

Frikki commented 3 years ago

And this https://github.com/fantasyland/fantasy-land/pull/315

semmel commented 5 months ago

I've toyed around with two approaches integrating Fantasyland support. Both are backwards compatible. I guess, I'd favour the separate package approach (A), since it's not invasive.

Benefits of not going for an add-on

Approach A) Provide a FL flavoured API in @most/core-fl by wrapping Stream with class FantasyLandStream <A>

Strawman PR

// FL-API.ts
import FantasyLandStream from 'FLDelegate'
import { periodic as _periodic, take as _take, … } from '../core/src/index'
import { compose, curry2, curry3 } from '@most/prelude'
export const fantasyLand = <A>(stream: Stream<A>) =>
  new FantasyLandStream(stream);

// Number -> FantasyLandStream ()
export const periodic = compose(fantasyLand, _periodic)

interface Tap {
  <A>(f: (a: A) => any, s: Stream<A>): FantasyLandStream<A>
  <A>(f: (a: A) => any): (s: Stream<A>) => FantasyLandStream<A>
}
export const tap: Tap = curry2((x, y) => fantasyLand(_tap(x, y)))
// …
// FLDelegate.ts
export class FantasyLandStream<A> implements Stream<A>, FunctorFantasyLand<A> {
  constructor(private readonly stream: Stream<A>) {}

  run(sink: Sink<A>, scheduler: Scheduler): Disposable {
    return this.stream.run(sink, scheduler)
  }

  ['fantasy-land/map']<B>(fn: (value: A) => B): FantasyLandStream<B> {
    return fantasyLand<B>(map(fn, this.stream))
  }
  //…
}

Approach B) Add FL support to @most/core by replacing Stream<A> with class FL<A> implements Stream<A>

// FL.ts
export abstract class FL<A> implements Stream<A> {
  ['fantasy-land/map']<B>(fn: (a: A) => B): FL<B> {
    return Map.create<A, B>(fn, this)
  }
  ['fantasy-land/empty'](): FL<never> {
    return new Empty()
  }
  // …

  _T?: A
  abstract run (sink: Sink<A>, scheduler: Scheduler): Disposable
}

class Map<A, B> extends FL<B> {
//…
// transform.ts
import { FL, Map } from 'FL'

export const map = <A, B>(f: (a: A) => B, stream: Stream<A>): FL<B> =>
  Map.create(f, stream)
// …