├── .clang-format ├── .dockerignore ├── .gitignore ├── .travis.yml ├── CMakeLists.txt ├── Dockerfile ├── LICENSE ├── Makefile ├── README.md ├── conanfile.py ├── include └── kitten │ ├── applicative.h │ ├── detail │ ├── deriving │ │ └── from_monad │ │ │ ├── derive_applicative.h │ │ │ └── derive_functor.h │ └── ranges │ │ └── algorithm.h │ ├── functor.h │ ├── instances │ ├── function.h │ ├── optional.h │ ├── sequence_container.h │ └── variant.h │ ├── kitten.h │ ├── monad.h │ └── multifunctor.h ├── profiles └── common └── tests ├── CMakeLists.txt ├── function_test.cpp ├── main.cpp ├── optional_test.cpp ├── sequence_container_test.cpp ├── utils.h └── variant_test.cpp /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | BasedOnStyle: Microsoft 3 | BreakBeforeBraces: Attach 4 | UseTab: Never 5 | --- 6 | Language: Cpp 7 | SortIncludes: true 8 | SortUsingDeclarations: true 9 | FixNamespaceComments: false 10 | AlwaysBreakTemplateDeclarations: Yes -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | build 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Prerequisites 2 | *.d 3 | 4 | # Compiled Object files 5 | *.slo 6 | *.lo 7 | *.o 8 | *.obj 9 | 10 | # Precompiled Headers 11 | *.gch 12 | *.pch 13 | 14 | # Compiled Dynamic libraries 15 | *.so 16 | *.dylib 17 | *.dll 18 | 19 | # Fortran module files 20 | *.mod 21 | *.smod 22 | 23 | # Compiled Static libraries 24 | *.lai 25 | *.la 26 | *.a 27 | *.lib 28 | 29 | # Executables 30 | *.exe 31 | *.out 32 | *.app 33 | 34 | .idea/ 35 | build/ 36 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: cpp 2 | 3 | services: 4 | - docker 5 | 6 | script: 7 | - make env-format-check 8 | - make env-test 9 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.10) 2 | 3 | project(kitten LANGUAGES CXX) 4 | 5 | # Definition 6 | 7 | option(BUILD_TESTS "Build test executable" ON) 8 | 9 | add_library(${PROJECT_NAME} INTERFACE) 10 | 11 | add_library(rvarago::${PROJECT_NAME} ALIAS ${PROJECT_NAME}) 12 | 13 | target_include_directories(${PROJECT_NAME} 14 | INTERFACE 15 | $ 16 | $ 17 | ) 18 | 19 | target_compile_features(${PROJECT_NAME} 20 | INTERFACE 21 | cxx_std_17 22 | ) 23 | 24 | # Installation 25 | 26 | include(GNUInstallDirs) 27 | 28 | # Generate the config file with the target 29 | install(TARGETS ${PROJECT_NAME} 30 | EXPORT ${PROJECT_NAME}Config 31 | ) 32 | 33 | install(DIRECTORY include/ 34 | DESTINATION ${CMAKE_INSTALL_INCLUDEDIR} 35 | ) 36 | 37 | # Make the target be importable from the installation directory 38 | install(EXPORT ${PROJECT_NAME}Config 39 | DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} 40 | NAMESPACE rvarago:: 41 | ) 42 | 43 | # Make the target be importable from the build directory via package registry 44 | export(EXPORT ${PROJECT_NAME}Config 45 | NAMESPACE rvarago:: 46 | ) 47 | 48 | export(PACKAGE ${PROJECT_NAME}) 49 | 50 | # Tests 51 | if(BUILD_TESTS) 52 | include(CTest) 53 | add_subdirectory(tests) 54 | endif() 55 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM conanio/gcc9:1.22.2 2 | 3 | RUN pip install clang-format 4 | 5 | USER root 6 | 7 | WORKDIR kitten 8 | 9 | COPY . . 10 | 11 | CMD ["/bin/bash"] 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Rafael Varago 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | PROJECT_NAME = kitten 2 | PROFILE = ../profiles/common 3 | BUILD_DIR = build 4 | BUILD_TESTS = ON 5 | BUILD_TYPE = Debug 6 | 7 | .PHONY: all test install compile gen dep mk clean env env-test env-format-check format-check format 8 | 9 | all: compile 10 | 11 | env: 12 | docker build -t $(PROJECT_NAME) . 13 | 14 | env-format-check: env 15 | docker run --rm $(PROJECT_NAME) make format-check --no-print-directory 16 | 17 | env-test: env 18 | docker run --rm $(PROJECT_NAME) make compile test --no-print-directory 19 | 20 | install: 21 | cd $(BUILD_DIR) && cmake --build . --target install 22 | 23 | test: 24 | cd $(BUILD_DIR) && ctest -VV . 25 | 26 | compile: gen 27 | cd $(BUILD_DIR) && cmake --build . 28 | 29 | gen: dep 30 | cd $(BUILD_DIR) && cmake -D BUILD_TESTS=$(BUILD_TESTS) -DCMAKE_BUILD_TYPE=$(BUILD_TYPE) .. 31 | 32 | dep: mk 33 | cd $(BUILD_DIR) && conan install .. --build=missing -pr $(PROFILE) -s build_type=$(BUILD_TYPE) 34 | 35 | mk: 36 | mkdir -p $(BUILD_DIR) 37 | 38 | clean: 39 | rm -rf $(BUILD_DIR) 40 | 41 | format-check: 42 | @if git ls-files -- '*.cpp' '*.h' | xargs clang-format -style=file -output-replacements-xml | grep -q " Category -> Cat -> Kitten 🐈 8 | 9 | [![Build Status](https://travis-ci.org/rvarago/absent.svg?branch=master)](https://travis-ci.org/rvarago/kitten) 10 | 11 | ## Problem description 12 | 13 | Functors, 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. 14 | 15 | Furthermore, they are structures that must respect mathematical laws that restrict their behavior. [learnyouahaskell](http://learnyouahaskell.com/functors-applicative-functors-and-monoids) 16 | provides a very good explanation about these laws in terms of programming, but of course, there are many other 17 | resources to learn about them. 18 | 19 | Although they're fairly abstract concepts, there are many practical applications for them when we want to compose 20 | _effectful functions_. We can think about an effectful function as a function `f` that instead of returning a value of type 21 | `A` it returns a value of type `X` where `X` represents an effect which models an "extra information" about the computation 22 | done inside `f`. 23 | 24 | For example: 25 | 26 | - A function `f` that can fail to produce a value of type `A` might return an `std::optional` 27 | where `std::optional` is a piece of extra information (an effect) that models the absence of a value. 28 | - A function `f` that can return multiple values of type `A` might return an `std::vector` where 29 | `std::vector` is a piece of "extra information" (an effect) that models multiple values. 30 | 31 | When we have functions `f: A -> B` and `g: B -> C` we can compose these two functions into a new function 32 | `h: A -> C` by using the usual function composition: 33 | 34 | ` h(x) = g(f(x)), A -> C` 35 | 36 | Therefore, it eliminates the intermediate steps involving `B`. 37 | 38 | That's only possible because of the co-domain `A` of `f` matches with the domain `B` of `g`, i.e. 39 | the output of the first function can be fed as the input for the second function. But, what happens when we have effectful 40 | functions involved? 41 | 42 | _kitten_ has the goal of providing instances for some common abstractions from Category Theory to C++. Internally, 43 | it makes heavy-usage of the STL to extend the capabilities of supported C++ types. 44 | 45 | ### Functors 46 | 47 | Now, consider `f` as an effectful function: `f: A -> X`, where `X` models some effect. We can't compose 48 | `g: B -> C` anymore, since it expects `B` and therefore can't be fed with an `X`. 49 | 50 | Given that we can't do the usual function composition, we need a more powerful way to do composition. 51 | 52 | We need a functor. 53 | 54 | If `X` admits a functor for some type parameter `T`, we can compose `f` and `g` by using `fmap`: 55 | 56 | `fmap(X, w: A -> B): X` 57 | 58 | `fmap` receives a functor `X`, a function `w: A -> B` that would do the composition of the types `A` and `B`, and 59 | it returns a new functor `X`. It basically: 60 | 61 | 1. unwraps `X` into `A` 62 | 2. feeds `A` into `w` 63 | 3. wraps (or lifts) the output of `w` into an `X` 64 | 4. then returns `X` 65 | 66 | Hence, we can do: 67 | 68 | `fmap(f(), g)` 69 | 70 | Using _kitten_, one example of using a functor is: 71 | 72 | ``` 73 | auto const maybe_name = maybe_find_person() | get_name; // or fmap(maybe_find_person(), get_name) 74 | ``` 75 | 76 | Where `maybe_find_person` returns an `std::optional`, and then the wrapped object of type `person` is fed into 77 | `get_name` that returns an object of type `name`. 78 | 79 | Thus, the result of the whole composition is of type `std::optional`. 80 | 81 | An equivalent way to define a functor is by providing the function `liftF`, 82 | which maps a function `w: A -> B` into another function `z: X[A] -> X[B]`, 83 | where `X[T]` is a functor. 84 | 85 | ### Applicatives 86 | 87 | What happens if `f` takes several arguments? For instance, we have the objects `xa: X` and `xb: X` and the 88 | function `f: (A, B) -> C`. 89 | How can we combine two effects `xa` and `xb` via `f` to obtain `X`? 90 | 91 | If we use `fmap` as we did before, we wouldn't be able, because `fmap` accepts only one argument, and we need to somehow 92 | provide two. 93 | 94 | We need a structure that's more powerful than a functor, we need an applicative. 95 | 96 | If `X` admits an applicative for some type parameter `T`, we can compose `f` using `combine` (also called `liftA2`): 97 | 98 | `combine(X, X, w: (A, B) -> C): X` 99 | 100 | `combine` receives the applicatives `X` and `X`, a binary function `w: (A, B) -> C` that would do the composition 101 | of the types `A` and `B`, and it returns a new applicative `X`. It basically: 102 | 103 | 1. unwraps `X` into `A` 104 | 2. unwraps `X` into `B` 105 | 3. feeds `A` and `B` into `w` 106 | 3. wraps the result `C` into `X` 107 | 4. then returns `X` 108 | 109 | Hence, we can do: 110 | 111 | `combine(xa, xb, f)` 112 | 113 | In addition, an applicative also has a function `pure(A): X` that allows one to lift a value 114 | of type `A` into an applicative `X`. 115 | 116 | Using _kitten_, one example of using an applicative is: 117 | 118 | ``` 119 | auto const maybe_three = maybe_one() + maybe_two(); // or combine(maybe_one(), maybe_two(), std::plus{}) 120 | ``` 121 | 122 | Where `maybe_one` and `maybe_two` return instances of `std::optional`, and then unwrapped objects of types 123 | `int` and `int` are fed into operator `+` (that defaults to `std::plus{}` for the unwrapped types) that returns an object 124 | of type `int` which is finally wrapped again in an `std::optional`. 125 | 126 | Thus, the result of the whole composition is of type `std::optional`. 127 | 128 | And what happens if we have n-ary rather than binary function `f`? 129 | 130 | We can simply chaining operator `+`, like: 131 | 132 | ``` 133 | auto const maybe_ten = maybe_one() + maybe_two() + maybe_three() + maybe_four(); // and so on ... 134 | ``` 135 | 136 | And what should we do if we need a different operation instead of addition? 137 | 138 | Given that operator `+` accepts two parameters, we have to use a convenient and general overload that accepts a tuple of two applicatives: 139 | 140 | ``` 141 | auto const maybe_six_as_string = std::tuple{maybe_two, maybe_three} + [](auto const& first, auto const& second) { 142 | return std::to_string(first * second); 143 | }; 144 | ``` 145 | 146 | Another helpful function provided by applicatives is `liftA2`, which generalizes `liftF` for binaries functions of kind 147 | `w: A -> B -> C`, lifting it into another function `z: X[A] -> X[B] -> X[C]`, where `X[T]` is some applicative. 148 | 149 | ### Monads 150 | 151 | What happens if `f` and `g` are both effectul functions: `f: A -> X` and `g: B -> X`. How can 152 | we compose `f` and `g`? 153 | 154 | If we use `fmap` as we did before, we would end up with a return type `X>`, that nests the same effect. This type 155 | would then need to be flattened, or collapsed, into an `X`, we need a structure that knows how to flat the effects. 156 | 157 | We need a structure that's more powerful than a functor, we need a monad. 158 | 159 | If `X` admits a monad for some type parameter `T`, we can compose `f` and `g` by using `bind`: 160 | 161 | `bind(X, w: A -> X): X` 162 | 163 | `bind` receives a monad `X`, a function `w: A -> X` that would do the composition of the types `A` and 164 | `X`, and it returns a new monad `X`. It basically: 165 | 166 | 1. unwraps `X` into `A` 167 | 2. feeds `A` into `w` 168 | 3. flats `X>` into `X` 169 | 4. then returns `X` 170 | 171 | Hence, we can do: 172 | 173 | `bind(f(), g)` 174 | 175 | In addition, a monad also has a function `wrap(A): X` that allows one to lift a value 176 | of type `A` into a monad `X`. 177 | 178 | Using _kitten_, one example of using a monad is: 179 | 180 | ``` 181 | auto const maybe_name = maybe_find_person() >> maybe_get_name; // or bind(maybe_find_person(), maybe_get_name) 182 | ``` 183 | 184 | Where `maybe_find_person` returns an `std::optional`, and then the wrapped object of type `person` is fed into 185 | `maybe_get_name` that itself returns an object of type `std::optional`. 186 | 187 | Thus, the result of the whole composition is of type `std::optional`. 188 | 189 | ## Multi-functors 190 | 191 | A multi-functor generalizes a functor in the sense that instead of having only 1 type parameter, it can have `N` different types. 192 | 193 | Given a multi-functor of arity 2, also called bi-functor,`X`, and the functions `fa: A1 -> A2` and `fb: B1-> B2`, 194 | a multi-functor uses `multimap` to instantiate a new bi-functor `X` via mapping the types through `fa` and `fb`. 195 | 196 | An interesting use case for a multi-functor is where we have a function that returns an `std::variant` and we 197 | want to map such type to `std::variant` via several functions `fa: A1 -> A2`, `fb: B1 -> B2`, and `fc: C1 -> C2` 198 | using _kitten_, we can do: 199 | 200 | ``` 201 | auto const variant_A2_B2_C2 = variant_A1_B1_C1 || syntax::overloaded { 202 | [](A1 value) { return A1_to_A2(value); }, 203 | [](B1 value) { return B1_to_B2(value); }, 204 | [](C1 value) { return C1_to_C2(value); } 205 | }; 206 | ``` 207 | 208 | Where `syntax::overloaded` is a helper function that enables us to create a function object on-the-fly, that receives a 209 | set of lambda expressions, and the right overload is then selected at compile-time depending on the type held by the 210 | `std::variant`. 211 | 212 | ## kitten 213 | 214 | _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 215 | such instances, it's then possible to use the combinators available as free functions. 216 | 217 | To simplify the notation, many combinators also come with overloaded operators that enable a, hopefully, nicer infix syntax 218 | suitable for chaining several calls. 219 | 220 | The combinators are available conveniently in the header: `kitten/kitten.h`, or by importing each one separately. And 221 | the main namespace is `rvarago::kitten`. 222 | 223 | Note 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. 224 | 225 | ### Functor 226 | 227 | | Combinator | Infix | 228 | |:-----------------:|:--------:| 229 | | `fmap` | | | 230 | | `liftF` | | 231 | 232 | ### Multi-functor 233 | 234 | | Combinator | Infix | 235 | |:-----------------:|:-------------:| 236 | | `multimap` | || | 237 | 238 | ### Applicative 239 | 240 | | Combinator | Infix | 241 | |:-----------------:|:-------------:| 242 | | `pure` | | 243 | | `combine` | + | 244 | | `liftA2` | | 245 | 246 | 247 | ### Monad 248 | 249 | | Combinator | Infix | 250 | |:-----------------:|:-------------:| 251 | | `wrap` | | 252 | | `bind` | >> | 253 | 254 | ### Adapters 255 | 256 | The following types are currently supported: 257 | 258 | | Type | Functor | Applicative | Monad | Multi-functor | 259 | |:---------------------------------:|:-------:|-------------|---------|:-------------:| 260 | | `types::function_wrapper` | x | | | | 261 | | `std::optional` | x | x | x | | 262 | | `std::deque` | x | x | x | | 263 | | `std::list` | x | x | x | | 264 | | `std::variant` | | | | x | 265 | | `std::vector` | x | x | | | 266 | 267 | - `types::function_wrapper` is a callable wrapper around a function-like type, e.g. function, function object, etc. 268 | And it allows using `fmap` to compose functions, e.g. given `fx : A -> B` and 269 | `fy: B -> C`, and both wrapped around `types::function_wrapper` which can conveniently be done 270 | by the helper function `types::fn`, then `fmap(fx, fy)` returns a new `types::function_wrapper` 271 | `fz: A -> C` that applies `fx` and then `fy`. So, by providing an argument 272 | `x` of type `A`, we have: `fmap(fx, fy)(x) == fy(fx(x))`. 273 | 274 | ## Requirements 275 | 276 | ### Mandatory 277 | 278 | * C++17 279 | 280 | ### Optional 281 | 282 | * CMake (_only if you need to build from sources_) 283 | * Make (_only if you want to use it to orchestrate task execution_) 284 | * Conan (_only if you want generate a package or build the tests using conan as a provider for the test framework_) 285 | * Docker (_only if you want build from inside a docker container_) 286 | 287 | ## Build 288 | 289 | The _Makefile_ wraps the commands to download dependencies (Conan), generate the build configuration, build, run the 290 | unit tests, and clear the build folder. Please consult the Makefile to adapt 291 | the commands in case you want to build _absent_ directly without using make. 292 | 293 | * Compile: 294 | 295 | `` 296 | make 297 | `` 298 | 299 | By default, it also builds the unit tests, you can disable the behavior by: 300 | 301 | `` 302 | make BUILD_TESTS=OFF 303 | `` 304 | 305 | 306 | The build always assumes that the default profile (*profiles/common*) applies to your build. If that's not, then you 307 | can specify your profile by setting _PROFILE_ as: 308 | 309 | `` 310 | make PROFILE= 311 | `` 312 | 313 | And to build with Release mode (by default it builds with Debug mode enabled): 314 | 315 | `` 316 | make BUILD_TYPE=Release 317 | `` 318 | 319 | * To run the unit tests, if previously compiled: 320 | 321 | `` 322 | make test 323 | `` 324 | 325 | ### Run unit tests inside a Docker container 326 | 327 | Optionally, it's also possible to run the unit tests inside a Docker container by executing: 328 | 329 | `` 330 | make env-test 331 | `` 332 | 333 | ## Installing on the system 334 | 335 | To install _kitten_: 336 | 337 | `` 338 | make install 339 | `` 340 | 341 | Then, it's possible to import _kitten_ into external CMake projects, say in a target _myExample_, by simply adding the 342 | following commands to its _CMakeLists.txt_: 343 | 344 | ``` 345 | find_package(kitten) 346 | target_link_libraries(myExample rvarago::kitten) 347 | ``` 348 | 349 | ## Package managers 350 | 351 | To simplify the integration, _kitten_ can also be provided by the following package managers: 352 | 353 | 1. [Conan](https://bintray.com/conan/conan-center/kitten%3A_) -------------------------------------------------------------------------------- /conanfile.py: -------------------------------------------------------------------------------- 1 | from conans import ConanFile 2 | 3 | class KittenConan(ConanFile): 4 | 5 | build_requires = "catch2/2.11.3" 6 | generators = "cmake_find_package" 7 | -------------------------------------------------------------------------------- /include/kitten/applicative.h: -------------------------------------------------------------------------------- 1 | #ifndef RVARAGO_KITTEN_APPLICATIVE_H 2 | #define RVARAGO_KITTEN_APPLICATIVE_H 3 | 4 | #include 5 | #include 6 | 7 | namespace rvarago::kitten { 8 | 9 | /** 10 | * An applicative is an abstraction that allows the combination of two wrapped values. 11 | * 12 | * Given two applicatives apa: AP[A] and apb: AP[B] and a binary function f: (A, B) -> C 13 | * It uses f to map over the values unwrapped from apa and apb and then returns a new applicative apc: AP[C]. 14 | * 15 | */ 16 | template