{"id":16906514,"url":"https://github.com/rsms/functional.js","last_synced_at":"2025-06-14T09:37:46.279Z","repository":{"id":57242885,"uuid":"81053492","full_name":"rsms/functional.js","owner":"rsms","description":"Work in a functional style with JavaScript and TypeScript","archived":false,"fork":false,"pushed_at":"2017-07-30T16:58:08.000Z","size":25,"stargazers_count":11,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-04-02T18:09:00.796Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/rsms.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":"2017-02-06T06:04:54.000Z","updated_at":"2021-05-20T11:51:21.000Z","dependencies_parsed_at":"2022-09-06T04:21:48.747Z","dependency_job_id":null,"html_url":"https://github.com/rsms/functional.js","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsms%2Ffunctional.js","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsms%2Ffunctional.js/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsms%2Ffunctional.js/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rsms%2Ffunctional.js/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rsms","download_url":"https://codeload.github.com/rsms/functional.js/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247805374,"owners_count":20999161,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","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":"2024-10-13T18:42:56.962Z","updated_at":"2025-04-11T15:26:29.598Z","avatar_url":"https://github.com/rsms.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# functional.js\n\nFunctional.js provides a set of functions\nfor working in a functional style with JavaScript and TypeScript\non lazy-evaluated sequences.\n\nBuild javascript into dist folder:\n\n```txt\n$ tsc\n```\n\nTry it with the interactive REPL:\n\n```\n$ ./repl\n❯ filter(city =\u003e city[0] == 'S',\n...      map(p =\u003e p.city, values(data.people)))\n[ 'Seattle',\n  'Stockholm',\n  'San Francisco',\n  ... ]\n❯\n```\n\nFrom NPM: [`yarn add func-js`](https://www.npmjs.com/package/func-js)\n\n## Introduction\n\nLet's start with something simple. Here's a map of some imaginary \"users\":\n\n```js\nconst users = new Map([\n  ['bob',   {name: 'Bob',   age: 28}],\n  ['anne',  {name: 'Anne',  age: 29}],\n  ['robin', {name: 'Robin', age: 33}],\n])\n```\n\nWe can `map` users to their names, resulting in a lazy `Seq\u003cstring\u003e`:\n\n```js\nconst names = map(([_, p]) =\u003e p.name, users)\n```\n\nWe can iterate over `names` with  provides an `Iterator` interface,\nmaking it work with anything that accepts an iterator:\n\n```js\nconsole.log('names:')\nfor (let name of names) {\n  console.log(' -', name)\n}\n// names:\n//  - Bob\n//  - Anne\n//  - Robin\n```\n\nOr just `collect` all values into an array:\n\n```js\nconsole.log('names:', collect(names))\n// names: [ 'Bob', 'Anne', 'Robin' ]\n```\n\n### Working with sequences\n\nOkay. Here are some small tech start-ups you probably haven't heard of:\n\n```js\nconst companies = new Set([\n  {name:'Microsoft', founded: {year: 1975, month: 4}},\n  {name:'Apple',     founded: {year: 1976, month: 4}},\n  {name:'Google',    founded: {year: 1998, month: 9}},\n  {name:'Facebook',  founded: {year: 2004, month: 2}},\n])\n```\n\nWe can `take` only the first three values,\nhere also mapping each company to its name.\nSince `take` returns a lazy sequence, no values are actually generated here,\nnor is our map function called.\n\n```js\nconst companyNames = map(c =\u003e c.name, companies)\nconst someCompanyNames = take(3, companyNames)\n```\n\n`collect` all values of someCompanyNames into an array and log it:\n\n```js\nconsole.log(\"first few companies' names:\",\n  collect(someCompanyNames))\n// first few companies' names: [ 'Microsoft', 'Apple', 'Google' ]\n```\n\nFrom here on, we will use a convenience function for logging to the console:\n\n```js\nfunction show(message, v) {\n  console.log(message, /*is just seq ... */ ? collect(take(50, v)) : v)\n}\n```\n\nIt makes our code easier to read. (This function is not part of `functional.js`.)\n\nLet's see what the average founding year of these companies is.\nFor this task we can `fold` the values together:\n\n```js\nlet foundingYears = map(x =\u003e x.founded.year, companies)\nshow('average founding year:',\n     fold((avgYear, year) =\u003e (avgYear + year) / 2,\n          foundingYears))\n// average founding year: 1995.375\n```\n\n`fold` is similar to `collect` but instead of returning an array\nof all values, it returns the _accumulated value_.\n`fold` operates left-to-right and is also knows as \"reduce\" and \"foldl\".\n\n`fold` can also take an initial value. When the initial value is omitted—as in our example above—the first value of the Seq is used as the initial accumulator.\nHere we provide an explicit initial value:\n\n```js\nshow('average founding year, including this year:',\n     fold((avgyear, year) =\u003e (avgyear + year) / 2,\n          foundingYears, new Date().getFullYear()))\n// average founding year, including this year: 1998\n// (when new Date().getFullYear() = 2017)\n```\n\nThe `foldr` function produces similar results to `fold`, but in the reverse order (from right to left):\n\n```js\nshow('some company names in reverse:',\n     foldr((names, name) =\u003e `${names} \u003e ${name}`,\n           someCompanyNames))\n// some company names in reverse: Google \u003e Apple \u003e Microsoft\n```\n\nNote that `foldr` uses more memory than `fold` and is limited by the stack-depth limit of JS runtimes that don't support tail-call elimination.\n\nSimilarly to `foldr`, `reverse` reverses a sequence:\n\n```js\nshow('some company names in reverse, again:',\n     reverse(someCompanyNames))\n// some company names in reverse, again: [ 'Google', 'Apple', 'Microsoft' ]\n```\n\nAs with `foldr`: beware that `reverse` requires as much memory as the sum of everything in the sequence, so don't use it on large sequences. If possible, create the initial sequence in reverse order instead of using the `reverse` function. For instance, if you start out with an array of items, pass the array itself to `reverse` before applying any other seqeunce operations.\n\n`drop` is a function similar to `take`, but rather than limiting outout, it skips over some number of values:\n\n```js\nshow('all companies but the two oldest:',\n     drop(2, companyNames))\n// all companies but the two oldest: [ 'Google', 'Facebook' ]\n```\n\nThe `filter` function can be used to skip values which doesn't pass some criteria:\n\n```js\nshow('companies which name ends in \"le\":',\n     map(c =\u003e c.name,\n         filter(c =\u003e c.name.substr(-2) == \"le\",\n                companies)))\n// companies which name ends in \"le\": [ 'Apple', 'Google' ]\n```\n\nThe function passed to `filter` decides if an item is included (if the function returns true), or skipped (when the function returns false.)\n\nLet's test if a seq is `empty`:\n\n```js\nshow('Is there any company which name ends in \"le\"?',\n     !empty(filter(c =\u003e c.name.substr(-2) == \"le\",\n                   companies)))\n// Is there any company which name ends in \"le\"? true\n\nshow('Is there any company which name ends in \"x\"?',\n     !empty(filter(c =\u003e c.name.substr(-2) == \"x\",\n                   companies)))\n// Is there any company which name ends in \"x\"? false\n```\n\nAgain, since `seq`s are lazy, in our first case above, only one of the filter functions are called. This is different from how the standard Array functions in JavaScript works where each operation is performed on every single item before continuing with another operation. In most cases dealing with Seqs is faster than using `Array.prototype.map`, `.filter` and friends.\n\nUsing the `any` function, we can implement the above code in a more readable way:\n\n```js\nshow('Is there any company which name ends in \"le\"?',\n     any(c =\u003e c.name.substr(-2) == \"le\",\n         companies))\n// Is there any company which name ends in \"le\"? true\n\nshow('Is there any company which name ends in \"x\"?',\n     any(c =\u003e c.name.substr(-2) == \"x\",\n         companies))\n// Is there any company which name ends in \"x\"? false\n```\n\nThe `all` function can be used to check if all values fit a certain criteria:\n\n```js\nshow('Does all company names contain an \"e\"?',\n     all(c =\u003e c.name.indexOf(\"e\") != -1,\n         companies))\n// Does all company names contain an \"e\"? false\n\nconst yearToday = new Date().getFullYear()\nshow('Were all companies founded in the last 50 years?',\n     all(c =\u003e yearToday - c.founded.year \u003c 50,\n         companies))\n// Were all companies founded in the last 50 years?? true\n```\n\n`zip` is a useful function that takes two sequences and produces a sequence of tuples containing the respective input sequences' values:\n\n```js\nconst namez = zip(map(c =\u003e c.name, companies),\n                  map(p =\u003e p.name, values(users)))\nshow('zipping company name with user name:', namez)\n// zipping company name with user name: [ [ 'Microsoft', 'Bob' ],\n//   [ 'Apple', 'Anne' ],\n//   [ 'Google', 'Robin' ] ]\n```\n\nSince many of the standard JavaScript collections accept Iterables (which Seq is), we can use zip to easily build things like Maps:\n\n```js\nshow('Map of company name to user name:', new Map(namez))\n// Map of company name to user name: Map {\n//  'Microsoft' =\u003e 'Bob', 'Apple' =\u003e 'Anne', 'Google' =\u003e 'Robin' }\n```\n\nWe can `zip` any number of sequences together:\n\n```js\nconst ln = '\\n  '\nshow('A bit of history on some imaginary people:', ln +\n     join(ln,\n          map(([year, company, name, nickname]) =\u003e\n                `${name} aka \"${nickname}\" at ${company} in ${year}`,\n              zip(map(x =\u003e x.founded.year, companies),\n                  map(x =\u003e x.name, companies),\n                  map(p =\u003e p.name, values(users)),\n                  keys(users) ))))\n// A bit of history on some imaginary people:\n//   Bob aka \"bobby\" at Microsoft in 1975\n//   Anne aka \"ann3\" at Apple in 1976\n//   Robin aka \"rob\" at Google in 1998\n```\n\nThe `zipf` function allows us to produce anything; nost just lists of values:\n\n```js\nshow('A bit of history on some imaginary people:', ln +\n     join(ln,\n          zipf((year, company, name, nickname) =\u003e\n                 `${name} aka \"${nickname}\" at ${company} in ${year}`,\n               map(x =\u003e x.founded.year, companies),\n               map(x =\u003e x.name, companies),\n               map(p =\u003e p.name, values(users)),\n               keys(users) )))\n// A bit of history on some imaginary people:\n//   Bob aka \"bobby\" at Microsoft in 1975\n//   Anne aka \"ann3\" at Apple in 1976\n//   Robin aka \"rob\" at Google in 1998\n```\n\n\n### Creating a `Seq`\n\nA `Seq` is simply an object that provides an iterator interface for producing values. Most functions in `functional.js` that return a Seq returns a _lazy_ sequence, meaning its values are generated only when needed.\n\nTo create a lazy sequence from some existing data, pass anything to the `seq` function:\n\n```js\nshow('items of an array:', seq([1, 2, 3]))\n// items of an array: [ 1, 2, 3 ]\n\nshow('characters of text:', seq(\"hello😀\"))\n// characters of text: [ 'h', 'e', 'l', 'l', 'o', '😀' ]\n\nshow('keys and values of an object:',\n     seq({\n       bob:            \"Happy\",\n       Anne:           \"Hungry\",\n       \"Frans-Harald\": \"Bored\"\n     }))\n// keys and values of an object: [\n//   [ 'bob', 'Happy' ],\n//   [ 'Anne', 'Hungry' ],\n//   [ 'Frans-Harald', 'Bored' ] ]\n```\n\nOftentimes you have data that's constant or somehow predefined, in which case the `seq` function can efficiently convert anything into a `Seq`. The neat thing about this design is that any item implementing [the iterable protocol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols) is a valid `Seq`. This includes all collection types of ES5 (Array, TypedArray, string, etc); when such an item is passed to the `seq` function, the item is simply returned. The only case where `seq` creates a new `Seq` object is when the item provided is an object and \n\nUse the `charseq` function to get a Seq of UTF-16 codepoints of some string (instead of grapheme clusters, as is the case with `seq` on strings):\n\n```js\nshow('characters as UTF-16 codepoints:',\n     charseq(\"hello😀\"))\n// characters as UTF-16 codepoints: [ 104, 101, 108, 108, 111, 55357, 56832 ]\n```\n\n`range` is a useful function for declaring ranges of numbers with an optional \"step\" arguments that controls the step increment:\n\n```js\nshow('range(0,4):    ', range(0,4))\nshow('range(2,5):    ', range(2,5))\nshow('range(-3,3):   ', range(-3,3))\nshow('range(0,20,5): ', range(0,20,5))\n// range(0,4):     [ 0, 1, 2, 3 ]\n// range(2,5):     [ 2, 3, 4 ]\n// range(-3,3):    [ -3, -2, -1, 0, 1, 2 ]\n// range(0,20,5):  [ 0, 5, 10, 15 ]\n```\n\nBecause `Seq`s are lazy, we can even declare infinite ranges by leaving out the `end` argument, or by using `Infinite`:\n\n```js\nshow('take(4, range()):    ', take(4, range()))\nshow('take(4, range(100)): ', take(4, range(100)))\nshow('take(4, range(100, Infinity, 100)): ',\n  take(4, range(100, Infinity, 100)))\n// take(4, range()):     [ 0, 1, 2, 3 ]\n// take(4, range(100)):  [ 100, 101, 102, 103 ]\n// take(4, range(100, Infinity, 100)):  [ 100, 200, 300, 400 ]\n```\n\nMore complex sequences can be created by providing a function that creates Iterators:\n\n```js\nshow('Custom iterable with generator:',\n     seq(function*(){\n       for (let i = 3; i; --i) {\n         yield '#' + Math.random().toFixed(3)\n       }\n     }))\n// Custom iterable with generator: [ '#0.354', '#0.295', '#0.291' ]\n\nshow('Custom iterable with function:',\n     seq(() =\u003e ({\n       i: 3,\n       next() { return {\n         value: '#' + Math.random().toFixed(3),\n         done: --this.i \u003c 0\n       }}\n     })))\n// Custom iterable with function: [ '#0.219', '#0.746', '#0.917' ]\n```\n\n### Conveniences\n\n`apply` is a convenience function for causing side-effects, like printing something to the console. It's like `forEach` but operates on lazy sequences:\n\n```js\napply(console.log, foundingYears)\n// 1975\n// 1976\n// 1998\n// 2004\n```\n\n`join` glues values together into a string:\n\n```js\nshow('Company months:',\n     join('/', map(c =\u003e c.founded.month,\n                   companies)))\n// Company months: 4/4/9/2\n```\n\n`avg` calculates the average of all numbers:\n\n```js\nshow('average founding year:',\n     avg(foundingYears))\n// average founding year: 1995.375\n```\n\n`sum`, `min` and `max` returns the sum, smallest and largest number, respectively:\n\n```js\nconst userAges = map(([_, p]) =\u003e p.age, users)\nshow(\"sum of users' age:\", sum(userAges))\nshow(\"youngest user age:\", min(userAges))\nshow(\"oldest user age:  \", max(userAges))\n// sum of users' age: 90\n// youngest user age: 28\n// oldest user age:   33\n```\n\n`nth` returns the value at a certain \"index\" into the sequence:\n\n```js\nshow('4th company in the list:',\n     nth(3, companies).name)\n// 4th company in the list: Facebook\n```\n\nNote that this means generating values for—and throwing away the results of—intermediate values.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frsms%2Ffunctional.js","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frsms%2Ffunctional.js","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frsms%2Ffunctional.js/lists"}