{"id":25195031,"url":"https://github.com/rvarago/kitten","last_synced_at":"2025-05-08T02:38:11.171Z","repository":{"id":110962451,"uuid":"192217327","full_name":"rvarago/kitten","owner":"rvarago","description":"A small C++17 library inspired by Category Theory.","archived":false,"fork":false,"pushed_at":"2023-01-04T17:21:45.000Z","size":134,"stargazers_count":16,"open_issues_count":3,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-03-31T16:34:30.290Z","etag":null,"topics":["category-theory","cpp","declarative-programming","functional-programming","functors","modern-cpp","monadic-interface","monads"],"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/rvarago.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2019-06-16T17:14:36.000Z","updated_at":"2024-11-25T09:09:49.000Z","dependencies_parsed_at":null,"dependency_job_id":"0dfd5632-c58f-461b-9092-99978c0e68ae","html_url":"https://github.com/rvarago/kitten","commit_stats":null,"previous_names":[],"tags_count":2,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rvarago%2Fkitten","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rvarago%2Fkitten/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rvarago%2Fkitten/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/rvarago%2Fkitten/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/rvarago","download_url":"https://codeload.github.com/rvarago/kitten/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252988063,"owners_count":21836450,"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":["category-theory","cpp","declarative-programming","functional-programming","functors","modern-cpp","monadic-interface","monads"],"created_at":"2025-02-10T00:40:55.992Z","updated_at":"2025-05-08T02:38:11.152Z","avatar_url":"https://github.com/rvarago.png","language":"C++","readme":"# kitten\n\nThis is a small header-only library with some machinery meant to extend the already great STL with some concepts obtained from Category Theory, such as functors and monads, in order to make the composition of some C++ type constructors even simpler.\n\nIt's basically a collection of utilities from my recent studies of Category Theory expressed in C++.\n\nCategory Theory -\u003e Category -\u003e Cat -\u003e Kitten 🐈\n\n[![Build Status](https://travis-ci.org/rvarago/absent.svg?branch=master)](https://travis-ci.org/rvarago/kitten)\n\n## Problem description\n\nFunctors, applicatives, monads, etc are mathematical structures studied in Category Theory that capture the essence of composition. And given that programming is all about composition, they have plenty of practical applications, particularly in Pure Functional Programming.\n\nFurthermore, they are structures that must respect mathematical laws that restrict their behavior. [learnyouahaskell](http://learnyouahaskell.com/functors-applicative-functors-and-monoids)\nprovides a very good explanation about these laws in terms of programming, but of course, there are many other\nresources to learn about them.\n\nAlthough they're fairly abstract concepts, there are many practical applications for them when we want to compose\n_effectful functions_. We can think about an effectful function as a function `f` that instead of returning a value of type\n`A` it returns a value of type `X\u003cA\u003e` where `X` represents an effect which models an \"extra information\" about the computation\ndone inside `f`.\n\nFor example:\n\n- A function `f` that can fail to produce a value of type `A` might return an `std::optional\u003cA\u003e`\nwhere `std::optional` is a piece of extra information (an effect) that models the absence of a value.\n- A function `f` that can return multiple values of type `A` might return an `std::vector\u003cA\u003e` where\n`std::vector` is a piece of \"extra information\" (an effect) that models multiple values.\n\nWhen we have functions `f: A -\u003e B` and `g: B -\u003e C` we can compose these two functions into a new function\n`h: A -\u003e C` by using the usual function composition:\n\n` h(x) = g(f(x)), A -\u003e C`\n\nTherefore, it eliminates the intermediate steps involving `B`.\n\nThat's only possible because of the co-domain `A` of `f` matches with the domain `B` of `g`, i.e.\nthe output of the first function can be fed as the input for the second function. But, what happens when we have effectful\nfunctions involved?\n\n_kitten_ has the goal of providing instances for some common abstractions from Category Theory to C++. Internally,\n it makes heavy-usage of the STL to extend the capabilities of supported C++ types.\n\n### Functors\n\nNow, consider `f` as an effectful function: `f: A -\u003e X\u003cB\u003e`, where `X` models some effect. We can't compose\n`g: B -\u003e C` anymore, since it expects `B` and therefore can't be fed with an `X\u003cB\u003e`.\n\nGiven that we can't do the usual function composition, we need a more powerful way to do composition.\n\nWe need a functor.\n\nIf `X\u003cT\u003e` admits a functor for some type parameter `T`, we can compose `f` and `g` by using `fmap`:\n\n`fmap(X\u003cA\u003e, w: A -\u003e B): X\u003cB\u003e`\n\n`fmap` receives a functor `X\u003cA\u003e`, a function `w: A -\u003e B` that would do the composition of the types `A` and `B`, and\nit returns a new functor `X\u003cB\u003e`. It basically:\n \n1. unwraps `X\u003cA\u003e` into `A`\n2. feeds `A` into `w`\n3. wraps (or lifts) the output of `w` into an `X\u003cB\u003e`\n4. then returns `X\u003cB\u003e`\n\nHence, we can do:\n\n`fmap(f(), g)`\n\nUsing _kitten_, one example of using a functor is:\n\n```\nauto const maybe_name = maybe_find_person() | get_name; // or fmap(maybe_find_person(), get_name)\n```\n\nWhere `maybe_find_person` returns an `std::optional\u003cperson\u003e`, and then the wrapped object of type `person` is fed into\n`get_name` that returns an object of type `name`.\n\nThus, the result of the whole composition is of type `std::optional\u003cname\u003e`.\n\nAn equivalent way to define a functor is by providing the function `liftF`,\nwhich maps a function `w: A -\u003e B` into another function `z: X[A] -\u003e X[B]`,\nwhere `X[T]` is a functor.\n\n### Applicatives\n\nWhat happens if `f` takes several arguments? For instance, we have the objects `xa: X\u003cA\u003e` and `xb: X\u003cB\u003e` and the\nfunction `f: (A, B) -\u003e C`.\nHow can we combine two effects `xa` and `xb` via `f` to obtain `X\u003cC\u003e`?\n\nIf we use `fmap` as we did before, we wouldn't be able, because `fmap` accepts only one argument, and we need to somehow\nprovide two.\n\nWe need a structure that's more powerful than a functor, we need an applicative.\n\nIf `X\u003cT\u003e` admits an applicative for some type parameter `T`, we can compose `f` using `combine` (also called `liftA2`):\n\n`combine(X\u003cA\u003e, X\u003cB\u003e, w: (A, B) -\u003e C): X\u003cC\u003e`\n\n`combine` receives the applicatives `X\u003cA\u003e` and `X\u003cB\u003e`, a binary function `w: (A, B) -\u003e C` that would do the composition\nof the types `A` and `B`, and it returns a new applicative `X\u003cB\u003e`. It basically:\n\n1. unwraps `X\u003cA\u003e` into `A`\n2. unwraps `X\u003cB\u003e` into `B`\n3. feeds `A` and `B` into `w`\n3. wraps the result `C` into `X\u003cC\u003e`\n4. then returns `X\u003cC\u003e`\n\nHence, we can do:\n\n`combine(xa, xb, f)`\n\nIn addition, an applicative also has a function `pure(A): X\u003cA\u003e` that allows one to lift a value\nof type `A` into an applicative `X\u003cA\u003e`.\n\nUsing _kitten_, one example of using an applicative is:\n\n```\nauto const maybe_three = maybe_one() + maybe_two(); // or combine(maybe_one(), maybe_two(), std::plus{})\n```\n\nWhere `maybe_one` and `maybe_two` return instances of `std::optional\u003cint\u003e`, and then unwrapped objects of types\n`int` and `int` are fed into operator `+` (that defaults to `std::plus{}` for the unwrapped types) that returns an object\nof type `int` which is finally wrapped again in an `std::optional\u003cint\u003e`.\n\nThus, the result of the whole composition is of type `std::optional\u003cint\u003e`.\n\nAnd what happens if we have n-ary rather than binary function `f`?\n\nWe can simply chaining operator `+`, like:\n\n```\nauto const maybe_ten = maybe_one() + maybe_two() + maybe_three() + maybe_four(); // and so on ...\n```\n\nAnd what should we do if we need a different operation instead of addition?\n\nGiven that operator `+` accepts two parameters, we have to use a convenient and general overload that accepts a tuple of two applicatives:\n\n```\nauto const maybe_six_as_string = std::tuple{maybe_two, maybe_three} + [](auto const\u0026 first, auto const\u0026 second) {\n            return std::to_string(first * second);\n};\n```\n\nAnother helpful function provided by applicatives is  `liftA2`, which generalizes `liftF` for binaries functions of kind\n`w: A -\u003e B -\u003e C`, lifting it into another function `z: X[A] -\u003e X[B] -\u003e X[C]`, where `X[T]` is some applicative.\n\n### Monads\n\nWhat happens if `f` and `g` are both effectul functions: `f: A -\u003e X\u003cB\u003e` and `g: B -\u003e X\u003cC\u003e`. How can\nwe compose `f` and `g`?\n\nIf we use `fmap` as we did before, we would end up with a return type `X\u003cX\u003cC\u003e\u003e`, that nests the same effect. This type\nwould then need to be flattened, or collapsed, into an `X\u003cC\u003e`, we need a structure that knows how to flat the effects.\n\nWe need a structure that's more powerful than a functor, we need a monad.\n\nIf `X\u003cT\u003e` admits a monad for some type parameter `T`, we can compose `f` and `g` by using `bind`:\n\n`bind(X\u003cA\u003e, w: A -\u003e X\u003cB\u003e): X\u003cB\u003e`\n\n`bind` receives a monad `X\u003cA\u003e`, a function `w: A -\u003e X\u003cB\u003e` that would do the composition of the types `A` and\n`X\u003cB\u003e`, and it returns a new monad `X\u003cB\u003e`. It basically:\n \n1. unwraps `X\u003cA\u003e` into `A`\n2. feeds `A` into `w`\n3. flats `X\u003cX\u003cB\u003e\u003e` into `X\u003cB\u003e`\n4. then returns `X\u003cB\u003e`\n\nHence, we can do:\n\n`bind(f(), g)`\n\nIn addition, a monad also has a function `wrap(A): X\u003cA\u003e` that allows one to lift a value\nof type `A` into a monad `X\u003cA\u003e`.\n\nUsing _kitten_, one example of using a monad is:\n\n```\nauto const maybe_name = maybe_find_person() \u003e\u003e maybe_get_name; // or bind(maybe_find_person(), maybe_get_name)\n```\n\nWhere `maybe_find_person` returns an `std::optional\u003cperson\u003e`, and then the wrapped object of type `person` is fed into\n`maybe_get_name` that itself returns an object of type `std::optional\u003cname\u003e`.\n\nThus, the result of the whole composition is of type `std::optional\u003cname\u003e`.\n\n## Multi-functors\n\nA multi-functor generalizes a functor in the sense that instead of having only 1 type parameter, it can have `N` different types.\n\nGiven a multi-functor of arity 2, also called bi-functor,`X\u003cA1, B1\u003e`, and the functions `fa: A1 -\u003e A2` and `fb: B1-\u003e B2`,\na multi-functor uses `multimap` to instantiate a new bi-functor `X\u003cA2, B2\u003e` via mapping the types through  `fa` and `fb`.\n\nAn interesting use case for a multi-functor is where we have a function that returns an `std::variant\u003cA1, B1, C1\u003e` and we\nwant to map such type to `std::variant\u003cA2, B2, C2\u003e` via several functions `fa: A1 -\u003e A2`, `fb: B1 -\u003e B2`, and `fc: C1 -\u003e C2`\nusing _kitten_, we can do:\n\n```\nauto const variant_A2_B2_C2 = variant_A1_B1_C1 || syntax::overloaded {\n    [](A1 value) { return A1_to_A2(value); },\n    [](B1 value) { return B1_to_B2(value); },\n    [](C1 value) { return C1_to_C2(value); }\n};\n```\n\nWhere `syntax::overloaded` is a helper function that enables us to create a function object on-the-fly, that receives a\nset of lambda expressions, and the right overload is then selected at compile-time depending on the type held by the\n`std::variant\u003cA1, B1, C1\u003e`.\n\n## kitten\n\n_kitten_ relies on the STL to provide functor, applicative, monad, and multi-functor instances for some C++ data types. Given that the data type admits\nsuch instances, it's then possible to use the combinators available as free functions.\n\nTo simplify the notation, many combinators also come with overloaded operators that enable a, hopefully, nicer infix syntax\nsuitable for chaining several calls.\n\nThe combinators are available conveniently in the header: `kitten/kitten.h`, or by importing each one separately. And\nthe main namespace is `rvarago::kitten`.\n\nNote that it's possible that a type may not admit instances for all the structures, e.g a type may have a functor but not a monad.\n\n### Functor\n\n|    Combinator     |   Infix  |\n|:-----------------:|:--------:|\n|      `fmap`       | \u0026#x7c;   |\n|      `liftF`      |          |\n\n### Multi-functor\n\n|    Combinator     |      Infix    |\n|:-----------------:|:-------------:|\n|      `multimap`   |  \u0026#x7c;\u0026#x7c; |\n\n### Applicative\n\n|    Combinator     |      Infix    |\n|:-----------------:|:-------------:|\n|      `pure`       |               |\n|      `combine`    |        +      |\n|      `liftA2`     |               |\n\n\n### Monad\n\n|    Combinator     |      Infix    |\n|:-----------------:|:-------------:|\n|      `wrap`       |               |\n|      `bind`       |        \u003e\u003e     |\n\n### Adapters\n\nThe following types are currently supported:\n\n|         Type                      | Functor | Applicative | Monad   | Multi-functor |\n|:---------------------------------:|:-------:|-------------|---------|:-------------:|\n| `types::function_wrapper\u003cF\u003e`      |    x    |             |         |               |\n| `std::optional\u003cT\u003e`                |    x    |     x       |   x     |               |\n| `std::deque\u003cT\u003e`                   |    x    |     x       |   x     |               |\n| `std::list\u003cT\u003e`                    |    x    |     x       |   x     |               |\n| `std::variant\u003cT...\u003e`              |         |             |         |       x       |\n| `std::vector\u003cT\u003e`                  |    x    |     x       |         |               |\n\n- `types::function_wrapper\u003cF\u003e` is a callable wrapper around a function-like type, e.g. function, function object, etc.\nAnd it allows using `fmap` to compose functions, e.g. given `fx : A -\u003e B` and\n`fy: B -\u003e C`, and both wrapped around `types::function_wrapper` which can conveniently be done\nby the helper function `types::fn`, then `fmap(fx, fy)` returns a new `types::function_wrapper`\n `fz: A -\u003e C` that applies `fx` and then `fy`. So, by providing an argument\n `x` of type `A`, we have: `fmap(fx, fy)(x) == fy(fx(x))`.\n\n## Requirements\n\n### Mandatory\n\n* C++17\n\n### Optional\n\n* CMake (_only if you need to build from sources_)\n* Make (_only if you want to use it to orchestrate task execution_)\n* Conan (_only if you want generate a package or build the tests using conan as a provider for the test framework_)\n* Docker (_only if you want build from inside a docker container_)\n\n## Build\n\nThe _Makefile_ wraps the commands to download dependencies (Conan), generate the build configuration, build, run the\nunit tests, and clear the build folder. Please consult the Makefile to adapt\nthe commands in case you want to build _absent_ directly without using make.\n\n* Compile:\n\n``\nmake\n``\n\nBy default, it also builds the unit tests, you can disable the behavior by:\n\n``\nmake BUILD_TESTS=OFF\n``\n\n\nThe build always assumes that the default profile (*profiles/common*) applies to your build. If that's not, then you\ncan specify your profile by setting _PROFILE_ as:\n\n``\nmake PROFILE=\u003cpath_to_your_profile\u003e\n``\n\nAnd to build with Release mode (by default it builds with Debug mode enabled):\n\n``\nmake BUILD_TYPE=Release\n``\n\n* To run the unit tests, if previously compiled:\n\n``\nmake test\n``\n\n### Run unit tests inside a Docker container\n\nOptionally, it's also possible to run the unit tests inside a Docker container by executing:\n\n``\nmake env-test\n``\n\n## Installing on the system\n\nTo install _kitten_:\n\n``\nmake install\n``\n\nThen, it's possible to import _kitten_ into external CMake projects, say in a target _myExample_, by simply adding the\nfollowing commands to its _CMakeLists.txt_:\n\n```\nfind_package(kitten)\ntarget_link_libraries(myExample rvarago::kitten)\n```\n\n## Package managers\n\nTo simplify the integration, _kitten_ can also be provided by the following package managers:\n\n1. [Conan](https://bintray.com/conan/conan-center/kitten%3A_)","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frvarago%2Fkitten","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frvarago%2Fkitten","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frvarago%2Fkitten/lists"}