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

scan operator description might be confusing when not using any seed #4348

Closed
1 of 5 tasks
martinsik opened this issue Nov 17, 2018 · 6 comments · Fixed by #5722
Closed
1 of 5 tasks

scan operator description might be confusing when not using any seed #4348

martinsik opened this issue Nov 17, 2018 · 6 comments · Fixed by #5722
Labels
docs Issues and PRs related to documentation

Comments

@martinsik
Copy link
Contributor

martinsik commented Nov 17, 2018

Documentation Related To Component:

scan

Please check those that apply

  • typo
  • documentation doesn't exist
  • documentation needs clarification
  • error(s) in example
  • needs example

Description Of The Issue

The last paragraph in scan's docblock says (emphasis is mine):

Returns an Observable that applies a specified accumulator function to each item emitted by the source Observable. If a seed value is specified, then that value will be used as the initial value for the accumulator. If no seed value is specified, the first item of the source is used as the seed.

However, the actual scan behavior isn't exactly like this:

...applies a specified accumulator function to each item emitted by the source Observable

It doesn't apply the accumulator function to every item if I don't set any seed. In that case it just passes the first emission through without calling accumulator:

from(['a', 'b', 'c']).pipe(
  scan<string, number>((acc, value, index) => {
    console.log(value, index);
    return 0;
  }),
).subscribe();

This will make the following output even when the first item was "a":

b 0
c 1

This is the relevant code:

protected _next(value: T): void {
if (!this.hasSeed) {
this.seed = value;
this.destination.next(value);
} else {
return this._tryNext(value);
}
}

Demo: https://stackblitz.com/edit/rxjs6-demo-ynfnan

So even though the sequence started with "a" the accumulator function was called for the first time for "b". Also the index passed started at 0 for the second item (does index count how many times the function was called or does it count the number of items that went through this operator?).

the first item of the source is used as the seed.

From the last sentence I would guess that the accumulator function will be called even for the first value like this order (even though it is a little weird):

accumulator('a', 'a', 0);
accumulator(..., 'b', 1);
accumulator(..., 'c', 2);

... while in fact it's called like this:

accumulator('a', 'b', 0);
accumulator(..., 'c', 1);

So maybe I'm just dumb but the description and actual behavior without setting any seed really confused me.

@cartant
Copy link
Collaborator

cartant commented Nov 17, 2018

You are correct and the documentation is wrong. The scan and reduce operators behave like Array.prototype.reduce when a seed is not specified.

That also means that the typing is incorrect. Fixing the types has been on my TODO list for some time.

@martinsik
Copy link
Contributor Author

martinsik commented Nov 22, 2018

@cartant I checked it again and documentation for Array.prototype.reduce and for the initialValueOptional param says:

Value to use as the first argument to the first call of the callback. If no initial value is supplied, the first element in the array will be used. Calling reduce() on an empty array without an initial value is an error.

and that's even when the description for reduce at the top of the page says (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/reduce):

The reduce() method executes a reducer function (that you provide) on each member of the array resulting in a single output value.

So comparing scan to Array.prototype.reduce is probably correct.

I'm just not sure that if I don't set any seed for scan I should be expecting the first value to go through unprocessed. I'm also still a little confused that in this case index will start from 0 for the second item (I don't know if there's any other operator that behaves like this).

@martinsik
Copy link
Contributor Author

I realised that the behavior is a little different for mergeScan as well. mergeScan requires its seed to be always set so in never just passes through the first item as scan does. But I think it could work just like scan so I don't know if this difference in intentional.

@cartant cartant added the docs Issues and PRs related to documentation label Jan 27, 2019
@davidmoshal
Copy link

yep, confusing, basically without a seed, scan just passes the event.
documentation bug

@bhutch29
Copy link

Please prioritize this, this is very confusing and just cost me multiple hours of debugging. I would hate for future developers to get stuck on this same documentation problem, considering it has already been discovered.

@felixfbecker
Copy link
Contributor

It is also confusing because the docs say

seed: Optional. Default is undefined.

Implying that if no seed is given, undefined will be used as a seed, i.e. the scan function will be called for the first item with undefined as accumulator (in reality it is not called for the first item).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
docs Issues and PRs related to documentation
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants