ftor ftor - 1 year ago 78
Javascript Question

How to map over arbitrary Iterables?

I wrote a

function for
s and now I want to derive a generic
that can map over arbitrary
s. However, I have encountered an issue: Since
s abstract the data source,
couldn't determine the type of it (e.g.
etc.). I need this type to invoke the corresponding identity element/concat function. Three solutions come to mind:

  1. pass the identity element/concat function explicitly
    const map = f => id => concat => xs
    (this is verbose and would leak internal API though)

  2. only map
    s that implement the monoid interface (that were cool, but introducing new types?)

  3. rely on the prototype or constructor identity of
    , etc.

I tried the latter but
always yield
no matter what a do, for instance:

Array.prototype.values.prototype.isPrototypeOf([].values()); // false
Array.prototype.isPrototypeOf([].values()); // false

My questions:

  • Where are the prototypes of

  • Is there a better approach that solves the given issue?

seem to share the same prototype:

Object.getPrototypeOf(Object.getPrototypeOf([][Symbol.iterator]())) ====

A distinction by prototypes seems not to be possible.

Edit: Here is my code:

const values = o => keys(o).values();
const next = iter => iter.next();

const foldl = f => acc => iter => {
let loop = (acc, {value, done}) => done
? acc
: loop(f(acc) (value), next(iter));

return loop(acc, next(iter));

// static `map` version only for `Array`s - not what I desire

const map = f => foldl(acc => x => [...acc, f(x)]) ([]);

console.log( map(x => x + x) ([1,2,3].values()) ); // A

console.log( map(x => x + x) (("abc")[Symbol.iterator]()) ); // B

The code in line
yields the desired result. However
yields an
instead of
and the concatenation only works, because
s and
s are coincidentally equivalent in this regard.

Edit: There seems to be confusion for what reason I do this: I want to use the iterable/iterator protocol to abstract iteration details away, so that my fold/unfold and derived map/filter etc. functions are generic. The problem is, that you can't do this without also having a protocol for identity/concat. And my little "hack" to rely on prototype identity didn't work out.

@redneb made a good point in his response and I agree with him that not every iterable is also a "mappable". However, keeping that in mind I still think it is meaningful - at least in Javascript - to utilize the protocol in this way, until maybe in future versions there is a mappable or collection protocol for such usage.

Answer Source

I have not used the iterable protocol before, but it seems to me that it is essentially an interface designed to let you iterate over container objects using a for loop. The problem is that you are trying to use that interface for something that it was not designed for. For that you would need a separate interface. It is conceivable that an object might be "iterable" but not "mappable". For example, imagine that in an application we are working with binary trees and we implement the iterable interface for them by traversing them say in BFS order, just because that order makes sense for this particular application. How would a generic map work for this particular iterable? It would need to return a tree of the "same shape", but this particular iterable implementation does not provide enough information to reconstruct the tree.

So the solution to this is to define a new interface (call it Mappable, Functor, or whatever you like) but it has to be a distinct interface. Then, you can implement that interface for types that makes sense, such as arrays.