Rewrite stream (#2207)
* Rewrite stream * Rename HALT to SKIP * Rename HALT to SKIP * Remove valueOf and toString * Update docs for HALT to SKIP * Rename halt to skip in test * Add test for combining nested streams atomically * Update change-log.md * Test basic SKIP * Add deprecated HALT * Combine continues with ended streams * Fix fantasy-land/of to match spec * Don't use arrow function * Improve scan description * Fix merge artifact
This commit is contained in:
parent
5e7528fefd
commit
58c86f7546
5 changed files with 214 additions and 159 deletions
|
|
@ -8,7 +8,7 @@
|
||||||
- [Stream.scan](#streamscan)
|
- [Stream.scan](#streamscan)
|
||||||
- [Stream.scanMerge](#streamscanmerge)
|
- [Stream.scanMerge](#streamscanmerge)
|
||||||
- [Stream.lift](#streamlift)
|
- [Stream.lift](#streamlift)
|
||||||
- [Stream.HALT](#streamhalt)
|
- [Stream.SKIP](#streamskip)
|
||||||
- [Stream["fantasy-land/of"]](#streamfantasy-landof)
|
- [Stream["fantasy-land/of"]](#streamfantasy-landof)
|
||||||
- [Instance members](#instance-members)
|
- [Instance members](#instance-members)
|
||||||
- [stream.map](#streammap)
|
- [stream.map](#streammap)
|
||||||
|
|
@ -121,13 +121,13 @@ Argument | Type | Required | Description
|
||||||
|
|
||||||
Creates a new stream with the results of calling the function on every value in the stream with an accumulator and the incoming value.
|
Creates a new stream with the results of calling the function on every value in the stream with an accumulator and the incoming value.
|
||||||
|
|
||||||
Note that you can prevent dependent streams from being updated by returning the special value `stream.HALT` inside the accumulator function.
|
Note that you can prevent dependent streams from being updated by returning the special value `stream.SKIP` inside the accumulator function.
|
||||||
|
|
||||||
`stream = Stream.scan(fn, accumulator, stream)`
|
`stream = Stream.scan(fn, accumulator, stream)`
|
||||||
|
|
||||||
Argument | Type | Required | Description
|
Argument | Type | Required | Description
|
||||||
------------- | -------------------------------- | -------- | ---
|
------------- | -------------------------------- | -------- | ---
|
||||||
`fn` | `(accumulator, value) -> result \| HALT` | Yes | A function that takes an accumulator and value parameter and returns a new accumulator value
|
`fn` | `(accumulator, value) -> result \| SKIP` | Yes | A function that takes an accumulator and value parameter and returns a new accumulator value of the same type
|
||||||
`accumulator` | `any` | Yes | The starting value for the accumulator
|
`accumulator` | `any` | Yes | The starting value for the accumulator
|
||||||
`stream` | `Stream` | Yes | Stream containing the values
|
`stream` | `Stream` | Yes | Stream containing the values
|
||||||
**returns** | `Stream` | | Returns a new stream containing the result
|
**returns** | `Stream` | | Returns a new stream containing the result
|
||||||
|
|
@ -183,9 +183,9 @@ Argument | Type | Required | Description
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
##### Stream.HALT
|
##### Stream.SKIP
|
||||||
|
|
||||||
A special value that can be returned to stream callbacks to halt execution of downstreams
|
A special value that can be returned to stream callbacks to skip execution of downstreams
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -375,14 +375,14 @@ console.log(doubled()) // logs 2
|
||||||
|
|
||||||
Dependent streams are *reactive*: their values are updated any time the value of their parent stream is updated. This happens regardless of whether the dependent stream was created before or after the value of the parent stream was set.
|
Dependent streams are *reactive*: their values are updated any time the value of their parent stream is updated. This happens regardless of whether the dependent stream was created before or after the value of the parent stream was set.
|
||||||
|
|
||||||
You can prevent dependent streams from being updated by returning the special value `stream.HALT`
|
You can prevent dependent streams from being updated by returning the special value `stream.SKIP`
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var halted = stream(1).map(function(value) {
|
var skipped = stream(1).map(function(value) {
|
||||||
return stream.HALT
|
return stream.SKIP
|
||||||
})
|
})
|
||||||
|
|
||||||
halted.map(function() {
|
skipped.map(function() {
|
||||||
// never runs
|
// never runs
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
@ -432,14 +432,14 @@ console.log(added()) // logs 12
|
||||||
|
|
||||||
A stream can depend on any number of streams and it's guaranteed to update atomically. For example, if a stream A has two dependent streams B and C, and a fourth stream D is dependent on both B and C, the stream D will only update once if the value of A changes. This guarantees that the callback for stream D is never called with unstable values such as when B has a new value but C has the old value. Atomicity also brings the performance benefits of not recomputing downstreams unnecessarily.
|
A stream can depend on any number of streams and it's guaranteed to update atomically. For example, if a stream A has two dependent streams B and C, and a fourth stream D is dependent on both B and C, the stream D will only update once if the value of A changes. This guarantees that the callback for stream D is never called with unstable values such as when B has a new value but C has the old value. Atomicity also brings the performance benefits of not recomputing downstreams unnecessarily.
|
||||||
|
|
||||||
You can prevent dependent streams from being updated by returning the special value `stream.HALT`
|
You can prevent dependent streams from being updated by returning the special value `stream.SKIP`
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
var halted = stream.combine(function(stream) {
|
var skipped = stream.combine(function(stream) {
|
||||||
return stream.HALT
|
return stream.SKIP
|
||||||
}, [stream(1)])
|
}, [stream(1)])
|
||||||
|
|
||||||
halted.map(function() {
|
skipped.map(function() {
|
||||||
// never runs
|
// never runs
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
# Change log for stream
|
# Change log for stream
|
||||||
|
|
||||||
## 2.0.0
|
## 2.0.0
|
||||||
|
- renamed HALT to SKIP [#2207](https://github.com/MithrilJS/mithril.js/pull/2207)
|
||||||
|
- rewrote implementation [#2207](https://github.com/MithrilJS/mithril.js/pull/2207)
|
||||||
- stream: Removed `valueOf` & `toString` methods ([#2150](https://github.com/MithrilJS/mithril.js/pull/2150)
|
- stream: Removed `valueOf` & `toString` methods ([#2150](https://github.com/MithrilJS/mithril.js/pull/2150)
|
||||||
|
|
||||||
## 1.1.0
|
## 1.1.0
|
||||||
|
|
|
||||||
226
stream/stream.js
226
stream/stream.js
|
|
@ -2,150 +2,140 @@
|
||||||
;(function() {
|
;(function() {
|
||||||
"use strict"
|
"use strict"
|
||||||
/* eslint-enable */
|
/* eslint-enable */
|
||||||
|
Stream.SKIP = {}
|
||||||
|
Stream.lift = lift
|
||||||
|
Stream.scan = scan
|
||||||
|
Stream.merge = merge
|
||||||
|
Stream.combine = combine
|
||||||
|
Stream.scanMerge = scanMerge
|
||||||
|
Stream["fantasy-land/of"] = Stream
|
||||||
|
|
||||||
var guid = 0, HALT = {}
|
let warnedHalt = false
|
||||||
function createStream() {
|
Object.defineProperty(Stream, "HALT", {
|
||||||
function stream() {
|
get: function() {
|
||||||
if (arguments.length > 0 && arguments[0] !== HALT) updateStream(stream, arguments[0])
|
warnedHalt && console.log("HALT is deprecated and has been renamed to SKIP");
|
||||||
return stream._state.value
|
warnedHalt = true
|
||||||
|
return Stream.SKIP
|
||||||
}
|
}
|
||||||
initStream(stream)
|
})
|
||||||
|
|
||||||
if (arguments.length > 0 && arguments[0] !== HALT) updateStream(stream, arguments[0])
|
function Stream(value) {
|
||||||
|
var dependentStreams = []
|
||||||
|
var dependentFns = []
|
||||||
|
|
||||||
return stream
|
function stream(v) {
|
||||||
|
if (arguments.length && v !== Stream.SKIP && open(stream)) {
|
||||||
|
value = v
|
||||||
|
stream.changing()
|
||||||
|
stream.state = "active"
|
||||||
|
dependentStreams.forEach(function(s, i) { s(dependentFns[i](value)) })
|
||||||
}
|
}
|
||||||
function initStream(stream) {
|
|
||||||
stream.constructor = createStream
|
|
||||||
stream._state = {id: guid++, value: undefined, state: 0, derive: undefined, recover: undefined, deps: {}, parents: [], endStream: undefined, unregister: undefined}
|
|
||||||
stream.map = stream["fantasy-land/map"] = map, stream["fantasy-land/ap"] = ap, stream["fantasy-land/of"] = createStream
|
|
||||||
stream.toJSON = toJSON
|
|
||||||
|
|
||||||
Object.defineProperties(stream, {
|
return value
|
||||||
end: {get: function() {
|
}
|
||||||
if (!stream._state.endStream) {
|
|
||||||
var endStream = createStream()
|
stream.constructor = Stream
|
||||||
endStream.map(function(value) {
|
stream.state = arguments.length && value !== Stream.SKIP ? "active" : "pending"
|
||||||
|
|
||||||
|
stream.changing = function() {
|
||||||
|
open(stream) && (stream.state = "changing")
|
||||||
|
dependentStreams.forEach(function(s) {
|
||||||
|
s.dependent && s.dependent.changing()
|
||||||
|
s.changing()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.map = function(fn, ignoreInitial) {
|
||||||
|
var target = stream.state === "active" && ignoreInitial !== Stream.SKIP
|
||||||
|
? Stream(fn(value))
|
||||||
|
: Stream()
|
||||||
|
|
||||||
|
dependentStreams.push(target)
|
||||||
|
dependentFns.push(fn)
|
||||||
|
return target
|
||||||
|
}
|
||||||
|
|
||||||
|
let end
|
||||||
|
function createEnd() {
|
||||||
|
end = Stream()
|
||||||
|
end.map(function(value) {
|
||||||
if (value === true) {
|
if (value === true) {
|
||||||
unregisterStream(stream)
|
stream.state = "ended"
|
||||||
endStream._state.unregister = function(){unregisterStream(endStream)}
|
dependentStreams.length = dependentFns.length = 0
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
})
|
})
|
||||||
stream._state.endStream = endStream
|
return end
|
||||||
}
|
}
|
||||||
return stream._state.endStream
|
|
||||||
}}
|
stream.toJSON = function() { return value != null && typeof value.toJSON === "function" ? value.toJSON() : value }
|
||||||
|
|
||||||
|
stream["fantasy-land/map"] = stream.map
|
||||||
|
stream["fantasy-land/ap"] = function(x) { return combine(function(s1, s2) { return s1()(s2()) }, [x, stream]) }
|
||||||
|
|
||||||
|
Object.defineProperty(stream, "end", {
|
||||||
|
get: function() { return end || createEnd() }
|
||||||
})
|
})
|
||||||
}
|
|
||||||
function updateStream(stream, value) {
|
return stream
|
||||||
updateState(stream, value)
|
|
||||||
for (var id in stream._state.deps) updateDependency(stream._state.deps[id], false)
|
|
||||||
if (stream._state.unregister != null) stream._state.unregister()
|
|
||||||
finalize(stream)
|
|
||||||
}
|
|
||||||
function updateState(stream, value) {
|
|
||||||
stream._state.value = value
|
|
||||||
stream._state.changed = true
|
|
||||||
if (stream._state.state !== 2) stream._state.state = 1
|
|
||||||
}
|
|
||||||
function updateDependency(stream, mustSync) {
|
|
||||||
var state = stream._state, parents = state.parents
|
|
||||||
if (parents.length > 0 && parents.every(active) && (mustSync || parents.some(changed))) {
|
|
||||||
var value = stream._state.derive()
|
|
||||||
if (value === HALT) return unregisterStream(stream)
|
|
||||||
updateState(stream, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function finalize(stream) {
|
|
||||||
stream._state.changed = false
|
|
||||||
for (var id in stream._state.deps) stream._state.deps[id]._state.changed = false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function combine(fn, streams) {
|
function combine(fn, streams) {
|
||||||
if (!streams.every(valid)) throw new Error("Ensure that each item passed to stream.combine/merge/lift is a stream")
|
var ready = streams.every(function(s) {
|
||||||
return initDependency(createStream(), streams, function() {
|
if (s.constructor !== Stream)
|
||||||
return fn.apply(this, streams.concat([streams.filter(changed)]))
|
throw new Error("Ensure that each item passed to stream.combine/stream.merge/lift is a stream")
|
||||||
|
return s.state === "active"
|
||||||
})
|
})
|
||||||
}
|
var stream = ready
|
||||||
|
? Stream(fn.apply(null, streams.concat([streams])))
|
||||||
|
: Stream()
|
||||||
|
|
||||||
function initDependency(dep, streams, derive) {
|
let changed = []
|
||||||
var state = dep._state
|
|
||||||
state.derive = derive
|
|
||||||
state.parents = streams.filter(notEnded)
|
|
||||||
|
|
||||||
registerDependency(dep, state.parents)
|
streams.forEach(function(s) {
|
||||||
updateDependency(dep, true)
|
s.map(function(value) {
|
||||||
|
changed.push(s)
|
||||||
|
if (ready || streams.every(function(s) { return s.state !== "pending" })) {
|
||||||
|
ready = true
|
||||||
|
stream(fn.apply(null, streams.concat([changed])))
|
||||||
|
changed = []
|
||||||
|
}
|
||||||
|
return value
|
||||||
|
}, Stream.SKIP).parent = stream
|
||||||
|
})
|
||||||
|
|
||||||
return dep
|
return stream
|
||||||
}
|
}
|
||||||
function registerDependency(stream, parents) {
|
|
||||||
for (var i = 0; i < parents.length; i++) {
|
|
||||||
parents[i]._state.deps[stream._state.id] = stream
|
|
||||||
registerDependency(stream, parents[i]._state.parents)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function unregisterStream(stream) {
|
|
||||||
for (var i = 0; i < stream._state.parents.length; i++) {
|
|
||||||
var parent = stream._state.parents[i]
|
|
||||||
delete parent._state.deps[stream._state.id]
|
|
||||||
}
|
|
||||||
for (var id in stream._state.deps) {
|
|
||||||
var dependent = stream._state.deps[id]
|
|
||||||
var index = dependent._state.parents.indexOf(stream)
|
|
||||||
if (index > -1) dependent._state.parents.splice(index, 1)
|
|
||||||
}
|
|
||||||
stream._state.state = 2 //ended
|
|
||||||
stream._state.deps = {}
|
|
||||||
}
|
|
||||||
|
|
||||||
function map(fn) {return combine(function(stream) {return fn(stream())}, [this])}
|
|
||||||
function ap(stream) {return combine(function(s1, s2) {return s1()(s2())}, [stream, this])}
|
|
||||||
function toJSON() {return this._state.value != null && typeof this._state.value.toJSON === "function" ? this._state.value.toJSON() : this._state.value}
|
|
||||||
|
|
||||||
function valid(stream) {return stream._state }
|
|
||||||
function active(stream) {return stream._state.state === 1}
|
|
||||||
function changed(stream) {return stream._state.changed}
|
|
||||||
function notEnded(stream) {return stream._state.state !== 2}
|
|
||||||
|
|
||||||
function merge(streams) {
|
function merge(streams) {
|
||||||
return combine(function() {
|
return combine(function() { return streams.map(function(s) { return s() }) }, streams)
|
||||||
return streams.map(function(s) {return s()})
|
|
||||||
}, streams)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function scan(reducer, seed, stream) {
|
function scan(fn, acc, origin) {
|
||||||
var newStream = combine(function (s) {
|
var stream = origin.map(function(v) {
|
||||||
var next = reducer(seed, s._state.value)
|
acc = fn(acc, v)
|
||||||
if (next !== HALT) return seed = next
|
return acc
|
||||||
return HALT
|
})
|
||||||
}, [stream])
|
stream(acc)
|
||||||
|
return stream
|
||||||
if (newStream._state.state === 0) newStream(seed)
|
|
||||||
|
|
||||||
return newStream
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function scanMerge(tuples, seed) {
|
function scanMerge(tuples, seed) {
|
||||||
var streams = tuples.map(function(tuple) {
|
var streams = tuples.map(function(tuple) { return tuple[0] })
|
||||||
var stream = tuple[0]
|
|
||||||
if (stream._state.state === 0) stream(undefined)
|
|
||||||
return stream
|
|
||||||
})
|
|
||||||
|
|
||||||
var newStream = combine(function() {
|
var stream = combine(function() {
|
||||||
var changed = arguments[arguments.length - 1]
|
var changed = arguments[arguments.length - 1]
|
||||||
|
streams.forEach(function(stream, i) {
|
||||||
streams.forEach(function(stream, idx) {
|
if (changed.indexOf(stream) > -1)
|
||||||
if (changed.indexOf(stream) > -1) {
|
seed = tuples[i][1](seed, stream())
|
||||||
seed = tuples[idx][1](seed, stream._state.value)
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return seed
|
return seed
|
||||||
}, streams)
|
}, streams)
|
||||||
|
|
||||||
return newStream
|
stream(seed)
|
||||||
|
|
||||||
|
return stream
|
||||||
}
|
}
|
||||||
|
|
||||||
function lift() {
|
function lift() {
|
||||||
|
|
@ -156,16 +146,12 @@ function lift() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
createStream["fantasy-land/of"] = createStream
|
function open(s) {
|
||||||
createStream.merge = merge
|
return s.state === "pending" || s.state === "active" || s.state === "changing"
|
||||||
createStream.combine = combine
|
}
|
||||||
createStream.scan = scan
|
|
||||||
createStream.scanMerge = scanMerge
|
|
||||||
createStream.lift = lift
|
|
||||||
createStream.HALT = HALT
|
|
||||||
|
|
||||||
if (typeof module !== "undefined") module["exports"] = createStream
|
if (typeof module !== "undefined") module["exports"] = Stream
|
||||||
else if (typeof window.m === "function" && !("stream" in window.m)) window.m.stream = createStream
|
else if (typeof window.m === "function" && !("stream" in window.m)) window.m.stream = Stream
|
||||||
else window.m = {stream : createStream}
|
else window.m = {stream : Stream}
|
||||||
|
|
||||||
}());
|
}());
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@ o.spec("scan", function() {
|
||||||
o(result[3]).deepEquals({a: 1})
|
o(result[3]).deepEquals({a: 1})
|
||||||
})
|
})
|
||||||
|
|
||||||
o("reducer can return HALT to prevent child updates", function() {
|
o("reducer can return SKIP to prevent child updates", function() {
|
||||||
var count = 0
|
var count = 0
|
||||||
var action = stream()
|
var action = stream()
|
||||||
var store = stream.scan(function (arr, value) {
|
var store = stream.scan(function (arr, value) {
|
||||||
|
|
@ -39,7 +39,7 @@ o.spec("scan", function() {
|
||||||
case "number":
|
case "number":
|
||||||
return arr.concat(value)
|
return arr.concat(value)
|
||||||
default:
|
default:
|
||||||
return stream.HALT
|
return stream.SKIP
|
||||||
}
|
}
|
||||||
}, [], action)
|
}, [], action)
|
||||||
var child = store.map(function (p) {
|
var child = store.map(function (p) {
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,43 @@ o.spec("stream", function() {
|
||||||
|
|
||||||
o(stream()()).equals(1)
|
o(stream()()).equals(1)
|
||||||
})
|
})
|
||||||
|
o("can SKIP", function() {
|
||||||
|
var a = Stream(2)
|
||||||
|
var b = a.map(function(value) {
|
||||||
|
return value === 5
|
||||||
|
? Stream.SKIP
|
||||||
|
: value
|
||||||
|
})
|
||||||
|
|
||||||
|
a(5)
|
||||||
|
|
||||||
|
o(b()).equals(2)
|
||||||
|
})
|
||||||
|
o("can HALT", function() {
|
||||||
|
var a = Stream(2)
|
||||||
|
var b = a.map(function(value) {
|
||||||
|
return value === 5
|
||||||
|
? Stream.HALT
|
||||||
|
: value
|
||||||
|
})
|
||||||
|
|
||||||
|
a(5)
|
||||||
|
|
||||||
|
o(b()).equals(2)
|
||||||
|
})
|
||||||
|
o("warns HALT deprecated", function() {
|
||||||
|
var log = console.log
|
||||||
|
var warning = ""
|
||||||
|
console.log = function(a) {
|
||||||
|
warning = a
|
||||||
|
}
|
||||||
|
|
||||||
|
Stream.HALT
|
||||||
|
|
||||||
|
console.log = log
|
||||||
|
|
||||||
|
o(warning).equals("HALT is deprecated and has been renamed to SKIP")
|
||||||
|
})
|
||||||
})
|
})
|
||||||
o.spec("combine", function() {
|
o.spec("combine", function() {
|
||||||
o("transforms value", function() {
|
o("transforms value", function() {
|
||||||
|
|
@ -87,6 +124,7 @@ o.spec("stream", function() {
|
||||||
o(d()).equals(15)
|
o(d()).equals(15)
|
||||||
o(count).equals(1)
|
o(count).equals(1)
|
||||||
})
|
})
|
||||||
|
|
||||||
o("combines default value atomically", function() {
|
o("combines default value atomically", function() {
|
||||||
var count = 0
|
var count = 0
|
||||||
var a = Stream(3)
|
var a = Stream(3)
|
||||||
|
|
@ -100,6 +138,21 @@ o.spec("stream", function() {
|
||||||
o(d()).equals(15)
|
o(d()).equals(15)
|
||||||
o(count).equals(1)
|
o(count).equals(1)
|
||||||
})
|
})
|
||||||
|
o("combines and maps nested streams atomically", function() {
|
||||||
|
var count = 0
|
||||||
|
var a = Stream(3)
|
||||||
|
var b = Stream.combine(function(a) {return a() * 2}, [a])
|
||||||
|
var c = Stream.combine(function(a) {return a() * a()}, [a])
|
||||||
|
var d = c.map(function(x){return x})
|
||||||
|
var e = Stream.combine(function(x) {return x()}, [d])
|
||||||
|
var f = Stream.combine(function(b, e) {
|
||||||
|
count++
|
||||||
|
return b() + e()
|
||||||
|
}, [b, e])
|
||||||
|
|
||||||
|
o(f()).equals(15)
|
||||||
|
o(count).equals(1)
|
||||||
|
})
|
||||||
o("combine lists only changed upstreams in last arg", function() {
|
o("combine lists only changed upstreams in last arg", function() {
|
||||||
var streams = []
|
var streams = []
|
||||||
var a = Stream()
|
var a = Stream()
|
||||||
|
|
@ -111,8 +164,22 @@ o.spec("stream", function() {
|
||||||
a(3)
|
a(3)
|
||||||
b(5)
|
b(5)
|
||||||
|
|
||||||
o(streams.length).equals(1)
|
o(streams.length).equals(2)
|
||||||
o(streams[0]).equals(b)
|
o(streams[0]).equals(a)
|
||||||
|
o(streams[1]).equals(b)
|
||||||
|
})
|
||||||
|
o("combine continues with ended streams", function() {
|
||||||
|
var a = Stream()
|
||||||
|
var b = Stream()
|
||||||
|
var combined = Stream.combine(function(a, b) {
|
||||||
|
return a() + b()
|
||||||
|
}, [a, b])
|
||||||
|
|
||||||
|
a(3)
|
||||||
|
a.end(true)
|
||||||
|
b(5)
|
||||||
|
|
||||||
|
o(combined()).equals(8)
|
||||||
})
|
})
|
||||||
o("combine lists only changed upstreams in last arg with default value", function() {
|
o("combine lists only changed upstreams in last arg with default value", function() {
|
||||||
var streams = []
|
var streams = []
|
||||||
|
|
@ -151,11 +218,11 @@ o.spec("stream", function() {
|
||||||
|
|
||||||
o(b()()).equals(undefined)
|
o(b()()).equals(undefined)
|
||||||
})
|
})
|
||||||
o("combine can halt", function() {
|
o("combine can skip", function() {
|
||||||
var count = 0
|
var count = 0
|
||||||
var a = Stream(1)
|
var a = Stream(1)
|
||||||
var b = Stream.combine(function() {
|
var b = Stream.combine(function() {
|
||||||
return Stream.HALT
|
return Stream.SKIP
|
||||||
}, [a])["fantasy-land/map"](function() {
|
}, [a])["fantasy-land/map"](function() {
|
||||||
count++
|
count++
|
||||||
return 1
|
return 1
|
||||||
|
|
@ -164,13 +231,13 @@ o.spec("stream", function() {
|
||||||
o(b()).equals(undefined)
|
o(b()).equals(undefined)
|
||||||
o(count).equals(0)
|
o(count).equals(0)
|
||||||
})
|
})
|
||||||
o("combine can conditionaly halt", function() {
|
o("combine can conditionaly skip", function() {
|
||||||
var count = 0
|
var count = 0
|
||||||
var halt = false
|
var skip = false
|
||||||
var a = Stream(1)
|
var a = Stream(1)
|
||||||
var b = Stream.combine(function(a) {
|
var b = Stream.combine(function(a) {
|
||||||
if (halt) {
|
if (skip) {
|
||||||
return Stream.HALT
|
return Stream.SKIP
|
||||||
}
|
}
|
||||||
return a()
|
return a()
|
||||||
}, [a])["fantasy-land/map"](function(a) {
|
}, [a])["fantasy-land/map"](function(a) {
|
||||||
|
|
@ -179,7 +246,7 @@ o.spec("stream", function() {
|
||||||
})
|
})
|
||||||
o(b()).equals(1)
|
o(b()).equals(1)
|
||||||
o(count).equals(1)
|
o(count).equals(1)
|
||||||
halt = true
|
skip = true
|
||||||
count = 0
|
count = 0
|
||||||
a(2)
|
a(2)
|
||||||
o(b()).equals(1)
|
o(b()).equals(1)
|
||||||
|
|
@ -554,7 +621,7 @@ o.spec("stream", function() {
|
||||||
})
|
})
|
||||||
o.spec("applicative", function() {
|
o.spec("applicative", function() {
|
||||||
o("identity", function() {
|
o("identity", function() {
|
||||||
var a = Stream()["fantasy-land/of"](function(value) {return value})
|
var a = Stream["fantasy-land/of"](function(value) {return value})
|
||||||
var v = Stream(5)
|
var v = Stream(5)
|
||||||
|
|
||||||
o(v["fantasy-land/ap"](a)()).equals(5)
|
o(v["fantasy-land/ap"](a)()).equals(5)
|
||||||
|
|
@ -565,16 +632,16 @@ o.spec("stream", function() {
|
||||||
var f = function(value) {return value * 2}
|
var f = function(value) {return value * 2}
|
||||||
var x = 3
|
var x = 3
|
||||||
|
|
||||||
o(a["fantasy-land/of"](x)["fantasy-land/ap"](a["fantasy-land/of"](f))()).equals(6)
|
o(a.constructor["fantasy-land/of"](x)["fantasy-land/ap"](a.constructor["fantasy-land/of"](f))()).equals(6)
|
||||||
o(a["fantasy-land/of"](x)["fantasy-land/ap"](a["fantasy-land/of"](f))()).equals(a["fantasy-land/of"](f(x))())
|
o(a.constructor["fantasy-land/of"](x)["fantasy-land/ap"](a.constructor["fantasy-land/of"](f))()).equals(a.constructor["fantasy-land/of"](f(x))())
|
||||||
})
|
})
|
||||||
o("interchange", function() {
|
o("interchange", function() {
|
||||||
var u = Stream(function(value) {return value * 2})
|
var u = Stream(function(value) {return value * 2})
|
||||||
var a = Stream()
|
var a = Stream()
|
||||||
var y = 3
|
var y = 3
|
||||||
|
|
||||||
o(a["fantasy-land/of"](y)["fantasy-land/ap"](u)()).equals(6)
|
o(a.constructor["fantasy-land/of"](y)["fantasy-land/ap"](u)()).equals(6)
|
||||||
o(a["fantasy-land/of"](y)["fantasy-land/ap"](u)()).equals(u["fantasy-land/ap"](a["fantasy-land/of"](function(f) {return f(y)}))())
|
o(a.constructor["fantasy-land/of"](y)["fantasy-land/ap"](u)()).equals(u["fantasy-land/ap"](a.constructor["fantasy-land/of"](function(f) {return f(y)}))())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue