String identity template tag

Isiah Meadows isiahmeadows at gmail.com
Tue Dec 18 22:30:03 UTC 2018


I could go with an iterator equivalent, but I'd like to defer that to
the seemingly-planned "iterlib" thing that's been considered since
before ES2015 was released. Something that works with arrays is good
enough for now.

BTW, your `ziperator` isn't really the same as my `Array.interpolate`
(which is better named `Array.interleave`). It needs to be this:

```js
function *ziperator(...iters) {
    for (let i = 0; i < iters.length; i++) {
        iters[i] = iters[i][Symbol.iterator]()
    }
    while (true) {
        for (let i = 0; i < iters.length; i++) {
            const {done, value} = iters[i].next()
            if (done) return undefined
            yield value
        }
    }
}
```

The optimized version is pretty straightforward (using private fields
+ methods here):

```js
function ziperator(...iters) { return new InterleavedIterator(iters) }

class InterleavedIterator {
    #iters, #index
    constructor(iters) { this.#iters = iters; this.#index = 0 }
    [Symbol.iterator]() { return this }
    next(value) { return this.#invoke("next", value) }
    throw(value) { return this.#invoke("throw", value) }
    return(value) { return this.#invoke("return", value) }
    #invoke(method, value) {
        if (this.#iters == null) return {done: true, value: undefined}
        const index = this.#index
        this.#index = (index + 1) % this.#iters.length
        const {done, value} = this.#iters[index][method](value)
        if (done) this.#iters = undefined
        return {done, value}
    }
}
```

-----

Isiah Meadows
contact at isiahmeadows.com
www.isiahmeadows.com
On Fri, Dec 14, 2018 at 2:55 PM Mike Samuel <mikesamuel at gmail.com> wrote:
>
>
>
> On Fri, Dec 14, 2018 at 2:26 PM Isiah Meadows <isiahmeadows at gmail.com> wrote:
>>
>> The main difference with that loop is that it's generalized to any number of arrays, not just two with the second array having length one less than the first. Otherwise, it'd look exactly the same. BTW, I like this route (`Array.interleave`) better since it doesn't have to result in just a single string result - it could just be an array of strings plugged into some API instead, or it could be procedurally streamed out in chunks.
>
>
> Fair enough.
> If you're not looking for something template tag specific then a simple zip over iterators should do it?
>
> function *ziperator(iterators) {
>     let progressed;
>     do {
>         progressed = false;
>         for (let iterator of iterators) {
>             for (let element of iterator) {
>                 yield element;
>                 progressed = true;
>                 break;
>             }
>         }
>     } while (progressed);
> }
>
> console.log(Array.from(ziperator([ ['a', 'b', 'c'][Symbol.iterator](), [1, 2][Symbol.iterator]() ])).join(''));
> // -> a1b2c
>
> (but optimized :)
>
>
>
>>
>> On Fri, Dec 14, 2018 at 14:04 Mike Samuel <mikesamuel at gmail.com> wrote:
>>>
>>>
>>>
>>> On Fri, Dec 14, 2018 at 12:51 PM Isiah Meadows <isiahmeadows at gmail.com> wrote:
>>>>
>>>> I'll point out Kai could be on to something, although I disagree `zip` would be the right abstraction. Maybe `Array.interleave(...arrays)`? You could do `Array.interleave(template, args).map(String).join("")` for similar effect, and it'd be more generally useful.
>>>>
>>>> The key here is that iteration would stop after the index hits any array's length, so it'd be polyfilled kinda like this:
>>>>
>>>> ```js
>>>> Array.interpolate = (...args) => {
>>>>     let ret = []
>>>>     let lengths = []
>>>>     let count = 0
>>>>     for (let i = 0; i < args.length; i++) {
>>>>         lengths[i] = args[i].count
>>>>     }
>>>>     for (let index = 0; ; index++) {
>>>>         for (let i = 0; i < args.length; i++) {
>>>>             if (index === lengths[i]) return ret
>>>>             ret[count++] = args[i][index]
>>>>         }
>>>>     }
>>>> }
>>>> ```
>>>>
>>>> (This could be optimized, though.)
>>>
>>>
>>> As a data point, something like this loop appears in most of the template tags I've written but
>>> it's never had these precise semantics so I didn't bother putting it into template-tag-common.
>>>
>>> That library makes it easy to split the operation of a template tag into 3 stages:
>>> 1. An optional configuration stage accessed by calling the template tag as a regular function: mytag({ /* options */)`...`
>>> 2. Static analysis over the strings.   This is memoized.
>>> 3. Computing a result from (options, strings, results of step 2, interoplated values)
>>>
>>> The final loop (step 3) in the template tags I maintain tends to looks like
>>>
>>> function computeResult(options, staticState /* from step 2 */, strings, ...values) {
>>>   let n = values.length;  // Could do Math.max(strings.length - 1, values.length);
>>>   let result = strings[0];  // Usually strings.raw
>>>   for (let i = 0; i < n;) {
>>>     const interpolatedValue = f(options, staticState[i], values[i]);
>>>     // Sometimes code here looks backwards at the result to see if it needs to avoid token-merging hazards.
>>>     result += interpolatedValue;
>>>     result += strings[++i];
>>>   }
>>>   return wrapResult(result);  // Produce a value of a type that encapsulates the tag's security guarantees.
>>> }
>>>
>>>
>>>
>>>


More information about the es-discuss mailing list