{"id":23683479,"url":"https://github.com/joellefkowitz/funky","last_synced_at":"2026-02-12T10:32:52.961Z","repository":{"id":237528936,"uuid":"794649306","full_name":"JoelLefkowitz/funky","owner":"JoelLefkowitz","description":"Functional and ergonomic transformations.","archived":false,"fork":false,"pushed_at":"2025-02-23T14:45:08.000Z","size":349,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-10-21T23:36:01.840Z","etag":null,"topics":["applicative","functional","functor","instance","monad"],"latest_commit_sha":null,"homepage":"","language":"C++","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/JoelLefkowitz.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE.md","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2024-05-01T16:59:09.000Z","updated_at":"2025-02-23T14:45:12.000Z","dependencies_parsed_at":null,"dependency_job_id":"b00996d6-ec13-445a-9090-33b6c29ee6b1","html_url":"https://github.com/JoelLefkowitz/funky","commit_stats":null,"previous_names":["joellefkowitz/functional","joellefkowitz/funky"],"tags_count":4,"template":false,"template_full_name":null,"purl":"pkg:github/JoelLefkowitz/funky","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoelLefkowitz%2Ffunky","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoelLefkowitz%2Ffunky/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoelLefkowitz%2Ffunky/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoelLefkowitz%2Ffunky/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JoelLefkowitz","download_url":"https://codeload.github.com/JoelLefkowitz/funky/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JoelLefkowitz%2Ffunky/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":29363154,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-02-12T08:51:36.827Z","status":"ssl_error","status_checked_at":"2026-02-12T08:51:26.849Z","response_time":55,"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":["applicative","functional","functor","instance","monad"],"created_at":"2024-12-29T20:07:55.608Z","updated_at":"2026-02-12T10:32:52.938Z","avatar_url":"https://github.com/JoelLefkowitz.png","language":"C++","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Funky\n\nFunctional and ergonomic transformations.\n\n![Review](https://img.shields.io/github/actions/workflow/status/JoelLefkowitz/funky/review.yaml)\n![Quality](https://img.shields.io/codacy/grade/_)\n\n## Installing\n\n```bash\nconan install funky\n```\n\nYou can also download the [sources](https://download-directory.github.io?url=https://github.com/JoelLefkowitz/funky/tree/master/src).\n\n## Motivation\n\nRather than this:\n\n```cpp\n#include \u003calgorithm\u003e\n#include \u003cvector\u003e\n\nstd::vector\u003cint\u003e sequence({1, 2, 3});\nstd::vector\u003cint\u003e mapped;\n\nstd::transform(\n    sequence.begin(),\n    sequence.end(),\n    std::back_inserter(mapped),\n    [](auto x) { return x + 1; }\n);\n```\n\nLet's write this:\n\n```cpp\n#include \u003cfunky/iterables/iterables.tpp\u003e\n#include \u003cvector\u003e\n\nstd::vector\u003cint\u003e sequence({1, 2, 3});\n\nauto mapped = map\u003cstd::vector\u003cint\u003e\u003e(\n    [](auto x) { return x + 1; },\n    sequence\n);\n```\n\nNow `mapped` will still be:\n\n```cpp\nstd::vector\u003cint\u003e({2, 3, 4});\n```\n\nAnd this function doesn't mutate the input and is much easier to read.\n\n\u003e Most structures are small enough that immutability makes transformations easier to read and thread safe without loss of performance.\n\nThe C++ templating system is smart enough to evaluate the types involved in these kinds of transformations for us. This means we can avoid using std::function or interfaces to declare the types involved. This also means we don't need to define concepts for `Functor` or `Foldable` to use `map` and `fold`.\n\nOur `map` function makes an empty container and still uses `.begin()` and `.end()` internally to map the results. This makes it just as generic as `std::transform` and suitable for transforming any iterable structure. This is reflected in its type signature:\n\n```cpp\n// map ≔ (A → B) → [ A ] → [ B ]\n```\n\nIf we do need to mutable a structure we can use the functions in `mutable.tpp`:\n\n```cpp\n#include \u003cfunky/iterables/mutable.tpp\u003e\n#include \u003cvector\u003e\n\nauto sequence = std::vector\u003cint\u003e({1, 2, 3, 2});\nremove(sequence, 2);\n```\n\nNow `sequence` will be updated in place:\n\n```cpp\nstd::vector\u003cint\u003e({1, 3});\n```\n\n## Documentation\n\nDocumentation and more detailed examples are hosted on [Github Pages](https://joellefkowitz.github.io/funky).\n\n## Usage\n\nThis package makes use of templates. The definitions and implementations are split between `.hpp` and `.tpp` files. If you are calling the functions implemented in them then include the `.tpp` file instead.\n\n## Overview\n\n### Callables\n\nAvailable in: `funky/generics/callables.hpp`\n\n#### compose\n\n```cpp\n// T → U → auto\nauto compose(const T \u0026f, const U \u0026g);\n\n// T → U → auto\nauto compose(const T \u0026f, const U \u0026...gs);\n```\n\n#### pipe\n\n```cpp\n// T → U → auto\nauto pipe(const T \u0026f, const U \u0026...gs);\n```\n\n### Iterables\n\nAvailable in: `funky/generics/iterables.hpp`\n\n#### convert\n\n```cpp\n// [ A ] → [ B ]\nFB convert(const FA \u0026source);\n```\n\n#### reverse\n\n```cpp\n// [ A ] → [ A ]\nFA reverse(const FA \u0026vec);\n```\n\n#### map\n\n```cpp\n// (char → char) → std::string → std::string\nstd::string map(const T \u0026mapper, const std::string \u0026source);\n\n// (A → B → C) → std::map\u003cA, B\u003e → std::vector\u003cC\u003e\nstd::vector\u003cC\u003e map(const T \u0026mapper, const std::map\u003cA, B\u003e \u0026source);\n\n// (A → B) → [ A ] → [ B ]\nFB map(const T \u0026mapper, const FA \u0026source);\n\n// (A → size_t → B) → [ A ] → [ B ]\nFB map(const T \u0026mapper, const FA \u0026source);\n```\n\n#### foreach\n\n```cpp\n// (A → void) → [ A ] → void\nvoid foreach (const T \u0026effect, const FA \u0026source);\n\n// (A → size_t → void) → [ A ] → void\nvoid foreach (const T \u0026effect, const FA \u0026source);\n```\n\n#### filter\n\n```cpp\n// (A → bool) → [ A ] → [ A ]\nFA filter(const T \u0026condition, const FA \u0026source);\n```\n\n#### fold\n\n```cpp\n// (B → A → B) → B → [ A ] → B\nB fold(const T \u0026folder, const B \u0026initial, const FA \u0026source);\n```\n\n#### concat\n\n```cpp\n// [ A ] → A → [ A ]\nFA concat(const FA \u0026vec, const A \u0026x);\n\n// [ A ] → [ A ] → [ A ]\nFA concat(const FA \u0026vec, const FA \u0026x);\n```\n\n#### flatten\n\n```cpp\n// std::vector\u003c[ A ]\u003e → [ A ]\nFA flatten(const std::vector\u003cFA\u003e \u0026vec);\n```\n\n#### slice\n\n```cpp\n// [ A ] → size_t → size_t → [ A ]\nFA slice(const FA \u0026vec, const size_t start, const size_t end);\n```\n\n#### slice_first\n\n```cpp\n// [ A ] → size_t → [ A ]\nFA slice_first(const FA \u0026vec, size_t width);\n```\n\n#### slice_last\n\n```cpp\n// [ A ] → size_t → [ A ]\nFA slice_last(const FA \u0026vec, size_t width);\n```\n\n#### drop_first\n\n```cpp\n// [ A ] → size_t → [ A ]\nFA drop_first(const FA \u0026vec, size_t width);\n```\n\n#### drop_last\n\n```cpp\n// [ A ] → size_t → [ A ]\nFA drop_last(const FA \u0026vec, size_t width);\n```\n\n#### aperture\n\n```cpp\n// [ A ] → size_t → std::vector\u003c[ A ]\u003e\nstd::vector\u003cFA\u003e aperture(const FA \u0026vec, size_t width);\n```\n\n### Sets\n\nAvailable in: `funky/generics/sets.hpp`\n\n#### contains\n\n```cpp\n// [ A ] → A → bool\nbool contains(const FA \u0026vec, const A \u0026x);\n```\n\n#### is_unique\n\n```cpp\n// [ A ] → bool\nbool is_unique(const FA \u0026vec);\n```\n\n#### unique\n\n```cpp\n// [ A ] → [ A ]\nFA unique(const FA \u0026vec);\n```\n\n#### intersects\n\n```cpp\n// [ A ] → [ A ] → bool\nbool intersects(const FA \u0026source, const FA \u0026target);\n```\n\n#### intersection\n\n```cpp\n// [ A ] → [ A ] → [ A ]\nFA intersection(const FA \u0026source, const FA \u0026target);\n```\n\n#### difference\n\n```cpp\n// [ A ] → [ A ] → [ A ]\nFA difference(const FA \u0026source, const FA \u0026target);\n```\n\n#### index\n\n```cpp\n// [ A ] → A → size_t\nsize_t index(const FA \u0026vec, const A \u0026x);\n```\n\n#### find\n\n```cpp\n// (A → bool) → [ A ] → size_t\nsize_t find(const T \u0026condition, const FA \u0026source);\n```\n\n#### min\n\n```cpp\n// [ A ] → A → A\nA min(const FA \u0026vec, A empty);\n```\n\n#### max\n\n```cpp\n// [ A ] → A → A\nA max(const FA \u0026vec, A empty);\n```\n\n### Pairs\n\nAvailable in: `funky/generics/pairs.hpp`\n\n#### zip\n\n```cpp\n// [ A ] → [ B ] → std::vector\u003cstd::pair\u003cA, B\u003e\u003e\nstd::vector\u003cstd::pair\u003cA, B\u003e\u003e zip(const FA \u0026a, const FB \u0026b);\n```\n\n#### product\n\n```cpp\n// [ A ] → [ B ] → std::vector\u003cstd::pair\u003cA, B\u003e\u003e\nstd::vector\u003cstd::pair\u003cA, B\u003e\u003e product(const FA \u0026a, const FB \u0026b);\n```\n\n#### pairs\n\n```cpp\n// std::map\u003cA, B\u003e → std::vector\u003cstd::pair\u003cA, B\u003e\u003e\nstd::vector\u003cstd::pair\u003cA, B\u003e\u003e pairs(const std::map\u003cA, B\u003e \u0026source);\n```\n\n### Mutable\n\nAvailable in: `funky/generics/mutable.hpp`\n\n#### insert\n\n```cpp\n// [ A ] → [ A ] → void\nvoid insert(const FA \u0026source, FA \u0026target);\n```\n\n#### remove\n\n```cpp\n// [ A ] → A → void\nvoid remove(FA \u0026vec, const A \u0026x);\n```\n\n#### partition\n\n```cpp\n// (A → bool) → [ A ] → void\nvoid partition(const T \u0026filter, FA \u0026vec);\n```\n\n### Booleans\n\nAvailable in: `funky/concrete/booleans.hpp`\n\n#### all\n\n```cpp\n// [ A ] → bool\nbool all(const FA \u0026source);\n\n// (A → bool) → [ A ] → bool\nbool all(const T \u0026predicate, const FA \u0026source);\n```\n\n#### any\n\n```cpp\n// [ A ] → bool\nbool any(const FA \u0026source);\n\n// (A → bool) → [ A ] → bool\nbool any(const T \u0026predicate, const FA \u0026source);\n```\n\n#### at_least\n\n```cpp\n// size_t → [ A ] → bool\nbool at_least(size_t min, const FA \u0026source);\n\n// (A → bool) → size_t → [ A ] → bool\nbool at_least(const T \u0026predicate, size_t min, const FA \u0026source);\n```\n\n### Numbers\n\nAvailable in: `funky/concrete/numbers.hpp`\n\n#### fractional\n\n```cpp\n// double → double\ndouble fractional(double x);\n```\n\n#### round\n\n```cpp\n// double → size_t → double\ndouble round(double x, size_t dps);\n```\n\n#### sign\n\n```cpp\n// int → int\nint sign(int n);\n```\n\n#### order\n\n```cpp\n// double → int\nint order(double n);\n```\n\n#### proportion\n\n```cpp\n// double → double\ndouble proportion(double x);\n```\n\n#### normalise\n\n```cpp\n// double → double → double → double → double\ndouble normalise(double x, double min, double max, double scale);\n```\n\n#### is_factor\n\n```cpp\n// int → int → bool\nbool is_factor(int divisor, int dividend);\n```\n\n#### has_factor\n\n```cpp\n// int → int → bool\nbool has_factor(int dividend, int divisor);\n```\n\n#### ratio\n\n```cpp\n// size_t → double → double\ndouble ratio(size_t dividend, double divisor);\n\n// double → size_t → double\ndouble ratio(double dividend, size_t divisor);\n\n// size_t → size_t → double\ndouble ratio(size_t dividend, size_t divisor);\n```\n\n#### unit\n\n```cpp\n// double → std::string → std::string\nstd::string unit(double n, const std::string \u0026metric);\n```\n\n#### bounds\n\n```cpp\n// A → A → A → bool\nbool bounds(A lower, A x, A higher);\n```\n\n#### includes\n\n```cpp\n// A → A → A → bool\nbool includes(A lower, A x, A higher);\n```\n\n#### clamp\n\n```cpp\n// A → A → A → A\nA clamp(A lower, A x, A higher);\n```\n\n### Strings\n\nAvailable in: `funky/concrete/strings.hpp`\n\n#### starts_with\n\n```cpp\n// std::string → std::string → bool\nbool starts_with(const std::string \u0026str, const std::string \u0026prefix);\n```\n\n#### ends_with\n\n```cpp\n// std::string → std::string → bool\nbool ends_with(const std::string \u0026str, const std::string \u0026suffix);\n```\n\n#### reverse\n\n```cpp\n// std::string → std::string\nstd::string reverse(const std::string \u0026str);\n```\n\n#### pad\n\n```cpp\n// std::string → size_t → std::string\nstd::string pad(const std::string \u0026str, size_t size);\n```\n\n#### truncate\n\n```cpp\n// std::string → size_t → std::string → std::string\nstd::string truncate(const std::string \u0026str,size_t limit,const std::string \u0026ellipsis = \"...\");\n```\n\n#### uppercase\n\n```cpp\n// std::string → std::string\nstd::string uppercase(const std::string \u0026str);\n```\n\n#### lowercase\n\n```cpp\n// std::string → std::string\nstd::string lowercase(const std::string \u0026str);\n```\n\n#### split\n\n```cpp\n// std::string → std::string → std::vector\u003cstd::string\u003e\nstd::vector\u003cstd::string\u003e split(const std::string \u0026str,const std::string \u0026delimiter);\n```\n\n#### join\n\n```cpp\n// std::vector\u003cstd::string\u003e → std::string → std::string\nstd::string join(const std::vector\u003cstd::string\u003e \u0026strings,const std::string \u0026delimiter = \"\");\n```\n\n#### chunk\n\n```cpp\n// std::string → size_t → std::vector\u003cstd::string\u003e\nstd::vector\u003cstd::string\u003e chunk(const std::string \u0026str, size_t size);\n```\n\n#### without_substrings\n\n```cpp\n// std::string → std::vector\u003cstd::string\u003e → std::string\nstd::string without_substrings(const std::string \u0026str,const std::vector\u003cstd::string\u003e \u0026substr);\n```\n\n#### format_hex\n\n```cpp\n// int → size_t → std::string\nstd::string format_hex(int n, size_t length = 6UL);\n```\n\n#### parse_hex\n\n```cpp\n// std::string → int\nint parse_hex(const std::string \u0026str);\n```\n\n### Vectors\n\nAvailable in: `funky/concrete/vectors.hpp`\n\n#### range\n\n```cpp\n// size_t → std::vector\u003csize_t\u003e\nstd::vector\u003csize_t\u003e range(size_t limit);\n\n// A → A → A → std::vector\u003cA\u003e\nstd::vector\u003cA\u003e range(A start, A stop, A step = 1);\n```\n\n#### linspace\n\n```cpp\n// A → A → size_t → std::vector\u003cA\u003e\nstd::vector\u003cA\u003e linspace(A start, A stop, size_t count);\n```\n\n#### enumerate\n\n```cpp\n// std::vector\u003cA\u003e → std::vector\u003cIndexed\u003cA\u003e\u003e\nstd::vector\u003cIndexed\u003cA\u003e\u003e enumerate(const std::vector\u003cA\u003e \u0026vec);\n```\n\n## Tooling\n\n### Dependencies\n\nTo install dependencies:\n\n```bash\nyarn install\npip install .[all]\nconan install .\n```\n\n### Tests\n\nTo run tests:\n\n```bash\nscons test\n```\n\n### Documentation\n\nTo generate the documentation locally:\n\n```bash\nscons docs\n```\n\n### Linters\n\nTo run linters:\n\n```bash\nscons lint\n```\n\n### Formatters\n\nTo run formatters:\n\n```bash\nscons format\n```\n\n### Publishing\n\nThe [ConanCenter](https://conan.io/center) doesn't yet allow users to publish packages independently. Package recipes are submitted to the [conan-center-index](https://github.com/conan-io/conan-center-index). A copy of this recipe is kept in this repository in the `publish` folder. This allows us to test that the recipe is compatible with new versions and makes it easier to submit updates to the conan-center-index.\n\nTo test the recipe can build the latest published tag:\n\n```bash\nconan create publish/all/conanfile.py --version $(yq -r \".versions | keys | .[0]\" publish/config.yml)\n```\n\nThis will fetch the sources and create a locally cached version of the package. This version can also be published to a local remote for testing:\n\n```bash\nconan upload \u003cpackage\u003e/\u003cversion\u003e -r \u003cremote\u003e\n```\n\n### Toolchains\n\nScripts are defined in the `scripts` folder and can be invoked with `toolchains`:\n\nTo generate header guards:\n\n```bash\nnpx toolchains guards\n```\n\n## Contributing\n\nPlease read this repository's [Code of Conduct](CODE_OF_CONDUCT.md) which outlines our collaboration standards and the [Changelog](CHANGELOG.md) for details on breaking changes that have been made.\n\nThis repository adheres to semantic versioning standards. For more information on semantic versioning visit [SemVer](https://semver.org).\n\nBump2version is used to version and tag changes. For example:\n\n```bash\nbump2version patch\n```\n\n### Contributors\n\n- [Joel Lefkowitz](https://github.com/joellefkowitz) - Initial work\n\n## Remarks\n\nLots of love to the open source community!\n\n\u003cdiv align='center'\u003e\n    \u003cimg width=200 height=200 src='https://media.giphy.com/media/osAcIGTSyeovPq6Xph/giphy.gif' alt='Be kind to your mind' /\u003e\n    \u003cimg width=200 height=200 src='https://media.giphy.com/media/KEAAbQ5clGWJwuJuZB/giphy.gif' alt='Love each other' /\u003e\n    \u003cimg width=200 height=200 src='https://media.giphy.com/media/WRWykrFkxJA6JJuTvc/giphy.gif' alt=\"It's ok to have a bad day\" /\u003e\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoellefkowitz%2Ffunky","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjoellefkowitz%2Ffunky","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjoellefkowitz%2Ffunky/lists"}