mirror of
https://github.com/flynx/types.js.git
synced 2025-10-29 02:20:07 +00:00
generator .map(..) and friends now support STOPing + added basic generator combinators...
Signed-off-by: Alex A. Naanou <alex.nanou@gmail.com>
This commit is contained in:
parent
efefaaffb9
commit
4277580689
3
Array.js
3
Array.js
@ -21,6 +21,7 @@ var generator = require('./generator')
|
||||
|
||||
// NOTE: this is used in a similar fashion to Python's StopIteration...
|
||||
var STOP =
|
||||
module.STOP =
|
||||
object.STOP
|
||||
|
||||
|
||||
@ -42,7 +43,7 @@ var wrapIterFunc = function(iter){
|
||||
} catch(err){
|
||||
if(err === STOP){
|
||||
return
|
||||
} else if( err instanceof STOP){
|
||||
} else if(err instanceof STOP){
|
||||
return err.value }
|
||||
throw err } } }
|
||||
|
||||
|
||||
150
README.md
150
README.md
@ -74,6 +74,7 @@ A library of JavaScript type extensions, types and type utilities.
|
||||
- [The basics](#the-basics)
|
||||
- [`generator.Generator`](#generatorgenerator)
|
||||
- [`generator.iter(..)`](#generatoriter)
|
||||
- [`generator.STOP`](#generatorstop)
|
||||
- [Generator instance iteration](#generator-instance-iteration)
|
||||
- [`<generator>.map(..)` / `<generator>.filter(..)` / `<generator>.reduce(..)`](#generatormap--generatorfilter--generatorreduce)
|
||||
- [`<generator>.slice(..)`](#generatorslice)
|
||||
@ -90,6 +91,15 @@ A library of JavaScript type extensions, types and type utilities.
|
||||
- [`<Generator>.map(..)` / `<Generator>.filter(..)` / `<Generator>.reduce(..)` / `<Generator>.flat()`](#generatormap--generatorfilter--generatorreduce--generatorflat)
|
||||
- [`<Generator>.toArray()`](#generatortoarray-1)
|
||||
- [`<Generator>.then(..)` / `<Generator>.catch(..)` / `<Generator>.finally(..)`](#generatorthen--generatorcatch--generatorfinally-1)
|
||||
- [Generator combinators](#generator-combinators)
|
||||
- [`<Generator>.chain(..)` / `<generator>.chain(..)`](#generatorchain--generatorchain)
|
||||
- [`<Generator>.concat(..)` / `<generator>.concat(..)`](#generatorconcat--generatorconcat)
|
||||
- [Generator library](#generator-library)
|
||||
- [`generator.range(..)`](#generatorrange)
|
||||
- [`generator.repeat(..)`](#generatorrepeat)
|
||||
- [`generator.produce(..)`](#generatorproduce)
|
||||
- [Generator helpers](#generator-helpers)
|
||||
- [`generator.stoppable(..)`](#generatorstoppable)
|
||||
- [Containers](#containers)
|
||||
- [`containers.UniqueKeyMap()` (`Map`)](#containersuniquekeymap-map)
|
||||
- [`<unique-key-map>.set(..)`](#unique-key-mapset)
|
||||
@ -1625,6 +1635,12 @@ But `Generator()` takes no arguments and thus can not be used as a wrapper
|
||||
while `.iter(..)` is designed to accept an iterable value like an array object.
|
||||
|
||||
|
||||
#### `generator.STOP`
|
||||
|
||||
<!-- XXX -->
|
||||
|
||||
|
||||
|
||||
### Generator instance iteration
|
||||
|
||||
This is a set of `Array`-like iterator methods that enable chaining of
|
||||
@ -1651,6 +1667,22 @@ var L = [1,2,3]
|
||||
.toArray()
|
||||
```
|
||||
|
||||
Throwing `STOP` form within the handler will stop generation, throwing
|
||||
`STOP(<value>)` will yield the `<value>` then stop.
|
||||
```javascript
|
||||
var stopAt = function(n){
|
||||
return function(e){
|
||||
if(e == n){
|
||||
// stop iteration yielding the value we are stopping at...
|
||||
throw generator.STOP(e) }
|
||||
return e } }
|
||||
|
||||
var L = [1,2,3,4,5]
|
||||
.iter()
|
||||
.map(stopAt(3))
|
||||
.toArray()
|
||||
```
|
||||
|
||||
<!--
|
||||
XXX .reduce(..) can return a non-iterable -- test and document this case...
|
||||
...compare with Array.prototype.reduce(..)
|
||||
@ -1900,6 +1932,124 @@ Return a function that will return a `<generator>` output as an `Array`.
|
||||
|
||||
|
||||
|
||||
### Generator combinators
|
||||
|
||||
<!-- XXX -->
|
||||
|
||||
#### `<Generator>.chain(..)` / `<generator>.chain(..)`
|
||||
|
||||
```bnf
|
||||
<Generator>.chain(<Generator>, ..)
|
||||
-> <Generator>
|
||||
|
||||
<generator>.chain(<Generator>, ..)
|
||||
-> <generator>
|
||||
```
|
||||
<!-- XXX -->
|
||||
|
||||
```javascript
|
||||
// double each element...
|
||||
var x2 = generator.iter
|
||||
.map(function(e){ return e * 2 })
|
||||
|
||||
generator.range(0, 100).chain(x2)
|
||||
```
|
||||
|
||||
#### `<Generator>.concat(..)` / `<generator>.concat(..)`
|
||||
|
||||
Concatenate the results from generators
|
||||
```bnf
|
||||
<Generator>.concat(<Generator>, ..)
|
||||
-> <Generator>
|
||||
|
||||
<generator>.concat(<generator>, ..)
|
||||
-> <generator>
|
||||
```
|
||||
|
||||
|
||||
<!-- XXX Example -->
|
||||
|
||||
|
||||
<!-- XXX
|
||||
#### generator.zip(..) / `<Generator>.zip(..)` / `<generator>.zip(..)`
|
||||
-->
|
||||
|
||||
<!-- XXX
|
||||
#### `<Generator>.tee(..)` / `<generator>.tee(..)`
|
||||
-->
|
||||
|
||||
|
||||
|
||||
### Generator library
|
||||
|
||||
#### `generator.range(..)`
|
||||
|
||||
Create a generator yielding a range of numbers
|
||||
```bnf
|
||||
range()
|
||||
range(<to>)
|
||||
range(<from>, <to>)
|
||||
range(<from>, <to>, <step>)
|
||||
-> <generator>
|
||||
```
|
||||
|
||||
<!-- XXX examples... -->
|
||||
|
||||
|
||||
#### `generator.repeat(..)`
|
||||
|
||||
Create a generator repeatedly yielding `<value>`
|
||||
```bnf
|
||||
repeat()
|
||||
repeat(<value>)
|
||||
repeat(<value>, <stop>)
|
||||
-> <generator>
|
||||
|
||||
<stop>(<value>)
|
||||
-> <bool>
|
||||
```
|
||||
|
||||
If no value is given `true` is yielded by default.
|
||||
|
||||
`<stop>` if given will be called with each `<value>` before it is yielded and
|
||||
if it returns `false` the iteration is stopped.
|
||||
|
||||
<!-- XXX examples... -->
|
||||
|
||||
|
||||
#### `generator.produce(..)`
|
||||
|
||||
Create a generator calling a function to produce yielded values
|
||||
```bnf
|
||||
produce()
|
||||
produce(<func>)
|
||||
-> <generator>
|
||||
|
||||
<func>()
|
||||
-> <value>
|
||||
```
|
||||
|
||||
`<func>(..)` can `throw` `STOP` or `STOP(<value>)` to stop production at any time.
|
||||
|
||||
<!-- XXX examples... -->
|
||||
|
||||
|
||||
|
||||
### Generator helpers
|
||||
|
||||
#### `generator.stoppable(..)`
|
||||
|
||||
Wrap function/generator adding support for stopping mid-iteration by throwing `STOP`.
|
||||
|
||||
```bnf
|
||||
stoppable(<generator>)
|
||||
-> <generator>
|
||||
```
|
||||
|
||||
<!-- XXX example? -->
|
||||
|
||||
|
||||
|
||||
## Containers
|
||||
|
||||
```javascript
|
||||
|
||||
150
generator.js
150
generator.js
@ -12,7 +12,15 @@ var object = require('ig-object')
|
||||
|
||||
|
||||
/*********************************************************************/
|
||||
// The generator hirearchy in JS is a bit complicated.
|
||||
|
||||
// NOTE: this is used in a similar fashion to Python's StopIteration...
|
||||
var STOP =
|
||||
module.STOP =
|
||||
object.STOP
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// The generator hierarchy in JS is a bit complicated.
|
||||
//
|
||||
// Consider the following:
|
||||
//
|
||||
@ -93,29 +101,42 @@ Generator.iter =
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
// Helpers...
|
||||
|
||||
//
|
||||
// makeGenerator(<name>)
|
||||
// -> <func>
|
||||
//
|
||||
// makeGenerator(<name>, <handler>)
|
||||
// -> <func>
|
||||
//
|
||||
//
|
||||
// <func>(...args)
|
||||
// -> <Generator>
|
||||
//
|
||||
// <Generator>(...inputs)
|
||||
// -> <generator>
|
||||
//
|
||||
// <handler>(args, ...inputs)
|
||||
// -> args
|
||||
//
|
||||
//
|
||||
// XXX this needs to be of the correct type... (???)
|
||||
// XXX need to accept generators as handlers...
|
||||
var makeGenerator = function(name){
|
||||
var makeGenerator = function(name, pre){
|
||||
return function(...args){
|
||||
var that = this
|
||||
return Object.assign(
|
||||
function*(){
|
||||
// XXX need to accept generator as handler (i.e. args[0] instanceof Generator)...
|
||||
/*/ XXX EXPERIMENTAL...
|
||||
if(args[0] instanceof Generator){
|
||||
yield* that(...arguments)[name](...args)
|
||||
.map(function(g){
|
||||
return [...g] })
|
||||
.flat()
|
||||
return }
|
||||
//*/
|
||||
yield* that(...arguments)[name](...args) },
|
||||
var a = pre ?
|
||||
pre.call(this, args, ...arguments)
|
||||
: args
|
||||
yield* that(...arguments)[name](...a) },
|
||||
{ toString: function(){
|
||||
return [
|
||||
that.toString(),
|
||||
// XXX need to normalize args better...
|
||||
`.${ name }(${ args.join(', ') })`,
|
||||
].join('\n ') }, }) } }
|
||||
|
||||
// XXX do a better doc...
|
||||
var makePromise = function(name){
|
||||
return function(...args){
|
||||
@ -124,6 +145,34 @@ var makePromise = function(name){
|
||||
return that(...arguments)[name](func) } } }
|
||||
|
||||
|
||||
var stoppable =
|
||||
module.stoppable =
|
||||
function(func){
|
||||
return Object.assign(
|
||||
func instanceof Generator ?
|
||||
function*(){
|
||||
try{
|
||||
yield* func.call(this, ...arguments)
|
||||
} catch(err){
|
||||
if(err === STOP){
|
||||
return
|
||||
} else if(err instanceof STOP){
|
||||
yield err.value
|
||||
return }
|
||||
throw err } }
|
||||
: function(){
|
||||
try{
|
||||
return func.call(this, ...arguments)
|
||||
} catch(err){
|
||||
if(err === STOP){
|
||||
return
|
||||
} else if(err instanceof STOP){
|
||||
return err.value }
|
||||
throw err } },
|
||||
{ toString: function(){
|
||||
return func.toString() }, }) }
|
||||
|
||||
|
||||
// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
|
||||
|
||||
var GeneratorMixin =
|
||||
@ -183,6 +232,20 @@ object.Mixin('GeneratorMixin', 'soft', {
|
||||
then: makePromise('then'),
|
||||
catch: makePromise('catch'),
|
||||
finally: makePromise('finally'),
|
||||
|
||||
// combinators...
|
||||
//
|
||||
chain: makeGenerator('chain'),
|
||||
concat: makeGenerator('concat',
|
||||
// initialize arguments...
|
||||
function(next, ...args){
|
||||
return next
|
||||
.map(function(e){
|
||||
return (e instanceof Generator
|
||||
|| typeof(e) == 'function') ?
|
||||
e(...args)
|
||||
: e }) }),
|
||||
//zip: makeGenerator('zip'),
|
||||
})
|
||||
|
||||
|
||||
@ -242,24 +305,24 @@ object.Mixin('GeneratorProtoMixin', 'soft', {
|
||||
// will be expanded...
|
||||
// NOTE: there is no point to add generator-handler support to either
|
||||
// .filter(..) or .reduce(..)
|
||||
map: function*(func){
|
||||
map: stoppable(function*(func){
|
||||
var i = 0
|
||||
if(func instanceof Generator){
|
||||
for(var e of this){
|
||||
yield* func(e, i++, this) }
|
||||
} else {
|
||||
for(var e of this){
|
||||
yield func(e, i++, this) } } },
|
||||
filter: function*(func){
|
||||
yield func(e, i++, this) } } }),
|
||||
filter: stoppable(function*(func){
|
||||
var i = 0
|
||||
for(var e of this){
|
||||
if(func(e, i++, this)){
|
||||
yield e } } },
|
||||
reduce: function*(func, res){
|
||||
yield e } } }),
|
||||
reduce: stoppable(function*(func, res){
|
||||
var i = 0
|
||||
for(var e of this){
|
||||
res = func(res, e, i++, this) }
|
||||
yield res },
|
||||
yield res }),
|
||||
|
||||
pop: function(){
|
||||
return [...this].pop() },
|
||||
@ -291,8 +354,27 @@ object.Mixin('GeneratorProtoMixin', 'soft', {
|
||||
finally: function(func){
|
||||
return this.promise().finally(func) },
|
||||
|
||||
// combinators...
|
||||
//
|
||||
chain: function*(...next){
|
||||
yield* next
|
||||
.reduce(function(cur, next){
|
||||
return next(cur) }, this) },
|
||||
concat: function*(...next){
|
||||
yield* this
|
||||
for(var e of next){
|
||||
yield* e } },
|
||||
|
||||
// XXX EXPERIMENTAL...
|
||||
/* XXX not sure how to do this yet...
|
||||
tee: function*(...next){
|
||||
// XXX take the output of the current generator and feed it into
|
||||
// each of the next generators... (???)
|
||||
},
|
||||
zip: function*(...items){
|
||||
// XXX
|
||||
},
|
||||
//*/
|
||||
})
|
||||
|
||||
|
||||
@ -300,6 +382,38 @@ GeneratorMixin(GeneratorPrototype)
|
||||
GeneratorProtoMixin(GeneratorPrototype.prototype)
|
||||
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// Generators...
|
||||
|
||||
// NOTE: step can be 0, this will repeat the first element infinitely...
|
||||
var range =
|
||||
module.range =
|
||||
function*(from, to, step){
|
||||
if(to == null){
|
||||
to = from
|
||||
from = 0 }
|
||||
step = step ?? (from > to ? -1 : 1)
|
||||
while(step > 0 ?
|
||||
from < to
|
||||
: from > to){
|
||||
yield from
|
||||
from += step } }
|
||||
|
||||
|
||||
var repeat =
|
||||
module.repeat =
|
||||
function*(value=true, stop){
|
||||
while( typeof(stop) == 'function' && stop(value) ){
|
||||
yield value } }
|
||||
|
||||
|
||||
var produce =
|
||||
module.produce =
|
||||
stoppable(function*(func){
|
||||
while(true){
|
||||
yield func() } })
|
||||
|
||||
|
||||
|
||||
|
||||
/**********************************************************************
|
||||
|
||||
3
main.js
3
main.js
@ -7,6 +7,8 @@
|
||||
(function(require){ var module={} // make module AMD/node compatible...
|
||||
/*********************************************************************/
|
||||
|
||||
var object = require('ig-object')
|
||||
|
||||
// Extend built-in types...
|
||||
require('./Object')
|
||||
require('./Array')
|
||||
@ -26,6 +28,7 @@ module.runner = require('./runner')
|
||||
|
||||
|
||||
// Shorthands...
|
||||
module.STOP = object.STOP
|
||||
// NOTE: this is a generic enough type to be accessible at the root...
|
||||
module.Generator = module.generator.Generator
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ig-types",
|
||||
"version": "6.2.1",
|
||||
"version": "6.3.0",
|
||||
"description": "Generic JavaScript types and type extensions...",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user