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:
Alex A. Naanou 2021-05-25 17:21:40 +03:00
parent efefaaffb9
commit 4277580689
5 changed files with 288 additions and 20 deletions

View File

@ -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
View File

@ -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

View File

@ -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() } })
/**********************************************************************

View File

@ -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

View File

@ -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": {