As you already stated, you lose some of the advantages of pure functions. In this particular case, you run the risk of late subscribers getting different streams of data than you may expect (depends on what you are doing in your real case vs. in this constructed one).
For instance, by adding late subscribers, stream 'A' would see 0 and 1. Stream 'B' would see only '1' (it skips 0 because obs is still active from the 'A' subscriber. Stream 'C' would behave like stream 'A'.
const { interval, pipe, subscribe } = Rx;
const { take, map, tap, share } = RxOperators;
const custom = () => {
let state = 0;
return pipe(
map(next => state * next),
tap(_ => state += 1),
share()
)
}
// Late subscribers can get different streams
const obs = interval(500).pipe(custom())
const sub1 = obs.pipe(take(2)).subscribe((x) => console.log('A', x))
setTimeout(() => obs.pipe(take(1)).subscribe((x) => console.log('B', x)), 500)
setTimeout(() => obs.pipe(take(3)).subscribe((x) => console.log('C', x)), 3000)
Whether this is acceptable or expected behavior will depend on your use case. While it is good to try and use pure functions for all of their advantages, sometimes it isn't practical or appropriate for your use case.
next * (next + 1)
would work