---
description: extends iterables with custom methods.
---

# Iterable+ext

[JS](..)⟩ [iteration]() ⟩ [iterable](iterable) ⟩ extension

{% hint style="success" %}
extends [iterables](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) with methods such as <mark style="color:yellow;">`filter`</mark>, <mark style="color:yellow;">`map`</mark>, <mark style="color:yellow;">`take`</mark>, ... etc.

:point\_right: [custom](../../appendix/custom "mention")

```javascript
// ⭐️ check if iterable
array.isIterable,    // true
object.isIterable,   // false
(3).isIterable,      // false

// ⭐️ extended methods
'xyz'.forEach(x => log(x));    // x, y, z
'abc'.map(x=>x+2).array,       // [ 'a2', 'b2', 'c2' ]
'abcdefg'.take(4).array,       // [ 'a', 'b', 'c', 'd' ]
'abcd'.some(x => x > 'm'),     // false

// ⭐️ (closed) range
range(0, 3).map(x => x * 2).array,     // [0, 2, 4, 6]
range(0, 10).every(x => x < 10),       // false
```
{% endhint %}

{% tabs %}
{% tab title="💾 程式" %}
* replit ⟩ [Iterable.js](https://replit.com/@pegasusroe/Iterablejs#ext/Iterable.js)

```javascript
// 2023.01.21 - 18:42 (?) first recorded, rename Iterable+ext.js
// ----------------------------------------

//
// ⭐ Iterable + ext
// ----------------------------------------
// - range()
// ----------------------------------------
// 🔸 .isIterable   -> boolean
// 🔸 .array        -> array
// ----------------------------------------
// 🔹 .filter()     -> it
// 🔹 .map()        -> it
// 🔹 .take()       -> it
// 🔹 .reduce()     -> any
// ----------------------------------------
// 🔹 .every()      -> boolean
// 🔹 .some()       -> boolean
// 🔹 .forEach()    -> void
// 🔹 .toArray()    -> array
//
Object.defineProperties(Object.prototype, {

    // 🔸 obj.isIterable
    isIterable: {
        get() {
            return typeof this[Symbol.iterator] === 'function';
        }
    },

    // 🔸 it.array
    array: {
        get: function() {
            this._checkIterable();
            return [...this];
        }
    },

    // obj._iterableValues
    _checkIterable: {
        value: function() {
            // throw error if not iterable
            if (!this.isIterable) {
                throw new TypeError(`object is not iterable!`);
            }
        }
    },

    // 🔹 it.map(f)
    map: {
        value: function(f) {
            this._checkIterable();
            const values = this;
            // return a mapped iterable
            return {
                // make iterabor (by using generator function)
                *[Symbol.iterator]() {
                    for (const val of values) yield f(val);
                }
            }
        }
    },

    // 🔹 it.filter(f)
    filter: {
        value: function(condition) {
            this._checkIterable();
            const values = this;
            // return a filtered iterable
            return {
                // make iterabor (by using generator function)
                *[Symbol.iterator]() {
                    for (const val of values) if (condition(val)) yield val;
                }
            }
        }
    },

    // 🔹 it.take(n)
    take: {
        value: function(n) {
            this._checkIterable();
            const values = this;
            // return a partial iterable
            return {
                // make iterabor (by using generator function)
                *[Symbol.iterator]() {
                    for (const val of values) {
                        if (n > 0) {
                            n -= 1;
                            yield val;
                        } else { return }
                    }
                }
            }
        }
    },

    // 🔹 it.forEach(f)
    forEach: {
        value: function(f) {
            this._checkIterable();
            for (const val of this) f(val);
        }
    },

    // 🔹 it.reduce()
    reduce: {
        value: function(...args) {
            this._checkIterable();
            return [...this].reduce(...args);
        }
    },

    // 🔹 it.toArray()
    toArray: {
        value: function() {
            this._checkIterable();
            return [...this];
        }
    },

    // 🔹 it.every(condition)
    every: {
        value: function(condition) {
            this._checkIterable();
            for (const val of this) if (!condition(val)) return false;
            return true;
        }
    },

    // 🔹 it.some(condition)
    some: {
        value: function(condition) {
            this._checkIterable();
            for (const val of this) if (condition(val)) return true;
            return false;
        }
    },

});

// ⭐️ closed range: [start, end]
function range(start, end, step = start < end ? 1 : -1) {
    
    function isInRange(i) {
        return (step > 0) ? (start <= i && i <= end) : (end <= i && i <= start);
    }
    
    // return an iterable
    return {
        *[Symbol.iterator]() {
            for (let i = start; isInRange(i) ; i += step) {
                yield i;
            }
        }
    }
}

// export
// module.exports = { range };
export { range };
```
{% endtab %}

{% tab title="💈範例" %}
* replit ⟩ [index.js](https://replit.com/@pegasusroe/Iterablejs#index.js)&#x20;

```javascript
'use strict';                // ⭐ toggle sloppy/strict mode
const { log } = console;

// ⭐ import
const { logError } = require('./helpers/debug.js');  // functions for debugging
const { isObject } = require('./ext/Object_ext.js');    // extend Object.prototype
const { range } = require('./ext/Iterable.js');    // iterable methods
// ---------------------------------------------------------------------------

const array = [1, 2, 3];
const string = "Hello, world!";
const map = new Map();
const set = new Set();
const object = {};

'xyz'.forEach(x => log(x));    // x, y, z

// ✅ log expressions that never throw
// ---------------------------------------------------------------------------
;[
    array.isIterable,        // ○
    string.isIterable,       // ○
    map.isIterable,          // ○
    set.isIterable,          // ○
    
    object.isIterable,                 // ⨉
    (3).isIterable,                    // ⨉

    range(0, 10).every(x => x < 10),   // ⨉
    'abcd'.some(x => x > 'm'),         // ⨉
    
].forEach(x => log(x));

// ✅ list iterables -> array
[
    range(1, 3),       // [ 1, 2, 3 ]
    range(3, -2),      // [ 3, 2, 1, 0, -1, -2 ]
    range(1, 3, 0.5),  // [ 1, 1.5, 2, 2.5, 3 ]

    range(0, 10).take(4),            // [0, 1, 2, 3]
    range(0, 3).map(x => x * 2),     // [0, 2, 4, 6]

    'abcd',                          // [ 'a', 'b', 'c', 'd' ]
    'abc'.map(x => x + 1),           // [ 'a1', 'b1', 'c1' ]

    'abcdefg'.filter(x => x < 'd'),  // [ 'a', 'b', 'c' ]
    'abcdefg'.take(4),               // [ 'a', 'b', 'c', 'd' ]

].forEach(x => log(x.array));

// ❗ log expressios that may throw
// ---------------------------------------------------------------------------
[
    `object.map(x => x)`,           // ⛔ TypeError: 'object' not iterable!

].forEach(exprStr => {
    try { log(eval(exprStr)) } catch (e) { logError(e) }
});
```
{% endtab %}

{% tab title="🔴 主題" %}
* [range](iterable+ext/range "mention") - half-open range of numbers. (iterable)
* [isiterable](iterable+ext/isiterable "mention") - check if an object is iterable.
{% endtab %}

{% tab title="👥 相關" %}
* [iterator](iterator/iterator "mention") extension (<mark style="color:red;">deprecated</mark>)
{% endtab %}

{% tab title="⬇️ 應用" %}
* [+ext](../../browser/canvas/+ext "mention") - use `range`.
* [grapheme-cluster](../val/prim/str/unicode/grapheme-cluster "mention")
{% endtab %}

{% tab title="📗 參考" %}
* [ ] [javascript-the-definitive-guide](../../master/ref/javascript-the-definitive-guide "mention") ⟩&#x20;
  * [ ] 12.2 Implementing Iterable Objects&#x20;
  * [ ] 12.3.1 Generator Examples (zip, ...)
{% endtab %}

{% tab title="📘 手冊" %}
* [Iteration protocols](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols)
{% endtab %}
{% endtabs %}

## History

{% tabs %}
{% tab title="idx" %}
```
0: (?)
```
{% endtab %}

{% tab title="0" %}
```javascript
// ⭐ extending iterables (`it` for short)
// ----------------------------------------
// 🔸 .isIterable   -> boolean
// 🔸 .array        -> array

// 🔹 .filter()     -> it
// 🔹 .map()        -> it
// 🔹 .take()       -> it
// 🔹 .reduce()     -> any
//
// 🔹 .every()      -> boolean
// 🔹 .some()       -> boolean
// 🔹 .forEach()    -> void
// 🔹 .toArray()    -> array

// ⭐ helper functions
// ----------------------------------------
// • 🔹 range(start, end, step): iterable

// Iterable (protocol)
Object.defineProperties(Object.prototype, {

    // 🔸 obj.isIterable
    isIterable: {
        get() {
            return typeof this[Symbol.iterator] === 'function';
        }
    },

    // 🔸 it.array
    array: {
        get: function() {
            this._checkIterable();
            return [...this];
        }
    },

    // obj._iterableValues
    _checkIterable: {
        value: function() {
            // throw error if not iterable
            if (!this.isIterable) {
                throw new TypeError(`object is not iterable!`);
            }
        }
    },

    // 🔹 it.map(f)
    map: {
        value: function(f) {
            this._checkIterable();
            const values = this;
            // return a mapped iterable
            return {
                // make iterabor (by using generator function)
                *[Symbol.iterator]() {
                    for (const val of values) yield f(val);
                }
            }
        }
    },

    // 🔹 it.filter(f)
    filter: {
        value: function(condition) {
            this._checkIterable();
            const values = this;
            // return a filtered iterable
            return {
                // make iterabor (by using generator function)
                *[Symbol.iterator]() {
                    for (const val of values) if (condition(val)) yield val;
                }
            }
        }
    },

    // 🔹 it.take(n)
    take: {
        value: function(n) {
            this._checkIterable();
            const values = this;
            // return a partial iterable
            return {
                // make iterabor (by using generator function)
                *[Symbol.iterator]() {
                    for (const val of values) {
                        if (n > 0) {
                            n -= 1;
                            yield val;
                        } else { return }
                    }
                }
            }
        }
    },

    // 🔹 it.forEach(f)
    forEach: {
        value: function(f) {
            this._checkIterable();
            for (const val of this) f(val);
        }
    },

    // 🔹 it.reduce()
    reduce: {
        value: function(...args) {
            this._checkIterable();
            return [...this].reduce(...args);
        }
    },

    // 🔹 it.toArray()
    toArray: {
        value: function() {
            this._checkIterable();
            return [...this];
        }
    },

    // 🔹 it.every(condition)
    every: {
        value: function(condition) {
            this._checkIterable();
            for (const val of this) if (!condition(val)) return false;
            return true;
        }
    },

    // 🔹 it.some(condition)
    some: {
        value: function(condition) {
            this._checkIterable();
            for (const val of this) if (condition(val)) return true;
            return false;
        }
    },

});

// ⭐️ closed range: [start, end]
function range(start, end, step = start < end ? 1 : -1) {
    
    function isInRange(i) {
        return (step > 0) ? (start <= i && i <= end) : (end <= i && i <= start);
    }
    
    // return an iterable
    return {
        *[Symbol.iterator]() {
            for (let i = start; isInRange(i) ; i += step) {
                yield i;
            }
        }
    }
}

// export
module.exports = { range };
```
{% endtab %}
{% endtabs %}
