{"id":22377627,"url":"https://github.com/pimm/ruply","last_synced_at":"2025-07-30T23:32:10.332Z","repository":{"id":47636815,"uuid":"305335484","full_name":"Pimm/ruply","owner":"Pimm","description":"run[If] and apply functions for easy-to-read code","archived":false,"fork":false,"pushed_at":"2023-05-25T08:40:29.000Z","size":301,"stargazers_count":6,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-07-16T10:30:46.997Z","etag":null,"topics":["functional-js","javascript"],"latest_commit_sha":null,"homepage":"","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/Pimm.png","metadata":{"files":{"readme":"readme.md","changelog":"changelog.md","contributing":null,"funding":null,"license":"copying.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2020-10-19T09:44:50.000Z","updated_at":"2022-03-02T20:33:14.000Z","dependencies_parsed_at":"2023-01-22T14:15:58.871Z","dependency_job_id":null,"html_url":"https://github.com/Pimm/ruply","commit_stats":null,"previous_names":[],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/Pimm/ruply","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pimm%2Fruply","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pimm%2Fruply/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pimm%2Fruply/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pimm%2Fruply/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/Pimm","download_url":"https://codeload.github.com/Pimm/ruply/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/Pimm%2Fruply/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":267960696,"owners_count":24172506,"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","status":"online","status_checked_at":"2025-07-30T02:00:09.044Z","response_time":70,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"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":["functional-js","javascript"],"created_at":"2024-12-04T22:14:58.473Z","updated_at":"2025-07-30T23:32:09.906Z","avatar_url":"https://github.com/Pimm.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# ruply \u0026middot; [![License (X11/MIT)](https://badgen.net/github/license/pimm/ruply)](https://github.com/Pimm/ruply/blob/master/copying.txt) [![npm version](https://badgen.net/npm/v/ruply)](https://www.npmjs.com/package/ruply) [![Test status](https://github.com/Pimm/ruply/actions/workflows/test.yaml/badge.svg)](https://github.com/Pimm/ruply/actions/workflows/test.yaml) [![Coverage status](https://coveralls.io/repos/github/Pimm/ruply/badge.svg?branch=master)](https://coveralls.io/github/Pimm/ruply?branch=master)\n\n`run[If]` and `apply` are functions that can help you craft easy-to-read code.\n\n# `run`\n\n`run` forwards the first argument to the callback, and returns the result.\n\n```javascript\n// Calculate the area.\nconst area = run(\n\tgetSize(),\n\t({ width, height }) =\u003e width * height\n);\n```\n`width` and `height` are neatly confined to the callback. The alternative without `run` has those variables overstay their welcome, polluting the scope:\n```javascript\n// Calculate the area.\nconst { width, height } = getSize();\nconst area = width * height;\n```\n\n# `apply`\n\n`apply` forwards the first argument to the callback ‒ just like `run` does ‒ but returns that argument instead of the result.\n\n```javascript\n// Remove the item from the selection.\nsetState(previousSelection =\u003e apply(\n\tnew Set(previousSelection),\n\tselection =\u003e selection.delete(item)\n));\n```\nThe alternative without `apply` is more wordy:\n```javascript\n// Remove the item from the selection.\nsetState(previousSelection =\u003e {\n\tconst selection = new Set(previousSelection);\n\tselection.delete(item);\n\treturn selection;\n});\n```\n\n# `runIf`\n\n`runIf` skips the callback if the first argument is [nullish](https://developer.mozilla.org/docs/Glossary/Nullish), and behaves the same as `run` otherwise. `runIf` is to `run` as the `?.` operator is to the `.` operator.\n\n```javascript\n// Parse the timestamp (if any).\nconst timestamp = runIf(\n\tresponse.data.timestamp,\n\tDate.parse\n);\n```\nAlternatives include this:\n```javascript\n// Parse the timestamp (if any).\nlet { timestamp } = response.data;\nif (timestamp != undefined) {\n\ttimestamp = Date.parse(timestamp);\n}\n```\nAnd this\u003csup\u003e*\u003c/sup\u003e:\n```javascript\n// Parse the timestamp (if any).\nlet timestamp = Date.parse(response.data.timestamp);\nif (isNaN(timestamp)) {\n\ttimestamp = undefined;\n}\n```\nAnd this dangerous\u003csup\u003e**\u003c/sup\u003e one:\n```javascript\n// Parse the timestamp (if any).\nconst timestamp = response.data.timestamp\n\t\u0026\u0026 Date.parse(response.data.timestamp);\n```\n\n[*] In the alternative with `isNaN`, invalid timestamps (such as `'invalid'`) are indistinguishable from missing timestamps. This is likely unexpected behaviour.\n\n[**] In the alternative with the `\u0026\u0026` operator, `Date.parse` is skipped not only if the timestamp is `undefined` but also if it is `''` (or any other [falsy value](https://developer.mozilla.org/docs/Glossary/Falsy)). This too is likely unexpected.\n\n### `runIf` and `??`\n\nYou can combine `runIf` and the `??` operator to provide a fallback used if the callback is skipped.\n\n```javascript\nreturn runIf(event.data, JSON.parse) ?? {};\n```\n\n# Installation\n\nInstall `ruply` using npm or Yarn and import the functions:\n```javascript\nimport { run, apply, runIf } from 'ruply';\n```\n\n# Under the hood\n\nThese are simplified implementations of `run[If]` and `apply` (without support for promises or chains):\n\n```javascript\nfunction run(value, callback) {\n\treturn callback(value);\n}\n\nfunction apply(value, callback) {\n\tcallback(value);\n\treturn value;\n}\n\nfunction runIf(value, callback) {\n\treturn value != null ? callback(value) : value;\n}\n```\n\n# Rationale\n\nThese functions are designed to help your code better convey your intentions to the reader.\n\nNon-goals include:\n1. producing the shortest possible code, and\n2. producing clever code.\n\nUse `run[If]` and `apply` in situations where you feel they help the reader of your code.\n\n# More examples\n\n## Shielded variable\n\n```javascript\nlet available = 0;\nfunction generateID() {\n\treturn available++;\n}\n```\n`run` scopes the variable, protecting it from accidental access.\n```javascript\nconst generateID = run(0, available =\u003e () =\u003e available++);\n```\n\n## Encapsulated logic\n\n```javascript\nlogger.log(`Received ${bundles.reduce(\n\t(sum, { messages }) =\u003e sum + messages.length, 0\n)} message(s)`);\n```\n`run` moves the summing logic outside the template literal.\n```javascript\nrun(\n\tbundles.reduce(\n\t\t(sum, { messages }) =\u003e sum + messages.length, 0\n\t),\n\tmessageCount =\u003e\n\t\tlogger.log(`Received ${messageCount} message(s)`)\n);\n```\n\n## Side effect in fallback\n\n```javascript\nif (data.has(index)) {\n\treturn data.get(index);\n}\n// If the buffer does not exist, allocate it…\nconst buffer = Buffer.alloc(size);\n// …and store it in the map.\ndata.set(index, buffer);\nreturn buffer;\n```\n`apply` rearranges the pieces, producing code closer to human speech.\n```javascript\nreturn data.get(index)\n\t?? apply(\n\t\t// If the buffer does not exist, allocate it…\n\t\tBuffer.alloc(size),\n\t\t// …and store it in the map.\n\t\tbuffer =\u003e data.set(index, buffer)\n\t);\n```\n\n## Timed asynchronous function\n\n```javascript\nconst start = performance.now();\nconst result = await queryDatabase();\nconsole.log(`${performance.now() - start} ms`);\nreturn result;\n```\n`apply` removes the need for the short-lived variable, and places `return` and `queryDatabase()` closer together.\n```javascript\nconst start = performance.now();\nreturn apply(\n\tqueryDatabase(),\n\t() =\u003e console.log(`${performance.now() - start} ms`)\n);\n```\nThe `await` keyword is optional, as `apply` is promise-aware.\n\n## Optional chain\n\n```javascript\nconst token =\n\trequest.headers.authorization != undefined\n\t\t? /^Bearer\\s+(.*)$/.exec(\n\t\t\trequest.headers.authorization\n\t\t)?.[1]\n\t\t: undefined;\n```\n`runIf` cuts out the `undefined` check (for cases where the header is missing) as well as the `?.` operator (for cases where the regular expression does not match).\n```javascript\nconst token = runIf(\n\trequest.headers.authorization,\n\theaderValue =\u003e /^Bearer\\s+(.*)$/.exec(headerValue),\n\t([, token]) =\u003e token\n);\n```\nIf you prefer keeping the `?.` operator, then you should.\n```javascript\nconst token = runIf(\n\trequest.headers.authorization,\n\theaderValue =\u003e /^Bearer\\s+(.*)$/.exec(headerValue)\n)?.[1];\n```\n\n## Optional step\n\n```javascript\nconst value = await cache.get(key);\n// Disregard the value if it has expired.\nif (value == null || value.expiration \u003c Date.now()) {\n\treturn null;\n}\nreturn value;\n```\n`runIf` cuts out the `null` check.\n```javascript\nreturn runIf(\n\tcache.get(key),\n\t// Disregard the value if it has expired.\n\tvalue =\u003e value.expiration \u003c Date.now() ? null : value\n);\n```\nThe `await` keyword is optional, as `runIf` is promise-aware.\n\n## Memoisation\n\n```javascript\nconst orders = new Map();\nfunction memoGetOrder(id) {\n\tif (orders.has(id)) {\n\t\treturn orders.get(id);\n\t}\n\tconst order = getOrder(id);\n\torders.set(id, order);\n\treturn order;\n}\n```\n`run` scopes the map, while `apply` rearranges the pieces of the function body to match the human speech equivalent.\n```javascript\nconst memoGetOrder = run(new Map(), orders =\u003e\n\tid =\u003e orders.get(id)\n\t\t?? apply(getOrder(id), order =\u003e orders.set(id, order))\n);\n```\n\n# Pronunciation\n\n\u003e /ɹʌˈplaɪ/\n\nLike _ru\u003cs\u003en a\u003c/s\u003epply_.\n\n# License (X11/MIT)\nCopyright (c) 2020, 2021 Pimm \"de Chinchilla\" Hogeling\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.\n\n**The Software is provided \"as is\", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement. in no event shall the authors or copyright holders be liable for any claim, damages or other liability, whether in an action of contract, tort or otherwise, arising from, out of or in connection with the Software or the use or other dealings in the Software.**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpimm%2Fruply","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fpimm%2Fruply","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fpimm%2Fruply/lists"}