From 10205cfb3a1a9806b62099226668c92a3f0e762e Mon Sep 17 00:00:00 2001 From: Matthias Seemann <296476+semmel@users.noreply.github.com> Date: Mon, 10 Jun 2019 20:01:17 +0200 Subject: [PATCH 1/8] Added branch for flatScan patch From 27cebd32ac5ce2a0728500fe8ef7884d73f479bf Mon Sep 17 00:00:00 2001 From: Matthias Seemann <296476+semmel@users.noreply.github.com> Date: Mon, 10 Jun 2019 20:26:26 +0200 Subject: [PATCH 2/8] flatScan now optionally takes the first source value as the seed value --- src/flatscan.js | 21 +++++++++++++++++---- src/flatscan.ts | 18 ++++++++++++++++-- src/observable.ts | 2 +- test/flatscan.ts | 6 ++++++ types/flatscan.d.ts | 2 +- types/observable.d.ts | 2 +- 6 files changed, 42 insertions(+), 9 deletions(-) diff --git a/src/flatscan.js b/src/flatscan.js index 6f8badc9f..6097ef1f8 100644 --- a/src/flatscan.js +++ b/src/flatscan.js @@ -4,9 +4,22 @@ import './doaction' import { makeObservable } from "./flatmap_" import EventStream from "./eventstream"; // TODO: toString test, Desc -EventStream.prototype.flatScan = function(seed, f) { - let current = seed +Bacon.EventStream.prototype.flatScan = function (seed, f) { + var current; + var isSeeded = false; + + if (typeof seed === "function") { + return this.flatMapConcat(function (next) { + return (isSeeded ? makeObservable(seed(current, next)) : makeObservable(next)) + .doAction(function (updated) { + isSeeded = true; + return current = updated; + }); + }).toProperty(); + } + + current = seed; return this.flatMapConcat(next => makeObservable(f(current, next)).doAction(updated => current = updated) - ).toProperty(seed) -} + ).toProperty(seed); +}; \ No newline at end of file diff --git a/src/flatscan.ts b/src/flatscan.ts index 3fae44173..52e06078b 100644 --- a/src/flatscan.ts +++ b/src/flatscan.ts @@ -4,9 +4,23 @@ import { Desc } from "./describe"; import { Function2 } from "./types"; /** @hidden */ -export function flatScan(src: Observable, seed: Out, f: Function2 | Out>): Property { - let current = seed +export function flatScan(src: Observable, seed: any | Function2 | Out>, f?: Function2 | Out>): Property { + let current: Out; + let isSeeded = false; + + if (typeof seed === "function") { + return src.flatMapConcat(function (next: In) { + return (isSeeded ? makeObservable(seed(current, next)) : makeObservable(next)) + .doAction(function (updated) { + isSeeded = true; + return current = updated; + }); + }).toProperty(); + } + + current = seed; return src.flatMapConcat((next: In) => + // @ts-ignore: TS2722 Cannot invoke an object which is possibly 'undefined'. Cause it's optional! makeObservable(f(current, next)).doAction(updated => current = updated) ).toProperty().startWith(seed).withDesc(new Desc(src, "flatScan", [seed, f])) } diff --git a/src/observable.ts b/src/observable.ts index c2854487c..de65affaf 100644 --- a/src/observable.ts +++ b/src/observable.ts @@ -1400,7 +1400,7 @@ export class EventStream extends Observable { * @param f transition function from previous state and new value to next state * @typeparam V2 state and result type */ - flatScan(seed: V2, f: Function2>): Property { + flatScan(seed: V2 | Function2>, f?: Function2>): Property { return flatScan(this, seed, f) } diff --git a/test/flatscan.ts b/test/flatscan.ts index dad062a93..5d48ac1c2 100644 --- a/test/flatscan.ts +++ b/test/flatscan.ts @@ -10,6 +10,12 @@ describe("EventStream.flatScan", function() { [0, 1, 3, error(), 6]) ); + describe.skip("beginning with the first source value successive values are accumulated values using the accumulator function which returns a stream of updated values", () => + expectPropertyEvents( + () => series(1, [1, 2, error(), 3]).flatScan(addAsync(1)), + [1, 3, error(), 6]) + ); + describe("Serializes updates even when they occur while performing previous update", () => expectPropertyEvents( () => series(1, [1, 2, error(), 3]).flatScan(0, addAsync(5)), diff --git a/types/flatscan.d.ts b/types/flatscan.d.ts index 68c85161f..545e96b8f 100644 --- a/types/flatscan.d.ts +++ b/types/flatscan.d.ts @@ -1,4 +1,4 @@ import { Observable, Property } from "./observable"; import { Function2 } from "./types"; /** @hidden */ -export declare function flatScan(src: Observable, seed: Out, f: Function2 | Out>): Property; +export declare function flatScan(src: Observable, seed: any | Function2 | Out>, f?: Function2 | Out>): Property; diff --git a/types/observable.d.ts b/types/observable.d.ts index e3745f097..2d4c0adbb 100644 --- a/types/observable.d.ts +++ b/types/observable.d.ts @@ -1068,7 +1068,7 @@ export declare class EventStream extends Observable { * @param f transition function from previous state and new value to next state * @typeparam V2 state and result type */ - flatScan(seed: V2, f: Function2>): Property; + flatScan(seed: V2 | Function2>, f?: Function2>): Property; /** Groups stream events to new streams by `keyF`. Optional `limitF` can be provided to limit grouped stream life. Stream transformed by `limitF` is passed on if provided. `limitF` gets grouped stream From e9a84248308fe3403be94208e7a8b42fc23eb3cf Mon Sep 17 00:00:00 2001 From: Juha Paananen Date: Thu, 27 Jun 2019 22:13:52 +0300 Subject: [PATCH 3/8] nicer type signature for flatScan --- src/observable.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/observable.ts b/src/observable.ts index 3ffc4e4e4..fb9b6d659 100644 --- a/src/observable.ts +++ b/src/observable.ts @@ -1408,6 +1408,10 @@ export class EventStream extends Observable { * @param f transition function from previous state and new value to next state * @typeparam V2 state and result type */ + flatScan(seed: V2, f: Function2>): Property + + flatScan(f: Function2>): Property + flatScan(seed: V2 | Function2>, f?: Function2>): Property { return flatScan(this, seed, f) } From 8fa6ac623749a5da28bf4e6627d86a7389391624 Mon Sep 17 00:00:00 2001 From: Juha Paananen Date: Thu, 27 Jun 2019 22:14:05 +0300 Subject: [PATCH 4/8] enable test for seedless flatScan --- test/flatscan.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/flatscan.ts b/test/flatscan.ts index 5d48ac1c2..7b42965f2 100644 --- a/test/flatscan.ts +++ b/test/flatscan.ts @@ -10,7 +10,7 @@ describe("EventStream.flatScan", function() { [0, 1, 3, error(), 6]) ); - describe.skip("beginning with the first source value successive values are accumulated values using the accumulator function which returns a stream of updated values", () => + describe("Without a seed value", () => expectPropertyEvents( () => series(1, [1, 2, error(), 3]).flatScan(addAsync(1)), [1, 3, error(), 6]) From 1f4b97e5fe2b70d193a5308570e423fdf73c8acc Mon Sep 17 00:00:00 2001 From: Juha Paananen Date: Thu, 27 Jun 2019 22:43:57 +0300 Subject: [PATCH 5/8] replace typeof check with argument count check, fix some typings --- src/flatscan.ts | 23 ++++++++++++----------- src/observable.ts | 7 +++++-- 2 files changed, 17 insertions(+), 13 deletions(-) diff --git a/src/flatscan.ts b/src/flatscan.ts index 52e06078b..401d34aeb 100644 --- a/src/flatscan.ts +++ b/src/flatscan.ts @@ -4,23 +4,24 @@ import { Desc } from "./describe"; import { Function2 } from "./types"; /** @hidden */ -export function flatScan(src: Observable, seed: any | Function2 | Out>, f?: Function2 | Out>): Property { - let current: Out; +export function flatScanSeedless(src: Observable, f: Function2 | V>): Property { + let current: V; let isSeeded = false; - if (typeof seed === "function") { - return src.flatMapConcat(function (next: In) { - return (isSeeded ? makeObservable(seed(current, next)) : makeObservable(next)) - .doAction(function (updated) { + return src.flatMapConcat(function (next: V) { + return (isSeeded ? makeObservable(f(current, next)) : makeObservable(next)) + .doAction(function (updated: V) { isSeeded = true; - return current = updated; + current = updated; }); - }).toProperty(); - } + }).toProperty(); +} - current = seed; +/** @hidden */ +export function flatScan(src: Observable, seed: Out, f: Function2 | Out>): Property { + let current = seed; return src.flatMapConcat((next: In) => // @ts-ignore: TS2722 Cannot invoke an object which is possibly 'undefined'. Cause it's optional! makeObservable(f(current, next)).doAction(updated => current = updated) ).toProperty().startWith(seed).withDesc(new Desc(src, "flatScan", [seed, f])) -} +} \ No newline at end of file diff --git a/src/observable.ts b/src/observable.ts index fb9b6d659..1201ced0c 100644 --- a/src/observable.ts +++ b/src/observable.ts @@ -69,7 +69,7 @@ import skipWhile from "./skipwhile"; import { groupBy, GroupTransformer } from "./groupby"; import { slidingWindow } from "./slidingwindow"; import { diff, Differ } from "./diff"; -import { flatScan } from "./flatscan"; +import { flatScan, flatScanSeedless } from "./flatscan"; import { holdWhen } from "./holdwhen"; import { zip } from "./zip"; import decode from "./decode"; @@ -1413,7 +1413,10 @@ export class EventStream extends Observable { flatScan(f: Function2>): Property flatScan(seed: V2 | Function2>, f?: Function2>): Property { - return flatScan(this, seed, f) + if (arguments.length == 1) { + return flatScanSeedless(this, seed as any as Function2>) + } + return flatScan(this, seed as any as V2, f as any as Function2>) } /** From a9c2d3cb28aec67e74b318f2561989dcfe297ec3 Mon Sep 17 00:00:00 2001 From: Juha Paananen Date: Thu, 27 Jun 2019 22:44:03 +0300 Subject: [PATCH 6/8] build --- dist/Bacon.js | 28 +++++++++++++++++----------- dist/Bacon.min.js | 2 +- dist/Bacon.noAssert.js | 17 +++++++++++++++-- types/flatscan.d.ts | 2 ++ types/observable.d.ts | 1 + 5 files changed, 36 insertions(+), 14 deletions(-) diff --git a/dist/Bacon.js b/dist/Bacon.js index aad205e12..b20778b09 100644 --- a/dist/Bacon.js +++ b/dist/Bacon.js @@ -2919,10 +2919,23 @@ function diff(src, start, f) { return transformP(scan(src, [start, nullMarker], (function (prevTuple, next) { return [next, f(prevTuple[0], next)]; })), composeT(filterT(function (tuple) { return tuple[1] !== nullMarker; }), mapT(function (tuple) { return tuple[1]; })), new Desc(src, "diff", [start, f])); } +/** @hidden */ +function flatScanSeedless(src, f) { + var current; + var isSeeded = false; + return src.flatMapConcat(function (next) { + return (isSeeded ? makeObservable(f(current, next)) : makeObservable(next)) + .doAction(function (updated) { + isSeeded = true; + current = updated; + }); + }).toProperty(); +} /** @hidden */ function flatScan(src, seed, f) { var current = seed; return src.flatMapConcat(function (next) { + // @ts-ignore: TS2722 Cannot invoke an object which is possibly 'undefined'. Cause it's optional! return makeObservable(f(current, next)).doAction(function (updated) { return current = updated; }); }).toProperty().startWith(seed).withDesc(new Desc(src, "flatScan", [seed, f])); } @@ -4185,17 +4198,10 @@ var EventStream = /** @class */ (function (_super) { */ EventStream.prototype.flatMapWithConcurrencyLimit = function (limit, f) { return flatMapWithConcurrencyLimit(this, limit, f); }; EventStream.prototype.flatMapEvent = function (f) { return flatMapEvent(this, f); }; - /** - Scans stream with given seed value and accumulator function, resulting to a Property. - Difference to [`scan`](#scan) is that the function `f` can return an [`EventStream`](eventstream.html) or a [`Property`](property.html) instead - of a pure value, meaning that you can use [`flatScan`](#flatscan) for asynchronous updates of state. It serializes - updates so that that the next update will be queued until the previous one has completed. - - * @param seed initial value to start with - * @param f transition function from previous state and new value to next state - * @typeparam V2 state and result type - */ EventStream.prototype.flatScan = function (seed, f) { + if (arguments.length == 1) { + return flatScanSeedless(this, seed); + } return flatScan(this, seed, f); }; EventStream.prototype.groupBy = function (keyF, limitF) { @@ -5223,7 +5229,7 @@ var $ = { /** * Bacon.js version as string */ -var version = '3.0.5'; +var version = ''; exports.$ = $; exports.Bus = Bus; diff --git a/dist/Bacon.min.js b/dist/Bacon.min.js index 417b7b537..66dc9051d 100644 --- a/dist/Bacon.min.js +++ b/dist/Bacon.min.js @@ -1 +1 @@ -undefined +undefined \ No newline at end of file diff --git a/dist/Bacon.noAssert.js b/dist/Bacon.noAssert.js index a70266bef..c2e0c02c6 100644 --- a/dist/Bacon.noAssert.js +++ b/dist/Bacon.noAssert.js @@ -2558,6 +2558,16 @@ f ])); } + function flatScanSeedless(src, f) { + var current; + var isSeeded = false; + return src.flatMapConcat(function (next) { + return (isSeeded ? makeObservable(f(current, next)) : makeObservable(next)).doAction(function (updated) { + isSeeded = true; + current = updated; + }); + }).toProperty(); + } function flatScan(src, seed, f) { var current = seed; return src.flatMapConcat(function (next) { @@ -3154,6 +3164,9 @@ return flatMapEvent(this, f); }; EventStream.prototype.flatScan = function (seed, f) { + if (arguments.length == 1) { + return flatScanSeedless(this, seed); + } return flatScan(this, seed, f); }; EventStream.prototype.groupBy = function (keyF, limitF) { @@ -3818,7 +3831,7 @@ jQuery.fn.asEventStream = $.asEventStream; } }; - var version = '3.0.5'; + var version = ''; exports.$ = $; exports.Bus = Bus; exports.CompositeUnsubscribe = CompositeUnsubscribe; @@ -3880,4 +3893,4 @@ exports.zipAsArray = zipAsArray; exports.zipWith = zipWith; Object.defineProperty(exports, '__esModule', { value: true }); -})); +})); \ No newline at end of file diff --git a/types/flatscan.d.ts b/types/flatscan.d.ts index 68c85161f..b58f43aa5 100644 --- a/types/flatscan.d.ts +++ b/types/flatscan.d.ts @@ -1,4 +1,6 @@ import { Observable, Property } from "./observable"; import { Function2 } from "./types"; /** @hidden */ +export declare function flatScanSeedless(src: Observable, f: Function2 | V>): Property; +/** @hidden */ export declare function flatScan(src: Observable, seed: Out, f: Function2 | Out>): Property; diff --git a/types/observable.d.ts b/types/observable.d.ts index 9d3eb4854..bf81dd9c5 100644 --- a/types/observable.d.ts +++ b/types/observable.d.ts @@ -1071,6 +1071,7 @@ export declare class EventStream extends Observable { * @typeparam V2 state and result type */ flatScan(seed: V2, f: Function2>): Property; + flatScan(f: Function2>): Property; /** Groups stream events to new streams by `keyF`. Optional `limitF` can be provided to limit grouped stream life. Stream transformed by `limitF` is passed on if provided. `limitF` gets grouped stream From a410e04c9c6770f1c219654033cd2c0202f4ada6 Mon Sep 17 00:00:00 2001 From: Juha Paananen Date: Thu, 27 Jun 2019 22:48:45 +0300 Subject: [PATCH 7/8] fixed type signature for seedless flatScan --- src/observable.ts | 2 +- types/observable.d.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/observable.ts b/src/observable.ts index 1201ced0c..effc7a0b8 100644 --- a/src/observable.ts +++ b/src/observable.ts @@ -1410,7 +1410,7 @@ export class EventStream extends Observable { */ flatScan(seed: V2, f: Function2>): Property - flatScan(f: Function2>): Property + flatScan(f: Function2>): Property flatScan(seed: V2 | Function2>, f?: Function2>): Property { if (arguments.length == 1) { diff --git a/types/observable.d.ts b/types/observable.d.ts index bf81dd9c5..9504d59cb 100644 --- a/types/observable.d.ts +++ b/types/observable.d.ts @@ -1071,7 +1071,7 @@ export declare class EventStream extends Observable { * @typeparam V2 state and result type */ flatScan(seed: V2, f: Function2>): Property; - flatScan(f: Function2>): Property; + flatScan(f: Function2>): Property; /** Groups stream events to new streams by `keyF`. Optional `limitF` can be provided to limit grouped stream life. Stream transformed by `limitF` is passed on if provided. `limitF` gets grouped stream From 2a45c58f176a731a3adbaba658d642ee457c390f Mon Sep 17 00:00:00 2001 From: Matthias Seemann <296476+semmel@users.noreply.github.com> Date: Mon, 15 Jul 2019 23:04:14 +0200 Subject: [PATCH 8/8] made .scan and .fold seedless, incl. tests --- src/flatscan.ts | 2 +- src/fold.ts | 9 +++- src/observable.ts | 39 ++++++++++++--- src/scan.ts | 28 ++++++++++- test/flatscan.ts | 20 +++++--- test/fold.ts | 38 +++++++++++++-- test/scan.ts | 110 ++++++++++++++++++++++++++++++++++++++++++ types/fold.d.ts | 2 + types/observable.d.ts | 3 ++ types/scan.d.ts | 2 + 10 files changed, 232 insertions(+), 21 deletions(-) diff --git a/src/flatscan.ts b/src/flatscan.ts index 401d34aeb..7c39da294 100644 --- a/src/flatscan.ts +++ b/src/flatscan.ts @@ -14,7 +14,7 @@ export function flatScanSeedless(src: Observable, f: Function2(src: Observable, seed: Out, f: Accumulator): Property { return src.scan(seed, f) .last() .withDesc(new Desc(src, "fold", [seed, f])); +} + +/** @hidden */ +export function foldSeedless(src: Observable, f: Accumulator): Property { + return src.scan(f) + .last() + .withDesc(new Desc(src, "fold", [f])); } \ No newline at end of file diff --git a/src/observable.ts b/src/observable.ts index effc7a0b8..400c5cba3 100644 --- a/src/observable.ts +++ b/src/observable.ts @@ -20,7 +20,7 @@ import doLogT from "./dolog"; import doErrorT from "./doerror"; import doActionT from "./doaction"; import doEndT from "./doend"; -import { Accumulator, default as scan } from "./scan"; +import { Accumulator, scanSeedless, default as scan } from "./scan"; import mapEndT from "./mapend"; import mapErrorT from "./maperror"; import { SpawnerOrObservable, EventSpawner, EventOrValue } from "./flatmap_"; @@ -40,7 +40,7 @@ import { filter } from "./filter"; import { and, not, or } from "./boolean"; import flatMapFirst from "./flatmapfirst"; import addPropertyInitValueToStream from "./internal/addpropertyinitialvaluetostream"; -import fold from "./fold"; +import { default as fold, foldSeedless } from "./fold"; import { startWithE, startWithP } from "./startwith"; import takeUntil from "./takeuntil"; import flatMap from "./flatmap"; @@ -407,8 +407,16 @@ Works like [`scan`](#scan) but only emits the final value, i.e. the value just before the observable ends. Returns a [`Property`](property.html). */ - fold(seed: V2, f: Accumulator): Property { - return fold(this, seed, f) + + fold(seed: V2, f: Accumulator): Property + + fold(f: Accumulator): Property + + fold(seed: V2 | Accumulator, f?: Accumulator): Property { + if (arguments.length === 1) { + return foldSeedless(this, seed as any as Accumulator); + } + return fold(this, seed as any as V2, f as any as Accumulator) } /** @@ -596,8 +604,15 @@ Only applicable for observables with arrays as values. } /** A synonym for [scan](#scan). */ - reduce(seed: V2, f: Accumulator): Property { - return fold(this, seed, f) + reduce(seed: V2, f: Accumulator): Property + + reduce(f: Accumulator): Property + + reduce(seed: V2 | Accumulator, f?: Accumulator): Property { + if (arguments.length === 1) { + return foldSeedless(this, seed as any as Accumulator); + } + return fold(this, seed as any as V2, f as any as Accumulator) } /** @@ -639,8 +654,16 @@ identically to EventStream.scan: the `seed` will be the initial value of seed won't be output as is. Instead, the initial value of `r` will be `f(seed, x)`. This makes sense, because there can only be 1 initial value for a Property at a time. */ - scan(seed: V2, f: Accumulator): Property { - return scan(this, seed, f) + + scan(seed: V2, f: Accumulator): Property + + scan(f: Accumulator): Property + + scan(seed: V2 | Accumulator, f?: Accumulator): Property { + if (arguments.length === 1) { + return scanSeedless(this, seed as any as Accumulator); + } + return scan(this, seed as any as V2, f as any as Accumulator) } /** Skips the first n elements from the stream diff --git a/src/scan.ts b/src/scan.ts index 63bd39f30..657421291 100644 --- a/src/scan.ts +++ b/src/scan.ts @@ -1,5 +1,5 @@ import Observable from "./observable"; -import { Property } from "./observable";; +import { Property } from "./observable"; import { Event, hasValue, Initial } from "./event"; import { more, noMore } from "./reply"; import { nop } from "./helpers"; @@ -58,3 +58,29 @@ export default function scan(src: Observable, seed: Out, f: Accumul } return resultProperty = new Property(new Desc(src, "scan", [seed, f]), subscribe) } + +/** @hidden */ +export function scanSeedless(src: Observable, f: Accumulator): Property { + let acc: V; + let hasAccumulatedFirstValue: Boolean = false; + const subscribe: Subscribe = (sink: EventSink) => { + let unsub = src.subscribeInternal(function(event: Event) { + if (hasValue(event)) { + //console.log("has value: ", hasValue(event), "isInitial:", event.isInitial); + if (!hasAccumulatedFirstValue) { + acc = event.value; + hasAccumulatedFirstValue = true; + return sink(event); // let the initial event pass through + } + + acc = f(acc, event.value); + return sink(event.apply(acc)); + + } else { + return sink(event); + } + }); + return unsub; + } + return new Property(new Desc(src, "scan", [f]), subscribe) +} diff --git a/test/flatscan.ts b/test/flatscan.ts index 7b42965f2..a83243658 100644 --- a/test/flatscan.ts +++ b/test/flatscan.ts @@ -10,12 +10,6 @@ describe("EventStream.flatScan", function() { [0, 1, 3, error(), 6]) ); - describe("Without a seed value", () => - expectPropertyEvents( - () => series(1, [1, 2, error(), 3]).flatScan(addAsync(1)), - [1, 3, error(), 6]) - ); - describe("Serializes updates even when they occur while performing previous update", () => expectPropertyEvents( () => series(1, [1, 2, error(), 3]).flatScan(0, addAsync(5)), @@ -34,6 +28,20 @@ describe("EventStream.flatScan", function() { [0, 1, 3, error(), 6], semiunstable) ); + describe("Without a seed value", () => { + it ("accumulates values with given seed and accumulator function which returns a stream of updated values", () => + expectPropertyEvents( + () => series(1, [1, 2, error(), 3]).flatScan(addAsync(1)), + [1, 3, error(), 6] + ) + ); + it("Serializes updates even when they occur while performing previous update", () => + expectPropertyEvents( + () => series(1, [0, 1, 2, error(), 3]).flatScan(addAsync(5)), + [0, error(), 1, 3, 6], semiunstable) + ); + }); + return it("yields the seed value immediately", function() { const outputs: number[] = []; new Bacon.Bus().flatScan(0, (a, b) => 1).onValue(value => { outputs.push(value) }); diff --git a/test/fold.ts b/test/fold.ts index b6f2c2dcc..57f955ca1 100644 --- a/test/fold.ts +++ b/test/fold.ts @@ -8,7 +8,7 @@ describe("EventStream.fold", function() { ); describe("has reduce as synonym", () => expectPropertyEvents( - () => series(1, [1, 2, error(), 3]).fold(0, add), + () => series(1, [1, 2, error(), 3]).reduce(0, add), [error(), 6]) ); describe("works with synchronous source", () => @@ -16,6 +16,31 @@ describe("EventStream.fold", function() { () => fromArray([1, 2, error(), 3]).fold(0, add), [error(), 6], unstable) ); + + describe("Without seed value", function(){ + it("folds stream into a single-valued Property, passes through errors", () => + expectPropertyEvents( + () => series(1, [0, 1, 2, error(), 3]).fold(add), + [error(), 6]) + ); + it("has reduce as synonym", () => + expectPropertyEvents( + () => series(1, [1, 2, error(), 3]).reduce(add), + [error(), 6]) + ); + it("works with synchronous source", () => + expectPropertyEvents( + () => fromArray([0, 1, 2, error(), 3]).fold(add), + [error(), 6], unstable) + ); + it("works with really large chunks too, with { eager: true }", function() { + const count = 50000; + return expectPropertyEvents( + () => series(1, range(1, count, true)).fold((x: number,y: number) => x+1), + [count]); + }); + }); + return describe("works with really large chunks too, with { eager: true }", function() { const count = 50000; return expectPropertyEvents( @@ -24,10 +49,15 @@ describe("EventStream.fold", function() { }); }); -describe("Property.fold", () => +describe("Property.fold", () => { describe("Folds Property into a single-valued one", () => expectPropertyEvents( - () => series(1, [2,3]).toProperty(1).fold(0, add), + () => series(1, [2, 3]).toProperty(1).fold(0, add), + [6]) + ); + describe("Without seed value folds Property into a single-valued one", () => + expectPropertyEvents( + () => series(1, [2, 3]).toProperty(1).fold(add), [6]) ) -); \ No newline at end of file +}); \ No newline at end of file diff --git a/test/scan.ts b/test/scan.ts index 5fb056b7f..d88e50636 100644 --- a/test/scan.ts +++ b/test/scan.ts @@ -59,6 +59,69 @@ describe("EventStream.scan", function() { expect(count).to.equal(1); }); }); + + describe("Without a seed value", () => { + it("accumulates values and lets errors pass", () => + expectPropertyEvents( + () => series(1, [1, 2, 3, error(), 4]).scan(add), + [1, 3, 6, error(), 10], + unstable + ) + ); + it("yields null seed value", () => + expectPropertyEvents( + () => series(1, [null, 1]).scan(() => 1), + [null, 1], unstable) + ); + it("works with synchronous streams", () => + expectPropertyEvents( + () => fromArray([0, 1, 2, 3]).scan((x, y) => x + y), + [0, 1, 3, 6], unstable) + ); + it("works with merged synchronous streams", () => + expectPropertyEvents( + () => Bacon.mergeAll(once(0), once(1), once(2)).scan((a, b) => a + b), + [0, 1, 3], unstable) + ); + it("works with functions as values", () => + expectPropertyEvents( + () => series(1, [(() => 0), (() => 1), (() => 2)]).scan((a, b) => b).map(f => f()), + [0, 1, 2], unstable) + ); + describe("calls accumulator function once per value", function () { + describe("(simple case)", function () { + let count = 0; + expectPropertyEvents( + () => series(2, [0, 1, 2, 3]).scan(function (x, y) { + count++; + return x + y; + }), + [0, 1, 3, 6], + { + extraCheck() { + return it("calls accumulator once per value", () => expect(count).to.equal(3)); + }, + unstable + } + ); + }); + it("(when pushing to Bus in accumulator)", function () { + let count = 0; + const someBus = new Bacon.Bus(); + someBus.onValue(function () {}); + const src = new Bacon.Bus(); + const result = src.scan(function (_, __) { + someBus.push(null); + return count++; + }); + result.onValue(); + result.onValue(); + src.push(0); + src.push(1); + expect(count).to.equal(1); + }); + }); + }); }); describe("Property.scan", function() { @@ -97,4 +160,51 @@ describe("Property.scan", function() { [1]) ); }); + describe("without Seed value", function() { + it("with Init value, starts with init, f(init, xs[0])", () => + expectPropertyEvents( + () => series(1, [1,2,3]).toProperty(0).scan(add), + [0, 1, 3, 6], + unstable + ) + ); + it("without Init value, starts with seed", () => + expectPropertyEvents( + () => series(1, [0, 2,3]).toProperty().scan(add), + [0, 2, 5], + unstable + ) + ); + it("treats null init value like any other value", function() { + expectPropertyEvents( + () => series(1, [null as any, 1]).toProperty().scan(add), + [null, 1], + unstable + ); + expectPropertyEvents( + () => series(1, [null as any, 2]).toProperty(1).scan(add), + [1, 1, 3], + unstable + ); + }); + describe("for synchronous source", function() { + it("with Init value, starts with f(seed, init)", () => + expectPropertyEvents( + () => fromArray([0,2,3]).toProperty(1).scan(add), + [1, 1, 3, 6], unstable) + ); + it("without Init value, starts with seed", () => + expectPropertyEvents( + () => fromArray([0,2,3]).toProperty().scan(add), + [0, 2, 5], unstable) + ); + it("works with synchronously responding empty source", () => + expectPropertyEvents( + () => Bacon.never().toProperty(1).scan(add), + [1], + unstable + ) + ); + }); + }); }); diff --git a/types/fold.d.ts b/types/fold.d.ts index 2f3055a5c..dd7329f41 100644 --- a/types/fold.d.ts +++ b/types/fold.d.ts @@ -7,3 +7,5 @@ import { Accumulator } from "./scan"; import { Property } from "./observable"; /** @hidden */ export default function fold(src: Observable, seed: Out, f: Accumulator): Property; +/** @hidden */ +export declare function foldSeedless(src: Observable, f: Accumulator): Property; diff --git a/types/observable.d.ts b/types/observable.d.ts index 9504d59cb..84599fee0 100644 --- a/types/observable.d.ts +++ b/types/observable.d.ts @@ -293,6 +293,7 @@ export declare abstract class Observable { [`Property`](property.html). */ fold(seed: V2, f: Accumulator): Property; + fold(f: Accumulator): Property; /** An alias for [onValue](#onvalue). @@ -439,6 +440,7 @@ export declare abstract class Observable { /** A synonym for [scan](#scan). */ reduce(seed: V2, f: Accumulator): Property; + reduce(f: Accumulator): Property; /** Creates an EventStream by sampling this stream/property value at each event from the `sampler` stream. The result @@ -479,6 +481,7 @@ export declare abstract class Observable { because there can only be 1 initial value for a Property at a time. */ scan(seed: V2, f: Accumulator): Property; + scan(f: Accumulator): Property; /** Skips the first n elements from the stream */ diff --git a/types/scan.d.ts b/types/scan.d.ts index 629e1abda..d622f708a 100644 --- a/types/scan.d.ts +++ b/types/scan.d.ts @@ -3,3 +3,5 @@ import { Property } from "./observable"; export declare type Accumulator = (acc: Out, value: In) => Out; /** @hidden */ export default function scan(src: Observable, seed: Out, f: Accumulator): Property; +/** @hidden */ +export declare function scanSeedless(src: Observable, f: Accumulator): Property;