{"id":32265819,"url":"https://github.com/kevinbeaty/underarm","last_synced_at":"2026-02-22T17:07:36.459Z","repository":{"id":21006852,"uuid":"24297488","full_name":"kevinbeaty/underarm","owner":"kevinbeaty","description":":raising_hand: Transducers Inspired by Underscore","archived":false,"fork":false,"pushed_at":"2016-02-02T00:56:56.000Z","size":912,"stargazers_count":87,"open_issues_count":0,"forks_count":3,"subscribers_count":5,"default_branch":"master","last_synced_at":"2026-01-12T15:31:58.182Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://simplectic.com/projects/underarm","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":"kartik-v/bootstrap-star-rating","license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/kevinbeaty.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2014-09-21T17:16:13.000Z","updated_at":"2024-11-14T03:07:22.000Z","dependencies_parsed_at":"2022-08-05T10:15:19.442Z","dependency_job_id":null,"html_url":"https://github.com/kevinbeaty/underarm","commit_stats":null,"previous_names":["kevinbeaty/underscore-transducer"],"tags_count":27,"template":false,"template_full_name":null,"purl":"pkg:github/kevinbeaty/underarm","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevinbeaty%2Funderarm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevinbeaty%2Funderarm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevinbeaty%2Funderarm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevinbeaty%2Funderarm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/kevinbeaty","download_url":"https://codeload.github.com/kevinbeaty/underarm/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/kevinbeaty%2Funderarm/sbom","scorecard":{"id":556914,"data":{"date":"2025-08-11","repo":{"name":"github.com/kevinbeaty/underarm","commit":"f207abc5fcf46e2279e8af717894f32e2716ff76"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3,"checks":[{"name":"Code-Review","score":0,"reason":"Found 1/22 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":-1,"reason":"no workflows found","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Maintained","score":0,"reason":"0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Token-Permissions","score":-1,"reason":"No tokens found","details":null,"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":10,"reason":"no binaries found in the repo","details":null,"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":-1,"reason":"no dependencies found","details":null,"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"License","score":10,"reason":"license file detected","details":["Info: project has a license file: LICENSE:0","Info: FSF or OSI recognized license: MIT License: LICENSE:0"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 10 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-20T12:37:21.087Z","repository_id":21006852,"created_at":"2025-08-20T12:37:21.087Z","updated_at":"2025-08-20T12:37:21.087Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29719590,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-22T15:10:41.462Z","status":"ssl_error","status_checked_at":"2026-02-22T15:10:04.636Z","response_time":110,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-10-22T21:20:05.598Z","updated_at":"2026-02-22T17:07:36.449Z","avatar_url":"https://github.com/kevinbeaty.png","language":"JavaScript","funding_links":[],"categories":["Libraries"],"sub_categories":["[Javascript](https://developer.mozilla.org/en-US/docs/Web/JavaScript)"],"readme":"# Underarm\n[![Build Status](https://secure.travis-ci.org/kevinbeaty/underarm.png)](http://travis-ci.org/kevinbeaty/underarm)\n\nUse JavaScript transducers with the familiar [Underscore.js][1] API with extra goodies like [lazy generators and callback processes][4].\n\nIf you are not familiar with transducers, check out [Transducers Explained][3].\n\nToo much API for you?  Just grab what you need from the [transduce][14] libraries, which underarm is based.\n\n## Install\nWorks with [any-promise][8] library (a pollyfill, es6-promise, promise, native-promise-only, bluebird, rsvp, when, q ... your choice) for asynchronous execution.  Underarm allows any transducer to become asynchronous: Promises can be used and returned in `init`, `step` and `result`. \n\nInstall your Promise library preference before underarm and it will be auto detected and used.\n\n```bash\n$ npm install promise # or es6-promise, bluebird, q, when, rsvp ... see any-promise\n$ npm install underarm\n$ bower install underarm\n```\n\n### Browser\nInclude an ES6 Promise Pollyfill.  Then include the browser version of underarm.\n\n* [Development][12]\n* [Minified][13]\n\nStructured to allow creation of custom builds by loading only desired libs.  For example, see:\n\n* [Base Development][20]\n* [Base Minified][21]\n\nCreated by using `browserify` with [this loader][22].\n\n### Transducers\n\nFirst some helper functions and imports.\n\n```javascript\n// import, mixin and helper functions\nvar _r = require('underarm');\n\nfunction isEven(x){\n  return x % 2 !== 1;\n}\n\nfunction inc(x){\n  return x+1;\n}\n\n// prints every result and input. Useful with tap\nfunction printIt(result, input){\n  console.log(input+' ['+result+']');\n}\n\nvar trans, result;\n```\n\nChaining transducers is the same as function composition. Composed transducers are executed left to right.\n\n```javascript\nresult = _r.into([], _r.compose(_r.filter(isEven), _r.map(inc)), [1,2,3,4]);\n// [ 3, 5 ]\n\n// these are also the same\ntrans = _r().filter(isEven).map(inc).compose();\nresult = _r.into([], trans, [1,2,3,4,5]);\nresult = _r().filter(isEven).map(inc).toArray([1,2,3,4,5]);\n// [ 3, 5 ]\n```\n\nLike underscore, use `tap` to intercept intermediate results. Accepts current result and item just like the step function. The return value is ignored.\n\n```javascript\nresult = _r()\n  .filter(function(num) { return num % 2 == 0; })\n  .tap(printIt)\n  .map(function(num) { return num * num })\n  .toArray([1,2,3,200]);\n// 2 []\n// 200 [4]\n// [4, 40000 ]\n```\n\nSupport for underscore collection functions.\n\n```javascript\nresult = _r().invoke('sort').toArray([[5, 1, 7], [3, 2, 1]]);\n// [ [ 1, 5, 7 ], [ 1, 2, 3 ] ]\n\nvar stooges = [{name: 'moe', age: 40}, {name: 'larry', age: 50}, {name: 'curly', age: 40}];\nresult = _r.into([], _r.pluck('name'), stooges);\n//  ['moe', 'larry', 'curly' ]\n\nresult = _r.into([], _r.where({age: 40}), stooges);\n// [ { name: 'moe', age: 40 }, { name: 'curly', age: 40 } ]\nresult = _r.into([], _r.findWhere({age: 40}), stooges);\n// [ { name: 'moe', age: 40 } ]\n\n\nresult = _r.into([], _r.every(isEven), [0, 2, 8, 4, 8]);\n// [true]\n\nresult = _r.into([], _r.some(isEven), [1, 3, 7, 11, 9]);\n// [false]\n\nresult = _r.into([], _r.contains(3), [1, 3, 7, 11, 9]);\n// [true]\n\nresult = _r.into([], _r.find(isEven), [7, 8, 7, 11, 12]);\n// [8]\n\nresult = _r.into([], _r.first(3), [1, 9, 13, 11, 9]);\n//  [1, 9, 13 ]\n```\n\nTransducers implemented\n\n#### Base\nBase Transducers mixing common functionality from supported [transduce][14] implementations.\n\n##### map(f)\nStep all items after applying a mapping function to each item. Wraps mapping function with `_r.iteratee`.\n\nAlias: collect\n\n##### filter(predicate)\nStep all the items that pass a truth test. Wraps predicate with `_r.iteratee`.\n\nAlias: select.\n\n##### remove(predicate)\n\nStep all the items that fail a truth test. Wraps predicate with `_r.iteratee`.\n\nAlias: reject\n\n##### take(n?)\n\nStep the first item if `n` is undefined. Passing `n` will step the first N values in the array.\n\nResolves as single value if n is undefined.\n\nAlias: first, head\n\n##### takeWhile(predicate)\nTakes items until predicate returns false. Wraps predicate with `_r.iteratee`.\n\n##### drop(n?)\nSteps everything but the first item if n is undefined.  Passing an `n` will drop N values.\n\nAlias: rest, tail\n\n##### dropWhile(predicate)\nDrops items while predicate returns true. Wraps predicate with `_r.iteratee`.\n\n##### cat\nConcatenating transducer.\n\nNOTE: unlike libraries, cat should be called as a function. Use `_r.cat()` instead of `_r.cat`\n\n##### mapcat(f)\nComposition of `_r.map(f)` and `_r.cat()`. Wraps mapping function with `_r.iteratee`\n\n##### partitionAll(n)\nPartitions the source into arrays of size n. When transformer completes, the array will be stepped with any remaining items.\n\nAlias: chunkAll\n\n##### partitionBy(f)\nPartitions the source into sub arrays while the value of the function changes equality. Wrap partitioning function by `_r.iteratee`.\n\n##### compact()\nTrim out all falsey values.\n\n##### invoke(method)\nInvoke a method (with arguments) on every item.\n\n##### pluck(key)\nConvenience version of a common use case of `map`: fetching a property.\n\n##### where(attrs)\nConvenience version of a common use case of `filter`: selecting only objects containing specific `key:value` pairs.\n\n#### Array\n\n##### forEach(iteratee)\nPasses every item through unchanged, but after executing `callback(item, idx)`.  Can be useful for \"tapping into\" composed transducer pipelines.   The return value of the callback is ignored, item is passed unchanged. (See [transduce-stream][7] for a use case.)\n\nAlias: each\n\n##### find(predicate)\nLike filter, but terminates transducer pipeline with the result of the first item that passes the predicate test. Will always step either 0 (if not found) or 1 (if found) values.\n\nResolves as single value.\n\nAlias: detect\n\n##### findWhere(attrs)\nConvenience version of a common use case of `find`: getting the first object containing specific `key:value` pairs. Early termination when found.\n\nResolves as single value.\n\n##### every(predicate?)\nChecks to see if every item passes the predicate test.  Steps a single item `true` or `false`.  Early termination on `false`.  Wraps predicate in `_r.iteratee`\n\nResolves as single value.\n\nAlias: all\n\n##### some(predicate?)\nChecks to see if some item passes the predicate test.  Steps a single item `true` or `false`.  Early termination on `true`. Wraps predicate in `_r.iteratee`\n\nResolves as single value.\n\nAlias: any\n\n##### contains(target)\nDoes the stream contain the target value (`target === item`)? Steps a single item `true` or `false`. Early termination on `true`.\n\nResolves as single value.\n\nAlias: include\n\n##### push(...args)\nPasses all items straight through until the result is requested.  Once completed, steps every argument through the pipeline, before returning the result.  This effectively pushes values on the end of the stream.\n\n##### unshift(...args)\nBefore stepping the first item, steps all arguments through the pipeline, then passes every item through unchanged.  This effectively unshifts values onto the beginning of the stream.\n\n##### at(index)\nRetrieves the value at the given index. Similar to indexing into an array.\n\nResolves as single value.\n\n##### slice(begin? end?)\nLike array slice, but with transducers.  Steps items between `begin` (inclusive) and `end` (exclusive).  If either index is negative, indexes from end of transformation.  If `end` is undefined, steps until result of transformation. If `begin` is undefined, begins at 0.\n\nNote that if either index is negative, items will be buffered until completion.\n\n##### initial(n?)\nSteps everything but the last entry. Passing `n` will step all values excluding the last N.\n\nNote that no items will be sent and all items will be buffered until completion.\n\n##### last(n?)\nStep the last element. Passing `n` will step the last N  values.\n\nResolves as single value if `n` is undefined\n\nNote that no items will be sent until completion.\n\n##### unique(isSorted?, iteratee?)\nProduce a duplicate-free version of the transformation. If the transformation has already been sorted, you have the option of using an algorithm that maintains less state. If iteratee is passed, it will be wrapped with `_r.iteratee` and use return value for comparison.\n\nAlias: uniq\n\n#### Math\n\n##### min(f?)\nSteps the max value on the result of the transformation. if `f` is provided, it is wrapped with `_r.iteratee` and called with each item and the return value is used to compare values. Otherwise, the items are compared as numbers.\n\nResolves as single value.\n\n##### max(f?)\nSteps the max value on the result of the transformation. if `f` is provided, it is wrapped with `_r.iteratee` and called with each item and the return value is used to compare values. Otherwise, the items are compared as numbers.\n\nResolves as single value.\n\n#### Strings\n\nStrings are a sequence of characters, so you can transduce over those as well. Particularly useful with [transduce-stream][7].\n\nFunctions that `split` over streams are treated as a substring, and splits across the entire transformation.  This allows methods to work with chunks sent through streams.  Methods that `split` over the String are processed lazily and as soon as possible: `lines`, `words` and `chars` will process a line/word/char as they are received, and buffer any intermediate chunks appropriately.\n\n##### split(separator, limit)\nWorks like `''.split` but splits across entire sequence of items. Accepts separator (String or RegExp) and limit of substrings to send.\n\n##### join(separator)\nBuffers all items and joins results on transducer `result`.\n\nResolves as a single value.\n\n##### nonEmpty()\nOnly steps items that are non empty strings (`input.trim().length \u003e 0`).\n\n##### lines(limit?)\nSplit chunks into lines and steps each line up to the optional limit.\n\n##### chars(limit?)\nSplit chunks into characters and steps each char up to the optional limit.\n\n##### words(delimiter?, limit?)\nSplit chunks into words and steps each word up to the optional limit. Can pass an optional delimiter to identify words, default on whitespace (`/\\s+/`).\n\n### Chaining\nChaining is implicit when calling as a function `_r()`, and the source can be optionally passed as an argument to the function. When chaining, each of the transducers can be called as a builder similar to underscore.\n\n##### value()\nCall on a chained transformation to sequence a wrapped value through the transformation and resolve as a value.  The value returned is similar to underscore. Transducers that resolve as a single value are noted above.  If a function does not resolve as a single value, resolves a a collection determined by `_r.empty(source)` (see below, normally an array).\n\n##### compose()\nCall on chained transformation to compose all transducer functions in chain and return a transducer that can be used to pass to transduce functions.\n\nAlias: transducer\n\n##### mixin()\nMixin custom transducer creating functions. Transducer creating functions will be called with arguments passed when chaining and should return a transducer. All transducers described above are mixed in.\n\n### Transduce functions\n\nAdds functionality from [transduce][14] with default values from dispatched functions and chained transformations.\n\n##### toArray(xf?, from?)\nReturns a new orray by iterating through`from` after running it through the optional transformation.\n\nIf called on a chained transformation, uses the composed transformation as `xf`. If `from` is not defined, uses the wrapped source when creating the chain, `_r(wrapped)`.\n\n##### into(to?, xf?, from?)\nReturns a new collection appending all items into the empty collection `to` by passing all items from source collection `from` through the transformation `xf`.\n\nUses generic dispatch `_r.append` for reducing function.  If the `from` collection is undefined, creates an empty collection using `_r.empty()`. If `xf` is a chained transformation, composes it.\n\nDelegates to `_r.transduce(xf, _r.append, to, from)` if `xf is defined, otherwise `_r.reduce(_r.append, to, from)` if `xf` is undefined.\n\nIf called on a chained transformation, uses the composed transformation as `xf`. If `from` is not defined, uses the wrapped source when creating the chain, `_r(wrapped)`. If `to` is not defined, uses `_r.empty(from)` to create an empty value.\n\n##### transduce(xf?, f, init, coll?)\nTransduce over a transformation using the installed transducers library, using chained transformation if chaining.  If chaining and the source collection is not defined, uses wrapped source passed when creating the chain `_r(wrapped)`.  Calls generic dispatch `_r.unwrap` with result before returning.\n\n##### reduce(f, init, coll?)\nReduces over a transformation using the installed transducers library. If the source `coll` is undefined, or null, creates an empty source by dispatching to `_r.empty(coll)`.\n\nAlias: foldl, inject\n\n##### reduced\nEnsures values are reduced to signal early termination when stepping through a transformation. Probably only useful if you want to mixin custom transducers.  Use `_r.isReduced` to check if a value is wrapped as `reduced`.  Use generic dispatch `_r.unwrap` to unwrap reduced values.\n\n### Async\nUses [transduce-async][15] to support promises in transducer `init`, `step` and `result`.\n\n##### prototype.async()\nMarks chained transformation as asynchronous.  See below for changes to API when `async`.\n\n##### prototype.value()\nIf chained transformation is `async` returns a promise for the value of the transformation\n\n##### prototype.then(resolve, reject)\nMarks chained transformation as `async` and adds Promise listeners to Promise `value`.  This means that any chained transformation is a promise.\n\n##### compose(\\*fns)\nLike a normal compose when chained transformation not `async`. If `async` all arguments are interleaved with `defer`.  This allows any transducer in composed pipeline to `step` or `result` a Promise in addition to a value.  The wrapped transformer is called with value of resolved Promise.\n\n#### transduce(xf?, f, init, coll?)\nLike a normal transduce when chained transformation is `async`.  If `async`, `init` and `coll` can be a Promise and `xf` can be an async transducer. The value of `coll` can be anything that can be converted to an iterator using [transduce][14]. The return value is a Promise for the result of the transformation.\n\n##### into(to?, xf?, from?)\nLike a normal `into` when chained transformation not `async`. If `async`, `to` and `from` can be a Promise and `xf` can be an async transducer. \n\n##### toArray(xf?, from?)\nLike a normal `toArray` when chained transformation not `async`. If `async`, `coll` can be a Promise and `xf` can be an async transducer.\n\n##### defer()\nCreate an async transducer that allows wrapped transformer to `step` or `result` a Promise in addition to a value. All items will be queued and processed asap. The wrapped transformer is called with value of resolved Promise.\n\n##### delay(wait)\nCreate an async transducer that delays step of wrapped transformer by `wait` milliseconds. All items will be queued and delayed and `step` will return a promise that will resolve after `wait` milliseconds for each item.\n\n\n### Iterators\n\nTransduce, into, etc. accept generators or any iterator in addition to arrays. An iterator can either adapt the protocol or simply define a next function.\n\n```javascript\nfunction* genNums(){\n  yield 1;\n  yield 2;\n  yield 3;\n  yield 4;\n}\n\nresult = _r.into([], _r.first(3), genNums());\n// [1, 2, 3 ]\n```\n\n### Sequence generation\n\nGenerate will create an iterator that executes a callback. This can be used to generate potentially infinite sequences.\n\n```javascript\n// Returns a function that generates the next fibonacci number\n// every time it is called. Use with generators below\nfunction fib(){\n  var x=1, y=1;\n  return function(){\n    var prev = x;\n    x = y;\n    y += prev;\n    return prev;\n  }\n}\n```\n\nIf not chaining, creates an iterator that can be used with transduce. If chaining, creates an iterator a wraps the result. Pass true as second argument optionally call function to init on each iteration (allows reuse).\n\n```javascript\n// call on init to allow reuse\ntrans = _r().generate(fib, true).first(7);\nresult = trans.value();\nresult = trans.value();\n```\n\n### Callback Processes\n\nTransducers can be consumed by reduce, but since they are designed to be independent, we can use them in a variety of contexts that consume an input and produce a result, such as [CSP][3]. We can also create a process using a callback where each call advances a step in the process.  These can be used as event handlers (like the [demo][4]).\n\n```javascript\n  var $demo = $('#demo3'),\n      coords = _r()\n        .where({type:'mousemove'})\n        .map(function(e){return {x: e.clientX, y: e.clientY}})\n        .map(function(p){return '('+p.x+', '+p.y+')'})\n        .each(updateText)\n        .asCallback(),\n\n      click = _r()\n        .where({type:'click'})\n        .each(updateCount)\n        .asCallback(),\n\n      events = _r()\n        .each(coords)\n        .each(click)\n        .asCallback();\n\n  $demo.on('mousemove click', events);\n\n  function updateText(p){\n     $demo.html(p);\n  }\n\n  function updateCount(e, idx){\n     $demo.html('Click '+idx);\n  }\n\n```\nWe are composing transducers.  The previous examples are all using transducers behind the scenes. Method chaining is implicit and is composition, `_r.generate` uses an iterator and passes on to `transduce`. Even `asCallback` uses transducers but steps through the results using the argument of a callback, instead of reducing over the results.\n\n##### tap(interceptor)\nTransduce also adds `tap`, which  invokes interceptor with each result and item, and then steps through unchanged. The primary purpose of this method is to \"tap into\" a method chain, in order to perform operations on intermediate results within the chain. Executes interceptor with current result and item.\n\n\n### Node Async\n\nIf you are using Node.js, `asyncCallback` returns a callback that follows the standard convention of `fn(err, item)` and accepts a continuation that is called on completion or error.\n\n### Streams\nYou can transduce over Node.js Streams with [transduce-stream][7].\n\n```javascript\n// test.js\nvar _r = require('underarm');\n    stream = require('transduce-stream');\n\nvar transducer = _r()\n  .words()\n  .map(function(x){return (+x * +x)+ ' '})\n  .uniq()\n  .take(4)\n  .push('\\n')\n  .compose();\n\nprocess.stdin.resume();\nprocess.stdin.pipe(stream(transducer)).pipe(process.stdout);\n```\n\nRun this from the terminal to calculate a formatted sequence of the first 4 unique squared values.\n\n```bash\n$ echo '33 27 33 444' | node test.js\n 1089  729  197136\n\n$ node test.js \u003c\u003c EOT\n12 32\n33 33\n33 43\n12 33 12\nEOT\n 144  1024  1089  1849\n```\n\n### Generic dispatch\n\nSince input and output are separated the transducer transformation, transducers can be reduced, and sequences can be created over any object that supports the following methods.\n\n##### iterator(value)\nReturns an iterator that has next function and returns `{value, done}`.  Default looks for object with iterator Symbol (or `'@@iterator'`)\n\n##### iteratee(value)\nJust like underscore, you can use \"where style\" objects and strings with functions that expect a mapping function or a predicate. By default, uses `_.iteratee` to match this behavior, but setup as a dispatched function to allow custom behavior.\n\n##### empty(value?)\nReturns empty object of the same type as argument.  Default returns `[]` if `_.isArray` or `undefined`, `{}` if `_.isObject` and an internal sentinel to ignore otherwise (used when not buffering in `asCallback` or chained `value` expects single value.\n\n##### append(result, item)\nAccepts an item and optional key and appends the item to the object.  By default, appends to arrays and objects by key and returns last item when used in `asCallback` or chained transducer with single `value`.\n\n##### wrap(value?) / unwrap(value)\nWhen chaining transducers, the object passed to `_r(obj)` is dispatched to `_r.wrap`.  By default, the object is not wrapped if it is defined, and wrapped with `_r.empty()` if not defined. When transducing over the sequence (with `value`, `into`, etc.) the object is then unwrapped with `_r.unwrap`.  By default, unwrap calls `_r().value()` on chained transformations, extracts value from `_r.reduced` or simply returns the value.  You can provide custom dispatchers for custom wrapped values.\n\n#### Example\n\nYou can dispatch to custom objects by registering supporting dispatch functions. Say, for example, you love using [immutable][6] collections.\n\n```javascript\nvar _r = require('underarm'),\n    _ = require('underscore'),\n    Immutable = require('immutable'),\n    Vector = Immutable.Vector;\n\n_r.iterator.register(function(obj){\n  if(obj instanceof Vector){\n    return obj.values();\n  }\n});\n\n_r.empty.register(function(obj){\n  if(obj instanceof Vector){\n    return Vector.empty();\n  }\n});\n\n_r.append.register(function(obj, item){\n  if(obj instanceof Vector){\n    return obj.push(item);\n  }\n});\n\nfunction mult(x){\n  return function(y){\n    return x * y;\n  }\n}\n\nfunction isEven(x){\n  return !(x % 2);\n}\n\nvar vector = Vector(1,2,3,4);\n\n// Vector [ 1, 2, 3, 4 ]\n_r.sequence(vector);\n\n// Vector [ 3, 6, 9, 12 ]\n_r.sequence(_r.map(mult(3)), vector);\n\n// Vector [ 7, 14, 21, 28 ]\n_r(vector).map(mult(7)).sequence();\n\n// Vector [ 14, 28 ]\n_r(vector).map(mult(7)).filter(isEven).sequence();\n\n// Vector [ 1, 2, 3, 4 ]\n_r(vector).sequence();\n```\n\nBy default, `value` transduces into an empty array if multiple values are expected, and a single value if single value is expected to match the Underscore API. To override the behavior of multiple values, simply register a dispatch an empty for an undefined object (`value` calls `_r.empty()` which by default returns an array).\n\n```javascript\n// [ 1, 2, 3, 4 ]\n_r(vector).value();\n\n_r.empty.register(function(obj){\n  if(_.isUndefined(obj)){\n    return Vector.empty();\n  }\n});\n\n// Vector [ 1, 2, 3, 4 ]\n_r(vector).value();\n```\n\n#### Utils\nFinally, adds utils from [transduce][14].\n\n##### compose()\nSimple function composition of arguments. Useful for composing (combining) transducers.\n\n##### iterable(value)\nReturns the iterable for the parameter.  Returns value if conforms to iterable protocol. Returns `undefined` if cannot return en iterable.\n\nThe return value will either conform to iterator protocol that can be invoked for iteration or will be undefined.\n\nSupports anything that returns true for `isIterable` and converts arrays to iterables over each indexed item. Converts to functions to infinite iterables that always call function on next\n\n##### transformer(value)\nAttempts to convert the parameter into a transformer.  If cannot be converted, returns `undefined`.  If defined, the return value will have `init`, `step`, `result` methods that can be used for transformation.  Converts arrays (`arrayPush`), strings (`stringAppend`), objects (`objectMerge`), functions (wrap as reducing function) or anything that `isTransformer` into a transformer.\n\n##### protocols\nSymbols (or strings that act as symbols) for `@@iterator` and [`@@transformer`][10] that you can use to configure your custom objects.\n\n##### identity(value)\nAlways returns value\n\n##### arrayPush(arr, item)\nArray.push as a reducing function.  Calls push and returns array;\n\n##### objectMerge(object, item)\nMerges the item into the object.  If `item` is an array of length 2, uses first (0 index) as the key and the second (1 index) as the value.  Otherwise iterates over own properties of items and merges values with same keys into the result object.\n\n##### stringAppend(string, item)\nAppends item onto result using `+`.\n\n##### is{Array, String, RegExp, Number, Undefined}\nPredicates for object types\n\n#### License\nMIT\n\n[1]: http://underscorejs.org/\n[3]: http://simplectic.com/blog/2014/transducers-explained-1/\n[4]: http://simplectic.com/projects/underarm/\n[5]: http://clojure.org/transducers\n[6]: https://github.com/facebook/immutable-js\n[7]: https://github.com/transduce/transduce-stream\n[8]: https://github.com/kevinbeaty/any-promise\n[12]: https://raw.githubusercontent.com/kevinbeaty/underarm/master/build/underarm.js\n[13]: https://raw.githubusercontent.com/kevinbeaty/underarm/master/build/underarm.min.js\n[14]: https://github.com/transduce/transduce\n[15]: https://github.com/transduce/transduce-async\n[18]: https://github.com/kevinbeaty/underarm\n[20]: https://raw.githubusercontent.com/kevinbeaty/underarm/master/build/underarm.base.js\n[21]: https://raw.githubusercontent.com/kevinbeaty/underarm/master/build/underarm.base.min.js\n[22]: https://github.com/kevinbeaty/underarm/tree/master/underarm.base.js\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkevinbeaty%2Funderarm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fkevinbeaty%2Funderarm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fkevinbeaty%2Funderarm/lists"}