Repository: mostjs/core
Pull request #57 (2017-05-05 12:02:16)

feat(withArrayValues): Add withArrayValues and zipArrayValues
### Summary

This adds `withArrayValues` and `zipArrayValues` for mapping values from an array onto a stream.  In the case of `withArrayValues`, for each event, the stream provides the time component, and the array provides the value component.  Similarly for `zipArrayValues`, except it accepts a function to compute the value component from the array value and incoming event value.

### Other changes

- Run flow on the whole test dir.  It will only check files with `@flow` at the top, which for now, is only the new `withArrayValues-test.js`.  But, I like the idea of using unit tests also to check types.  Feedback welcome 😄 
  - Note that to do this, I had to give flow visibility into the top-level `node_modules` folder, since lerna is hoisting common dependencies (such as `mocha` and `@briancavalier/assert`) to the top level.

### Todo:

- [ ] deprecate `fromArray` and `fromIterable`

davidchase: 👍
TylorS: I really like having the tests type checked :+1:

Patch:
diff --git a/packages/core/.flowconfig b/packages/core/.flowconfig
index 8536ccc..ce0a8f6 100644
--- a/packages/core/.flowconfig
+++ b/packages/core/.flowconfig
@@ -1,6 +1,8 @@
 [ignore]
 
 [include]
+../../node_modules
+test/
 test/flow/**/*.js
 dist/.*\.flow
 
diff --git a/packages/core/src/combinator/withArrayValues.js b/packages/core/src/combinator/withArrayValues.js
new file mode 100644
index 0000000..7ff7ce3
--- /dev/null
+++ b/packages/core/src/combinator/withArrayValues.js
@@ -0,0 +1,44 @@
+/** @license MIT License (c) copyright 2010-2017 original author or authors */
+import { empty } from '../source/core'
+import Pipe from '../sink/Pipe'
+
+export const withArrayValues = (array, stream) =>
+  zipArrayValues(keepLeft, array, stream)
+
+export const zipArrayValues = (f, array, stream) =>
+  array.length === 0 || stream === empty()
+    ? empty()
+    : new ZipArrayValues(f, array, stream)
+
+const keepLeft = (a, _) => a
+
+class ZipArrayValues {
+  constructor (f, values, source) {
+    this.f = f
+    this.values = values
+    this.source = source
+  }
+
+  run (sink, scheduler) {
+    return this.source.run(new ZipArrayValuesSink(this.f, this.values, sink), scheduler)
+  }
+}
+
+class ZipArrayValuesSink extends Pipe {
+  constructor (f, values, sink) {
+    super(sink)
+    this.f = f
+    this.values = values
+    this.index = 0
+  }
+
+  event (t, b) {
+    const f = this.f
+    this.sink.event(t, f(this.values[this.index], b))
+
+    this.index += 1
+    if (this.index >= this.values.length) {
+      this.sink.end(t)
+    }
+  }
+}
diff --git a/packages/core/src/index.js b/packages/core/src/index.js
index 21561ee..7112c19 100644
--- a/packages/core/src/index.js
+++ b/packages/core/src/index.js
@@ -9,6 +9,10 @@ export { fromArray } from './source/fromArray'
 
 export { fromIterable } from './source/fromIterable'
 
+import { zipArrayValues as _zipArrayValues, withArrayValues as _withArrayValues } from './combinator/withArrayValues'
+export const zipArrayValues = curry3(_zipArrayValues)
+export const withArrayValues = curry2(_withArrayValues)
+
 // -----------------------------------------------------------------------
 // Observing
 
diff --git a/packages/core/src/index.js.flow b/packages/core/src/index.js.flow
index 9eed0a7..5ebefaa 100644
--- a/packages/core/src/index.js.flow
+++ b/packages/core/src/index.js.flow
@@ -125,6 +125,14 @@ declare export function zipArray <A, B, C, D, E, F, Z> (f: (A, B, C, D, E, F) =>
 declare export function zipArray <Z> (f: (...rest: Array<any>) => Z, ss: Array<Stream<any>>): Stream<Z>
 declare export function zipArray <Z> (f: (...rest: Array<any>) => Z): (ss: Array<Stream<any>>) => Stream<Z>
 
+declare export function withArrayValues <A> (a: Array<A>, s: Stream<any>): Stream<A>
+declare export function withArrayValues <A> (a: Array<A>): (s: Stream<any>) => Stream<A>
+
+declare export function zipArrayValues <A, B, C> (f: (A, B) => C, a: Array<A>, s: Stream<B>): Stream<C>
+declare export function zipArrayValues <A, B, C> (f: (A, B) => C): (a: Array<A>, s: Stream<B>) => Stream<C>
+declare export function zipArrayValues <A, B, C> (f: (A, B) => C, a: Array<A>): (s: Stream<B>) => Stream<C>
+declare export function zipArrayValues <A, B, C> (f: (A, B) => C): (a: Array<A>) => (s: Stream<B>) => Stream<C>
+
 declare export function filter <A> (p: (A) => boolean, s: Stream<A>): Stream<A>
 declare export function filter <A> (p: (A) => boolean): (s: Stream<A>) => Stream<A>
 
diff --git a/packages/core/type-definitions/combinator/withArrayValues.d.ts b/packages/core/type-definitions/combinator/withArrayValues.d.ts
new file mode 100644
index 0000000..fa5bba0
--- /dev/null
+++ b/packages/core/type-definitions/combinator/withArrayValues.d.ts
@@ -0,0 +1,6 @@
+import { Stream } from '@most/types'
+
+export function zipArrayValues <A, B, C> (f: (a: A, b: B) => C, a: Array<A>, s: Stream<B>): Stream<C>;
+export function zipArrayValues <A, B, C> (f: (a: A, b: B) => C): (a: Array<A>, s: Stream<B>) => Stream<C>;
+export function zipArrayValues <A, B, C> (f: (a: A, b: B) => C, a: Array<A>): (s: Stream<B>) => Stream<C>;
+export function zipArrayValues <A, B, C> (f: (a: A, b: B) => C): (a: Array<A>) => (s: Stream<B>) => Stream<C>;
diff --git a/packages/core/type-definitions/most.d.ts b/packages/core/type-definitions/most.d.ts
index 7caf268..2fa4c0c 100644
--- a/packages/core/type-definitions/most.d.ts
+++ b/packages/core/type-definitions/most.d.ts
@@ -22,6 +22,7 @@ export * from './combinator/switch';
 export * from './combinator/timeslice';
 export * from './combinator/transform';
 export * from './combinator/zip';
+export * from './combinator/withArrayValues';
 
 export * from './source/core';
 export * from './source/newStream';


Test patch:
diff --git a/packages/core/test/combinator/withArrayValues-test.js b/packages/core/test/combinator/withArrayValues-test.js
new file mode 100644
index 0000000..f954af8
--- /dev/null
+++ b/packages/core/test/combinator/withArrayValues-test.js
@@ -0,0 +1,84 @@
+// @flow
+import { describe, it } from 'mocha'
+import { eq, fail } from '@briancavalier/assert'
+
+import { zipArrayValues, withArrayValues } from '../../src/combinator/withArrayValues'
+import { empty } from '../../src/source/core'
+
+import { collectEventsFor, makeEvents } from '../helper/testEnv'
+
+describe('withArrayValues', () => {
+  describe('zipArrayValues', () => {
+    it('should be empty for empty array', () => {
+      const s = zipArrayValues(fail, [], makeEvents(1, 1))
+      return collectEventsFor(1, s).then(eq([]))
+    })
+
+    it('should be empty for empty stream', () => {
+      const s = zipArrayValues(fail, [1, 2, 3], empty())
+      return collectEventsFor(1, s).then(eq([]))
+    })
+
+    it('should contain zipped values when more events than values', () => {
+      const a = ['a', 'b', 'c']
+      const n = a.length + 1
+      const s = zipArrayValues((a, b) => a + String(b), a, makeEvents(1, n))
+
+      return collectEventsFor(n, s)
+        .then(eq([
+          { time: 0, value: 'a0' },
+          { time: 1, value: 'b1' },
+          { time: 2, value: 'c2' }
+        ]))
+    })
+
+    it('should contain zipped values when more values than events', () => {
+      const a = ['a', 'b', 'c']
+      const n = a.length - 1
+      const s = zipArrayValues((a, b) => a + String(b), a, makeEvents(1, n))
+
+      return collectEventsFor(a.length, s)
+        .then(eq([
+          { time: 0, value: 'a0' },
+          { time: 1, value: 'b1' }
+        ]))
+    })
+  })
+
+  describe('withArrayValues', () => {
+    it('should be empty for empty array', () => {
+      const s = withArrayValues([], makeEvents(1, 1))
+      return collectEventsFor(1, s).then(eq([]))
+    })
+
+    it('should be empty for empty stream', () => {
+      const s = withArrayValues([1, 2, 3], empty())
+      return collectEventsFor(1, s).then(eq([]))
+    })
+
+    it('should contain array values when more events than values', () => {
+      const a = ['a', 'b', 'c']
+      const n = a.length + 1
+      const s = withArrayValues(a, makeEvents(1, n))
+
+      return collectEventsFor(n, s)
+        .then(eq([
+          { time: 0, value: 'a' },
+          { time: 1, value: 'b' },
+          { time: 2, value: 'c' }
+        ]))
+    })
+
+    it('should contain array values when more values than events', () => {
+      const a = ['a', 'b', 'c']
+      const n = a.length - 1
+      const s = withArrayValues(a, makeEvents(1, n))
+
+      return collectEventsFor(a.length, s)
+        .then(eq([
+          { time: 0, value: 'a' },
+          { time: 1, value: 'b' }
+        ]))
+    })
+  })
+})