addon-sdk/source/lib/sdk/util/sequence.js

Sat, 03 Jan 2015 20:18:00 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Sat, 03 Jan 2015 20:18:00 +0100
branch
TOR_BUG_3246
changeset 7
129ffea94266
permissions
-rw-r--r--

Conditionally enable double key logic according to:
private browsing mode or privacy.thirdparty.isolate preference and
implement in GetCookieStringCommon and FindCookie where it counts...
With some reservations of how to convince FindCookie users to test
condition and pass a nullptr when disabling double key logic.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
michael@0 4 */
michael@0 5 "use strict";
michael@0 6
michael@0 7 module.metadata = {
michael@0 8 "stability": "experimental"
michael@0 9 };
michael@0 10
michael@0 11 // Disclamer:
michael@0 12 // In this module we'll have some common argument / variable names
michael@0 13 // to hint their type or behavior.
michael@0 14 //
michael@0 15 // - `f` stands for "function" that is intended to be side effect
michael@0 16 // free.
michael@0 17 // - `p` stands for "predicate" that is function which returns logical
michael@0 18 // true or false and is intended to be side effect free.
michael@0 19 // - `x` / `y` single item of the sequence.
michael@0 20 // - `xs` / `ys` sequence of `x` / `y` items where `x` / `y` signifies
michael@0 21 // type of the items in sequence, so sequence is not of the same item.
michael@0 22 // - `_` used for argument(s) or variable(s) who's values are ignored.
michael@0 23
michael@0 24 const { complement, flip, identity } = require("../lang/functional");
michael@0 25 const { iteratorSymbol } = require("../util/iteration");
michael@0 26 const { isArray, isArguments, isMap, isSet,
michael@0 27 isString, isBoolean, isNumber } = require("../lang/type");
michael@0 28
michael@0 29 const Sequence = function Sequence(iterator) {
michael@0 30 if (iterator.isGenerator && iterator.isGenerator())
michael@0 31 this[iteratorSymbol] = iterator;
michael@0 32 else
michael@0 33 throw TypeError("Expected generator argument");
michael@0 34 };
michael@0 35 exports.Sequence = Sequence;
michael@0 36
michael@0 37 const polymorphic = dispatch => x =>
michael@0 38 x === null ? dispatch.null(null) :
michael@0 39 x === void(0) ? dispatch.void(void(0)) :
michael@0 40 isArray(x) ? (dispatch.array || dispatch.indexed)(x) :
michael@0 41 isString(x) ? (dispatch.string || dispatch.indexed)(x) :
michael@0 42 isArguments(x) ? (dispatch.arguments || dispatch.indexed)(x) :
michael@0 43 isMap(x) ? dispatch.map(x) :
michael@0 44 isSet(x) ? dispatch.set(x) :
michael@0 45 isNumber(x) ? dispatch.number(x) :
michael@0 46 isBoolean(x) ? dispatch.boolean(x) :
michael@0 47 dispatch.default(x);
michael@0 48
michael@0 49 const nogen = function*() {};
michael@0 50 const empty = () => new Sequence(nogen);
michael@0 51 exports.empty = empty;
michael@0 52
michael@0 53 const seq = polymorphic({
michael@0 54 null: empty,
michael@0 55 void: empty,
michael@0 56 array: identity,
michael@0 57 string: identity,
michael@0 58 arguments: identity,
michael@0 59 map: identity,
michael@0 60 set: identity,
michael@0 61 default: x => x instanceof Sequence ? x : new Sequence(x)
michael@0 62 });
michael@0 63 exports.seq = seq;
michael@0 64
michael@0 65
michael@0 66
michael@0 67
michael@0 68 // Function to cast seq to string.
michael@0 69 const string = (...etc) => "".concat(...etc);
michael@0 70 exports.string = string;
michael@0 71
michael@0 72 // Function for casting seq to plain object.
michael@0 73 const object = (...pairs) => {
michael@0 74 let result = {};
michael@0 75 for (let [key, value] of pairs)
michael@0 76 result[key] = value;
michael@0 77
michael@0 78 return result;
michael@0 79 };
michael@0 80 exports.object = object;
michael@0 81
michael@0 82 // Takes `getEnumerator` function that returns `nsISimpleEnumerator`
michael@0 83 // and creates lazy sequence of it's items. Note that function does
michael@0 84 // not take `nsISimpleEnumerator` itslef because that would allow
michael@0 85 // single iteration, which would not be consistent with rest of the
michael@0 86 // lazy sequences.
michael@0 87 const fromEnumerator = getEnumerator => seq(function* () {
michael@0 88 const enumerator = getEnumerator();
michael@0 89 while (enumerator.hasMoreElements())
michael@0 90 yield enumerator.getNext();
michael@0 91 });
michael@0 92 exports.fromEnumerator = fromEnumerator;
michael@0 93
michael@0 94 // Takes `object` and returns lazy sequence of own `[key, value]`
michael@0 95 // pairs (does not include inherited and non enumerable keys).
michael@0 96 const pairs = polymorphic({
michael@0 97 null: empty,
michael@0 98 void: empty,
michael@0 99 map: identity,
michael@0 100 indexed: indexed => seq(function* () {
michael@0 101 const count = indexed.length;
michael@0 102 let index = 0;
michael@0 103 while (index < count) {
michael@0 104 yield [index, indexed[index]];
michael@0 105 index = index + 1;
michael@0 106 }
michael@0 107 }),
michael@0 108 default: object => seq(function* () {
michael@0 109 for (let key of Object.keys(object))
michael@0 110 yield [key, object[key]];
michael@0 111 })
michael@0 112 });
michael@0 113 exports.pairs = pairs;
michael@0 114
michael@0 115
michael@0 116 const keys = polymorphic({
michael@0 117 null: empty,
michael@0 118 void: empty,
michael@0 119 indexed: indexed => seq(function* () {
michael@0 120 const count = indexed.length;
michael@0 121 let index = 0;
michael@0 122 while (index < count) {
michael@0 123 yield index;
michael@0 124 index = index + 1;
michael@0 125 }
michael@0 126 }),
michael@0 127 map: map => seq(function* () {
michael@0 128 for (let [key, _] of map)
michael@0 129 yield key;
michael@0 130 }),
michael@0 131 default: object => seq(function* () {
michael@0 132 for (let key of Object.keys(object))
michael@0 133 yield key;
michael@0 134 })
michael@0 135 });
michael@0 136 exports.keys = keys;
michael@0 137
michael@0 138
michael@0 139 const values = polymorphic({
michael@0 140 null: empty,
michael@0 141 void: empty,
michael@0 142 set: identity,
michael@0 143 indexed: indexed => seq(function* () {
michael@0 144 const count = indexed.length;
michael@0 145 let index = 0;
michael@0 146 while (index < count) {
michael@0 147 yield indexed[index];
michael@0 148 index = index + 1;
michael@0 149 }
michael@0 150 }),
michael@0 151 map: map => seq(function* () {
michael@0 152 for (let [_, value] of map) yield value;
michael@0 153 }),
michael@0 154 default: object => seq(function* () {
michael@0 155 for (let key of Object.keys(object)) yield object[key];
michael@0 156 })
michael@0 157 });
michael@0 158 exports.values = values;
michael@0 159
michael@0 160
michael@0 161
michael@0 162 // Returns a lazy sequence of `x`, `f(x)`, `f(f(x))` etc.
michael@0 163 // `f` must be free of side-effects. Note that returned
michael@0 164 // sequence is infinite so it must be consumed partially.
michael@0 165 //
michael@0 166 // Implements clojure iterate:
michael@0 167 // http://clojuredocs.org/clojure_core/clojure.core/iterate
michael@0 168 const iterate = (f, x) => seq(function* () {
michael@0 169 let state = x;
michael@0 170 while (true) {
michael@0 171 yield state;
michael@0 172 state = f(state);
michael@0 173 }
michael@0 174 });
michael@0 175 exports.iterate = iterate;
michael@0 176
michael@0 177 // Returns a lazy sequence of the items in sequence for which `p(item)`
michael@0 178 // returns `true`. `p` must be free of side-effects.
michael@0 179 //
michael@0 180 // Implements clojure filter:
michael@0 181 // http://clojuredocs.org/clojure_core/clojure.core/filter
michael@0 182 const filter = (p, sequence) => seq(function* () {
michael@0 183 if (sequence !== null && sequence !== void(0)) {
michael@0 184 for (let item of sequence) {
michael@0 185 if (p(item))
michael@0 186 yield item;
michael@0 187 }
michael@0 188 }
michael@0 189 });
michael@0 190 exports.filter = filter;
michael@0 191
michael@0 192 // Returns a lazy sequence consisting of the result of applying `f` to the
michael@0 193 // set of first items of each sequence, followed by applying f to the set
michael@0 194 // of second items in each sequence, until any one of the sequences is
michael@0 195 // exhausted. Any remaining items in other sequences are ignored. Function
michael@0 196 // `f` should accept number-of-sequences arguments.
michael@0 197 //
michael@0 198 // Implements clojure map:
michael@0 199 // http://clojuredocs.org/clojure_core/clojure.core/map
michael@0 200 const map = (f, ...sequences) => seq(function* () {
michael@0 201 const count = sequences.length;
michael@0 202 // Optimize a single sequence case
michael@0 203 if (count === 1) {
michael@0 204 let [sequence] = sequences;
michael@0 205 if (sequence !== null && sequence !== void(0)) {
michael@0 206 for (let item of sequence)
michael@0 207 yield f(item);
michael@0 208 }
michael@0 209 }
michael@0 210 else {
michael@0 211 // define args array that will be recycled on each
michael@0 212 // step to aggregate arguments to be passed to `f`.
michael@0 213 let args = [];
michael@0 214 // define inputs to contain started generators.
michael@0 215 let inputs = [];
michael@0 216
michael@0 217 let index = 0;
michael@0 218 while (index < count) {
michael@0 219 inputs[index] = sequences[index][iteratorSymbol]();
michael@0 220 index = index + 1;
michael@0 221 }
michael@0 222
michael@0 223 // Run loop yielding of applying `f` to the set of
michael@0 224 // items at each step until one of the `inputs` is
michael@0 225 // exhausted.
michael@0 226 let done = false;
michael@0 227 while (!done) {
michael@0 228 let index = 0;
michael@0 229 let value = void(0);
michael@0 230 while (index < count && !done) {
michael@0 231 ({ done, value }) = inputs[index].next();
michael@0 232
michael@0 233 // If input is not exhausted yet store value in args.
michael@0 234 if (!done) {
michael@0 235 args[index] = value;
michael@0 236 index = index + 1;
michael@0 237 }
michael@0 238 }
michael@0 239
michael@0 240 // If none of the inputs is exhasted yet, `args` contain items
michael@0 241 // from each input so we yield application of `f` over them.
michael@0 242 if (!done)
michael@0 243 yield f(...args);
michael@0 244 }
michael@0 245 }
michael@0 246 });
michael@0 247 exports.map = map;
michael@0 248
michael@0 249 // Returns a lazy sequence of the intermediate values of the reduction (as
michael@0 250 // per reduce) of sequence by `f`, starting with `initial` value if provided.
michael@0 251 //
michael@0 252 // Implements clojure reductions:
michael@0 253 // http://clojuredocs.org/clojure_core/clojure.core/reductions
michael@0 254 const reductions = (...params) => {
michael@0 255 const count = params.length;
michael@0 256 let hasInitial = false;
michael@0 257 let f, initial, source;
michael@0 258 if (count === 2) {
michael@0 259 ([f, source]) = params;
michael@0 260 }
michael@0 261 else if (count === 3) {
michael@0 262 ([f, initial, source]) = params;
michael@0 263 hasInitial = true;
michael@0 264 }
michael@0 265 else {
michael@0 266 throw Error("Invoked with wrong number of arguments: " + count);
michael@0 267 }
michael@0 268
michael@0 269 const sequence = seq(source);
michael@0 270
michael@0 271 return seq(function* () {
michael@0 272 let started = hasInitial;
michael@0 273 let result = void(0);
michael@0 274
michael@0 275 // If initial is present yield it.
michael@0 276 if (hasInitial)
michael@0 277 yield (result = initial);
michael@0 278
michael@0 279 // For each item of the sequence accumulate new result.
michael@0 280 for (let item of sequence) {
michael@0 281 // If nothing has being yield yet set result to first
michael@0 282 // item and yield it.
michael@0 283 if (!started) {
michael@0 284 started = true;
michael@0 285 yield (result = item);
michael@0 286 }
michael@0 287 // Otherwise accumulate new result and yield it.
michael@0 288 else {
michael@0 289 yield (result = f(result, item));
michael@0 290 }
michael@0 291 }
michael@0 292
michael@0 293 // If nothing has being yield yet it's empty sequence and no
michael@0 294 // `initial` was provided in which case we need to yield `f()`.
michael@0 295 if (!started)
michael@0 296 yield f();
michael@0 297 });
michael@0 298 };
michael@0 299 exports.reductions = reductions;
michael@0 300
michael@0 301 // `f` should be a function of 2 arguments. If `initial` is not supplied,
michael@0 302 // returns the result of applying `f` to the first 2 items in sequence, then
michael@0 303 // applying `f` to that result and the 3rd item, etc. If sequence contains no
michael@0 304 // items, `f` must accept no arguments as well, and reduce returns the
michael@0 305 // result of calling f with no arguments. If sequence has only 1 item, it
michael@0 306 // is returned and `f` is not called. If `initial` is supplied, returns the
michael@0 307 // result of applying `f` to `initial` and the first item in sequence, then
michael@0 308 // applying `f` to that result and the 2nd item, etc. If sequence contains no
michael@0 309 // items, returns `initial` and `f` is not called.
michael@0 310 //
michael@0 311 // Implements clojure reduce:
michael@0 312 // http://clojuredocs.org/clojure_core/clojure.core/reduce
michael@0 313 const reduce = (...args) => {
michael@0 314 const xs = reductions(...args);
michael@0 315 let x;
michael@0 316 for (x of xs) void(0);
michael@0 317 return x;
michael@0 318 };
michael@0 319 exports.reduce = reduce;
michael@0 320
michael@0 321 const each = (f, sequence) => {
michael@0 322 for (let x of seq(sequence)) void(f(x));
michael@0 323 };
michael@0 324 exports.each = each;
michael@0 325
michael@0 326
michael@0 327 const inc = x => x + 1;
michael@0 328 // Returns the number of items in the sequence. `count(null)` && `count()`
michael@0 329 // returns `0`. Also works on strings, arrays, Maps & Sets.
michael@0 330
michael@0 331 // Implements clojure count:
michael@0 332 // http://clojuredocs.org/clojure_core/clojure.core/count
michael@0 333 const count = polymorphic({
michael@0 334 null: _ => 0,
michael@0 335 void: _ => 0,
michael@0 336 indexed: indexed => indexed.length,
michael@0 337 map: map => map.size,
michael@0 338 set: set => set.size,
michael@0 339 default: xs => reduce(inc, 0, xs)
michael@0 340 });
michael@0 341 exports.count = count;
michael@0 342
michael@0 343 // Returns `true` if sequence has no items.
michael@0 344
michael@0 345 // Implements clojure empty?:
michael@0 346 // http://clojuredocs.org/clojure_core/clojure.core/empty_q
michael@0 347 const isEmpty = sequence => {
michael@0 348 // Treat `null` and `undefined` as empty sequences.
michael@0 349 if (sequence === null || sequence === void(0))
michael@0 350 return true;
michael@0 351
michael@0 352 // If contains any item non empty so return `false`.
michael@0 353 for (let _ of sequence)
michael@0 354 return false;
michael@0 355
michael@0 356 // If has not returned yet, there was nothing to iterate
michael@0 357 // so it's empty.
michael@0 358 return true;
michael@0 359 };
michael@0 360 exports.isEmpty = isEmpty;
michael@0 361
michael@0 362 const and = (a, b) => a && b;
michael@0 363
michael@0 364 // Returns true if `p(x)` is logical `true` for every `x` in sequence, else
michael@0 365 // `false`.
michael@0 366 //
michael@0 367 // Implements clojure every?:
michael@0 368 // http://clojuredocs.org/clojure_core/clojure.core/every_q
michael@0 369 const isEvery = (p, sequence) => {
michael@0 370 if (sequence !== null && sequence !== void(0)) {
michael@0 371 for (let item of sequence) {
michael@0 372 if (!p(item))
michael@0 373 return false;
michael@0 374 }
michael@0 375 }
michael@0 376 return true;
michael@0 377 };
michael@0 378 exports.isEvery = isEvery;
michael@0 379
michael@0 380 // Returns the first logical true value of (p x) for any x in sequence,
michael@0 381 // else `null`.
michael@0 382 //
michael@0 383 // Implements clojure some:
michael@0 384 // http://clojuredocs.org/clojure_core/clojure.core/some
michael@0 385 const some = (p, sequence) => {
michael@0 386 if (sequence !== null && sequence !== void(0)) {
michael@0 387 for (let item of sequence) {
michael@0 388 if (p(item))
michael@0 389 return true;
michael@0 390 }
michael@0 391 }
michael@0 392 return null;
michael@0 393 };
michael@0 394 exports.some = some;
michael@0 395
michael@0 396 // Returns a lazy sequence of the first `n` items in sequence, or all items if
michael@0 397 // there are fewer than `n`.
michael@0 398 //
michael@0 399 // Implements clojure take:
michael@0 400 // http://clojuredocs.org/clojure_core/clojure.core/take
michael@0 401 const take = (n, sequence) => n <= 0 ? empty() : seq(function* () {
michael@0 402 let count = n;
michael@0 403 for (let item of sequence) {
michael@0 404 yield item;
michael@0 405 count = count - 1;
michael@0 406 if (count === 0) break;
michael@0 407 }
michael@0 408 });
michael@0 409 exports.take = take;
michael@0 410
michael@0 411 // Returns a lazy sequence of successive items from sequence while
michael@0 412 // `p(item)` returns `true`. `p` must be free of side-effects.
michael@0 413 //
michael@0 414 // Implements clojure take-while:
michael@0 415 // http://clojuredocs.org/clojure_core/clojure.core/take-while
michael@0 416 const takeWhile = (p, sequence) => seq(function* () {
michael@0 417 for (let item of sequence) {
michael@0 418 if (!p(item))
michael@0 419 break;
michael@0 420
michael@0 421 yield item;
michael@0 422 }
michael@0 423 });
michael@0 424 exports.takeWhile = takeWhile;
michael@0 425
michael@0 426 // Returns a lazy sequence of all but the first `n` items in
michael@0 427 // sequence.
michael@0 428 //
michael@0 429 // Implements clojure drop:
michael@0 430 // http://clojuredocs.org/clojure_core/clojure.core/drop
michael@0 431 const drop = (n, sequence) => seq(function* () {
michael@0 432 if (sequence !== null && sequence !== void(0)) {
michael@0 433 let count = n;
michael@0 434 for (let item of sequence) {
michael@0 435 if (count > 0)
michael@0 436 count = count - 1;
michael@0 437 else
michael@0 438 yield item;
michael@0 439 }
michael@0 440 }
michael@0 441 });
michael@0 442 exports.drop = drop;
michael@0 443
michael@0 444 // Returns a lazy sequence of the items in sequence starting from the
michael@0 445 // first item for which `p(item)` returns falsy value.
michael@0 446 //
michael@0 447 // Implements clojure drop-while:
michael@0 448 // http://clojuredocs.org/clojure_core/clojure.core/drop-while
michael@0 449 const dropWhile = (p, sequence) => seq(function* () {
michael@0 450 let keep = false;
michael@0 451 for (let item of sequence) {
michael@0 452 keep = keep || !p(item);
michael@0 453 if (keep) yield item;
michael@0 454 }
michael@0 455 });
michael@0 456 exports.dropWhile = dropWhile;
michael@0 457
michael@0 458 // Returns a lazy sequence representing the concatenation of the
michael@0 459 // suplied sequences.
michael@0 460 //
michael@0 461 // Implements clojure conact:
michael@0 462 // http://clojuredocs.org/clojure_core/clojure.core/concat
michael@0 463 const concat = (...sequences) => seq(function* () {
michael@0 464 for (let sequence of sequences)
michael@0 465 for (let item of sequence)
michael@0 466 yield item;
michael@0 467 });
michael@0 468 exports.concat = concat;
michael@0 469
michael@0 470 // Returns the first item in the sequence.
michael@0 471 //
michael@0 472 // Implements clojure first:
michael@0 473 // http://clojuredocs.org/clojure_core/clojure.core/first
michael@0 474 const first = sequence => {
michael@0 475 if (sequence !== null && sequence !== void(0)) {
michael@0 476 for (let item of sequence)
michael@0 477 return item;
michael@0 478 }
michael@0 479 return null;
michael@0 480 };
michael@0 481 exports.first = first;
michael@0 482
michael@0 483 // Returns a possibly empty sequence of the items after the first.
michael@0 484 //
michael@0 485 // Implements clojure rest:
michael@0 486 // http://clojuredocs.org/clojure_core/clojure.core/rest
michael@0 487 const rest = sequence => drop(1, sequence);
michael@0 488 exports.rest = rest;
michael@0 489
michael@0 490 // Returns the value at the index. Returns `notFound` or `undefined`
michael@0 491 // if index is out of bounds.
michael@0 492 const nth = (xs, n, notFound) => {
michael@0 493 if (n >= 0) {
michael@0 494 if (isArray(xs) || isArguments(xs) || isString(xs)) {
michael@0 495 return n < xs.length ? xs[n] : notFound;
michael@0 496 }
michael@0 497 else if (xs !== null && xs !== void(0)) {
michael@0 498 let count = n;
michael@0 499 for (let x of xs) {
michael@0 500 if (count <= 0)
michael@0 501 return x;
michael@0 502
michael@0 503 count = count - 1;
michael@0 504 }
michael@0 505 }
michael@0 506 }
michael@0 507 return notFound;
michael@0 508 };
michael@0 509 exports.nth = nth;
michael@0 510
michael@0 511 // Return the last item in sequence, in linear time.
michael@0 512 // If `sequence` is an array or string or arguments
michael@0 513 // returns in constant time.
michael@0 514 // Implements clojure last:
michael@0 515 // http://clojuredocs.org/clojure_core/clojure.core/last
michael@0 516 const last = polymorphic({
michael@0 517 null: _ => null,
michael@0 518 void: _ => null,
michael@0 519 indexed: indexed => indexed[indexed.length - 1],
michael@0 520 map: xs => reduce((_, x) => x, xs),
michael@0 521 set: xs => reduce((_, x) => x, xs),
michael@0 522 default: xs => reduce((_, x) => x, xs)
michael@0 523 });
michael@0 524 exports.last = last;
michael@0 525
michael@0 526 // Return a lazy sequence of all but the last `n` (default 1) items
michael@0 527 // from the give `xs`.
michael@0 528 //
michael@0 529 // Implements clojure drop-last:
michael@0 530 // http://clojuredocs.org/clojure_core/clojure.core/drop-last
michael@0 531 const dropLast = flip((xs, n=1) => seq(function* () {
michael@0 532 let ys = [];
michael@0 533 for (let x of xs) {
michael@0 534 ys.push(x);
michael@0 535 if (ys.length > n)
michael@0 536 yield ys.shift();
michael@0 537 }
michael@0 538 }));
michael@0 539 exports.dropLast = dropLast;
michael@0 540
michael@0 541 // Returns a lazy sequence of the elements of `xs` with duplicates
michael@0 542 // removed
michael@0 543 //
michael@0 544 // Implements clojure distinct
michael@0 545 // http://clojuredocs.org/clojure_core/clojure.core/distinct
michael@0 546 const distinct = sequence => seq(function* () {
michael@0 547 let items = new Set();
michael@0 548 for (let item of sequence) {
michael@0 549 if (!items.has(item)) {
michael@0 550 items.add(item);
michael@0 551 yield item;
michael@0 552 }
michael@0 553 }
michael@0 554 });
michael@0 555 exports.distinct = distinct;
michael@0 556
michael@0 557 // Returns a lazy sequence of the items in `xs` for which
michael@0 558 // `p(x)` returns false. `p` must be free of side-effects.
michael@0 559 //
michael@0 560 // Implements clojure remove
michael@0 561 // http://clojuredocs.org/clojure_core/clojure.core/remove
michael@0 562 const remove = (p, xs) => filter(complement(p), xs);
michael@0 563 exports.remove = remove;
michael@0 564
michael@0 565 // Returns the result of applying concat to the result of
michael@0 566 // `map(f, xs)`. Thus function `f` should return a sequence.
michael@0 567 //
michael@0 568 // Implements clojure mapcat
michael@0 569 // http://clojuredocs.org/clojure_core/clojure.core/mapcat
michael@0 570 const mapcat = (f, sequence) => seq(function* () {
michael@0 571 const sequences = map(f, sequence);
michael@0 572 for (let sequence of sequences)
michael@0 573 for (let item of sequence)
michael@0 574 yield item;
michael@0 575 });
michael@0 576 exports.mapcat = mapcat;

mercurial