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

Does xstream have a similar concept to rx's pipeable operators? #243

Closed
a-laughlin opened this issue Apr 23, 2018 · 7 comments
Closed

Does xstream have a similar concept to rx's pipeable operators? #243

a-laughlin opened this issue Apr 23, 2018 · 7 comments

Comments

@a-laughlin
Copy link

a-laughlin commented Apr 23, 2018

https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md

I'm a huge fan of FP with pipe and compose (ramda, lodash/fp), so I like that rx has a pipe fn. I also like xstream's simplicity. Curious if xstream enables piping of some sort, sans the dot. Fns like .map and .filter make me a bit apprehensive. Patching prototypes for custom fns can get tricky, especially if the chaining hides internal mutation.

Compose seems like it might be similar to source$.pipe(fns...) in the rx link. Is it? And, if so, how easy would it be to reverse it for pipe's redability? e.g., const pipe = (...fns)=>compose(...fns.reverse());.

Related: This mention of pipe-style api seems like it might reference the same concept, but is the only issue with the word "pipe" in it.

Thanks!

@Widdershin
Copy link
Contributor

Not built in, but it wouldn't be hard to make a package like so:

const map = (...args) => (stream) => stream.map(...args);
const filter = (...args) => (stream) => stream.filter(...args);
const pipe = (stream, ...operators) => operators.reduce((s, f) => f(s), stream);

const result = pipe(
  xs.fromArray([1, 2, 3]),
  map(i => i * 2), 
  filter(i => i > 3)
)

If you wanted to match the rx api exactly you would do this:

xs.prototype.pipe = function (...operators) { return operators.reduce((s, f) => f(s), this) };

I'm sure there are quite a few people who would appreciate someone making an xstream-pipe style library like this, would be just a bit of boilerplate.

I don't think it will ever end up in the core xstream library since it's not strictly necessary and having a small bundle is a goal for xstream.

@a-laughlin
Copy link
Author

Thanks for the code Nick!

I like your way better than the rx api. Cleaner.

Given that syntax, how would you think about creating custom operators?

I started to create a function that returned a listener object, but then realized it would likely need a producer.

Looking at the code (oooh. One file. Clean. Nice inline docs. Beautiful!), it looks like most of the operators return a combined listener+producer object. Hmm. They're intermittently polymorphic too. map returns a stream matching the input type (MemoryStream || Stream), filter returns a Stream, and fold returns a MemoryStream. Interesting. Creating custom versions of those could get verbose and complicated.

Thinking out loud here, maybe it would be simpler to create them using the existing operators. That should be easy enough since they can be piped/composed using your syntax. Something like:

const pipe$ = (...operators)=>stream=>operators.reduce((s, f) => f(s), stream);
// ^^ same concept, with operators first for composability, followed by $ to distinguish it
// from a regular pipe function.
// wait... no.
// It IS a regular pipe function written that way.
// There's no need to write a custom pipe operator.
// Regular pipe/compose functions would work fine.

// so an omit function (opposite of filter) could just be written like:
const filter = predicate => stream => stream.filter(predicate);
const not = predicate=>(...args)=>!predicate(...args);
const omit = pipe(not, filter); // or compose(filter, not);
const takeNon3sUntil7 = pipe(  omit(x=>x%3===0),  take(5));

const non3sTo7$ = takeNon3sUntil7(xs.fromArray([1,2,3,4,5,6,7,8]));
// --1--2-----4--5-----7|

Does that make sense? To summarize, more complex operators can be implemented as one-liners by applying regular pipe/compose functions to the existing set of operators (each written as fn=>stream=>stream.foo(fn)). No need to patch the prototype, or manually create producer+listener objects, unless doing something that isn't composable from existing operators or for performance. If that's correct, cool!

@Widdershin
Copy link
Contributor

Does that make sense? To summarize, more complex operators can be implemented as one-liners by applying regular pipe/compose functions to the existing set of operators (each written as fn=>stream=>stream.foo(fn)). No need to patch the prototype, or manually create producer+listener objects, unless doing something that isn't composable from existing operators or for performance. If that's correct, cool!

That is indeed correct 😃

@anilanar
Copy link

I did it here: https://github.com/anilanar/xstream-pipe

@staltz staltz closed this as completed Jun 15, 2018
@raveclassic
Copy link

raveclassic commented Jun 14, 2019

@staltz Referring to your comment here, do you still consider replacing methods with pipeable operators in the core to minimize API surface? Current implementation is mixing methods with compose and is very confusing.

What do you think about dropping methods in favor of a single .pipe method or even moving piping out of the core and delegating it to other libs like ramda, fp-ts, etc.

@staltz
Copy link
Owner

staltz commented Jun 17, 2019

@raveclassic It's important for xstream to not have breaking changes unless it's unavoidable, because there are many codebases and tutorials that depend on xstream's API as it is right now. For a more modular approach, I recommend taking a look at https://github.com/callbag/callbag which is also a possible replacement for xstream inside Cycle.js in the future.

@raveclassic
Copy link

@staltz Yeah I agree xstream should not break API. At least the methods could be deprecated in favor of pipe. Anyway callbags look promising and indeed much more modular, thanks for pointing out!

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

5 participants