如何映射任意Iterables?

时间:2016-09-10 11:11:55

标签: javascript functional-programming ecmascript-6 iteration iterable

我为reduce编写了Iterable函数,现在我希望派生一个可以映射到任意map的通用Iterable。但是,我遇到了一个问题:由于Iterable抽象了数据源,map无法确定其类型(例如ArrayString,{ {1}}等。我需要这种类型来调用相应的标识元素/ concat函数。我想到了三种解决方案:

  1. 显式传递identity元素/ concat函数Map(这很详细,但会泄漏内部API)
  2. 只有实现monoid接口的地图const map = f => id => concat => xs(很酷,但引入新类型?)
  3. 依赖于IterableArrayIterator等的原型或构造函数标识。
  4. 我尝试了后者,但StringIterator / isPrototypeOf无论做什么都会产生instanceof,例如:

    false

    我的问题:

    • Array.prototype.values.prototype.isPrototypeOf([].values()); // false Array.prototype.isPrototypeOf([].values()); // false / ArrayIterator / ...的原型在哪里?
    • 是否有更好的方法可以解决给定的问题?

    修改: StringIterator[][Symbol.iterator]()似乎共享相同的原型:

    ("")[Symbol.iterator]()

    原型的区别似乎是不可能的。

    修改:这是我的代码:

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

    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中的代码产生了所需的结果。但是,A会产生B而不是Array,而且只有String s和String在这方面恰好相同。

    编辑:我这样做的原因似乎很混乱:我想使用iterable / iterator协议来抽象迭代细节,以便我的fold /展开和派生的map / filter等功能是通用的。问题是,如果没有身份/连接协议,你就无法做到这一点。而我的小" hack"依靠原型身份并没有成功。

    @redneb在他的回复中提出了一个很好的观点,我同意他的观点,并非每个可迭代都是一个可映射的"。但是,记住这一点我仍然认为以这种方式利用协议是有意义的 - 至少在Javascript中 - 直到将来的版本中有可用于这种用途的可映射或收集协议。

5 个答案:

答案 0 :(得分:5)

我之前没有使用iterable protocol,但在我看来,它本质上是一个设计用于让您使用for循环迭代容器对象的接口。问题是您正在尝试将该接口用于不是为其设计的内容。为此,您需要一个单独的界面。可以想象,一个对象可能是可迭代的"但不是" mappable"。例如,假设在应用程序中我们正在使用二叉树,并且我们通过遍历它们以BFS顺序遍历它们来实现它们的可迭代接口,只是因为该顺序对于此特定应用程序是有意义的。通用映射如何适用于此特定迭代?它需要返回一个"相同形状的树#34;但是这个特定的可迭代实现不能提供足够的信息来重建树。

所以解决这个问题的方法是定义一个新的接口(称之为MappableFunctor,或者你喜欢的任何东西),但它必须是一个独特的接口。然后,您可以为有意义的类型实现该接口,例如数组。

答案 1 :(得分:1)

  

明确传递identity element / concat函数const map = f => id => concat => xs

是的,如果xs参数未公开构造新值的功能,则几乎总是必需的。在Scala中,每个集合类型都有一个builder,遗憾的是ECMAScript标准中没有任何内容与此匹配。

  

仅映射实现monoid接口的Iterables

嗯,是的,这可能是一种方法。您甚至不需要引入“新类型”,Fantasyland specification已存在此标准。然而,缺点是

  • 大多数内置类型(StringMapSet)尽管可迭代,但未实现monoid接口
  • 并非所有“mappables”都是monoid!

另一方面,并​​非所有迭代都必须是可映射的。尝试在任意迭代上写map而不回退到Array结果注定要失败。

所以只需查找FunctorTraversable接口,并在它们存在的地方使用它们。它们可能在内部构建在迭代器上,但这不应该与您有关。您可能想要做的唯一事情是提供一个通用的帮助器来创建这样的基于迭代器的映射方法,以便您可以例如用它装饰MapString。该帮助器也可以将构建器对象作为参数。

  

依赖于ArrayIterator,StringIterator等的原型或构造函数标识。

这不起作用,例如,类型化数组使用与普通数组相同的迭代器类型。由于迭代器没有办法访问迭代对象,因此无法区分它们。但是你真的不应该,只要你处理迭代器本身,你应该最多映射到另一个迭代器,但不能映射到创建迭代器的迭代类型。

  

ArrayIterator / StringIterator /...?

的原型在哪里?

它们没有全局变量,但您可以在创建实例后使用Object.getPrototypeOf访问它们。

答案 2 :(得分:0)

你可以比较对象字符串,虽然这不是万无一失的,因为在某些环境中已经存在已知的错误,在ES6中用户可以修改这些字符串。

console.log(Object.prototype.toString.call(""[Symbol.iterator]()));
console.log(Object.prototype.toString.call([][Symbol.iterator]()));

更新:通过测试迭代器对象的可训练性,您可以获得更可靠的结果,它确实需要完全符合ES6规范的环境。这样的事情。

var sValues = String.prototype[Symbol.iterator];
var testString = 'abc';

function isStringIterator(value) {
  if (value === null || typeof value !== 'object') {
    return false;
  }
  try {
    return value.next.call(sValues.call(testString)).value === 'a';
  } catch (ignore) {}
  return false;
}

var aValues = Array.prototype.values;
var testArray = ['a', 'b', 'c'];

function isArrayIterator(value) {
  if (value === null || typeof value !== 'object') {
    return false;
  }
  try {
    return value.next.call(aValues.call(testArray)).value === 'a';
  } catch (ignore) {}
  return false;
}

var mapValues = Map.prototype.values;
var testMap = new Map([
  [1, 'MapSentinel']
]);

function isMapIterator(value) {
  if (value === null || typeof value !== 'object') {
    return false;
  }
  try {
    return value.next.call(mapValues.call(testMap)).value === 'MapSentinel';
  } catch (ignore) {}
  return false;
}

var setValues = Set.prototype.values;
var testSet = new Set(['SetSentinel']);

function isSetIterator(value) {
  if (value === null || typeof value !== 'object') {
    return false;
  }
  try {
    return value.next.call(setValues.call(testSet)).value === 'SetSentinel';
  } catch (ignore) {}
  return false;
}

var string = '';
var array = [];
var map = new Map();
var set = new Set();
console.log('string');
console.log(isStringIterator(string[Symbol.iterator]()));
console.log(isArrayIterator(string[Symbol.iterator]()));
console.log(isMapIterator(string[Symbol.iterator]()));
console.log(isSetIterator(string[Symbol.iterator]()));
console.log('array');
console.log(isStringIterator(array[Symbol.iterator]()));
console.log(isArrayIterator(array[Symbol.iterator]()));
console.log(isMapIterator(array[Symbol.iterator]()));
console.log(isSetIterator(array[Symbol.iterator]()));
console.log('map');
console.log(isStringIterator(map[Symbol.iterator]()));
console.log(isArrayIterator(map[Symbol.iterator]()));
console.log(isMapIterator(map[Symbol.iterator]()));
console.log(isSetIterator(map[Symbol.iterator]()));
console.log('set');
console.log(isStringIterator(set[Symbol.iterator]()));
console.log(isArrayIterator(set[Symbol.iterator]()));
console.log(isMapIterator(set[Symbol.iterator]()));
console.log(isSetIterator(set[Symbol.iterator]()));
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.1/es6-shim.js"></script>

注意:包含ES6-shim,因为Chrome目前不支持Array#values

答案 3 :(得分:0)

我知道这个问题已经发布了很长时间了,但请看一下 https://www.npmjs.com/package/fluent-iterable

它支持迭代地图以及约50种其他方法。

答案 4 :(得分:-1)

对于任意迭代,没有干净的方法来做这件事。可以为built-in iterables创建地图并引用它。

const iteratorProtoMap = [String, Array, Map, Set]
.map(ctor => [
  Object.getPrototypeOf((new ctor)[Symbol.iterator]()),
  ctor]
)
.reduce((map, entry) => map.set(...entry), new Map);

function getCtorFromIterator(iterator) {
  return iteratorProtoMap.get(Object.getPrototypeOf(iterator));
}

有了自定义迭代的可能性,还可以添加用于添加它们的API。

为了提供连接/构造所需迭代的通用模式,可以为地图而不是构造函数提供回调。