Embrace Functional Programming in C++
Endo is a lightweight, header-only C++ library designed to help you write more expressive and intention-revealing metaprograms. It reduces noise and complexity by letting you operate at a higher level of abstraction—composing behaviors, manipulating types, and enforcing constraints—without the clutter of hand-written SFINAE or clumsy type unpacking.
Below are some short examples showing nice things you can do with algbraic types that Endo provides.
Maybe holds a the value of the given (or deduced) type if no value is passed then it contains a Nothing value.
#pragma once
#include <iostream>
#include "ndo.hpp"
int main() {
ndo::maybe<int> may5{10};
auto lam = [](int x) { return "hello"; };
std::string ctad = may5.map_or_throw(lam);
std::size_t strict = may5.map_or_throw<std::string>(lam).size();
std::cout << ctad << " : " << strict << std::endl;
ndo::maybe<std::vector<int>> may7{{56, 1, 2, 3}};
auto lam3 = [](std::vector<int> v) {
return 4;
};
auto lam4 = [](int v) {
return ndo::ndo_nothing;
};
auto lam5 = [](ndo::ndo_null_t n) {
return 3;
};
auto v = may7.and_then(lam3).and_then(lam4).and_then(lam5); //notice how failure short circuits, lam5 doesnt matter because lam4 failed.
std::cout << std::boolalpha << v.has_value() << std::endl;
};
hello : 5
false
Either consists of two types, an error type and a success type, the value of either is determined by the last assignment given to it.
#pragma once
#include <iostream>
#include "ndo.hpp"
int main() {
ndo::either<std::string, int> e;
e = 4;
e = "uh oh error";
if (e.has_value()) {
std::cout << "has it : " << e.just_or_throw() << std::endl;
} else {
std::cout << "doesnt have it : " << e.error_or_default() << std::endl;
};
auto v = e.and_then([](int x) { return "hi"; }).and_then([](const char* v) { return 10; });
std::cout << "get it : " << v.just_or(0) << std::endl;
};
doesnt have it : uh oh error
get it : 0
Below are some short examples showing nice things you can do with Endo algorithms.
#include <iostream>
#include "ndo.hpp"
int main() {
auto ns = ndo::spread(5, 10);
for (auto e : ns) {
std::cout << e << std::endl;
}
};
5, 6, 7, 8, 9, 10
#include <iostream>
#include "ndo.hpp"
int main() {
std::vector<int> vec{0, 1, 2, 3};
auto v = ndo::map_from(vec, [](int x) { return x + 2; });
std::for_each(v.begin(), v.end(), [](int v) { std::cout << v << " , "; });
return 0;
}
2, 3, 4, 5
#include <iostream>
#include "ndo.hpp"
int main() {
std::tuple<int, std::string> t1 = std::make_tuple(4, "hi");
std::tuple<char, bool> t2 = std::make_tuple('a', false);
auto zipped = ndo::zip(t1, t2);
std::cout << "ZIPPED\n";
std::cout << std::get<0>(zipped).first << " " << std::get<1>(zipped).first << " " << std::get<0>(zipped).second << " " << std::get<1>(zipped).second << std::endl;
auto unzipped = ndo::unzip(zipped);
std::cout << "UNZIPPED\n";
std::cout << std::get<0>(unzipped) << " " << std::get<1>(unzipped) << " " << std::get<2>(unzipped) << " " << std::get<3>(unzipped) << std::endl;
auto fzip = ndo::flat_zip(t1, t2);
std::cout << "FLAT ZIPPED\n";
std::cout << std::get<0>(fzip) << " " << std::get<1>(fzip) << " " << std::get<2>(fzip) << " " << std::get<3>(fzip) << std::endl;
return 0;
}
ZIPPED
4 hi a false
UNZIPPED
4 hi a false
FLAT ZIPPED
4 hi a false
Endo supports functional composition and partial application via binding.
Below is an example of how to achieve currying with Endo.
#include <iostream>
#include "ndo.hpp"
int sum(int x, int y, int z, int t) { return x + y + z + t; };
int foo(int x, int y) { return x + y; };
int main() {
auto v = ndo::curry(sum);
auto v1 = v(10);
auto v2 = v1(20);
auto v3 = v2(10)(5);
auto ucurr = ndo::uncurry(v1);
std::cout << "here" << ucurr(1, 2, 2) << std::endl;
auto vuc = ndo::curry(foo);
auto c = ndo::uncurry(vuc);
auto ans = c(1, 2);
std::cout << ans << std::endl;
return 0;
};Here is how function composition can be achieved with Endo.
#include <iostream>
#include "ndo.hpp"
int main() {
auto value = ndo::compositor::compose([](std::string v) { return (v[0] + v[1]); }, [](int v) { return "hi"; });
auto v1 = value(3);
std::cout << v1 << std::endl;
auto value2 = ndo::compositor::compose([](int x) { return x * 2; }, [](std::string new_s) { return 10; }, [](std::string og) { return "hello"; });
auto v2 = value2("hi");
std::cout << v2 << std::endl;
return 0;
}Endo also supports the Y-Combinator (though arguably outdated).
#include <iostream>
#include "ndo.hpp"
int main() {
auto fact = ndo::make_y_combinator([](auto self, int n) -> int {
return n <= 1 ? 1 : n * self(n - 1);
});
auto logger = [](auto f) {
return [xf = std::forward<decltype(f)>(f)](auto&&... args) mutable {
std::cout << xf(std::forward<decltype(args)>(args)...) << std::endl;
};
};
auto fact_log = logger(fact);
fact_log(5);
return 0;
}Meta
Endo has a range of metaprogramming utilites.
Endo has a built in modifiable type set.
#include <iostream>
#include "ndo.hpp"
int main() {
static_assert(!ndo::type_set<int, char, bool>::contains<float>);
static_assert(ndo::type_set<int, char, bool>::contains<int>);
static_assert(std::is_same_v<ndo::type_set<int, char, bool>::get<2>, bool>);
static_assert(std::is_same_v<ndo::type_set<int, char, bool>::get<1>, char>);
static_assert(!std::is_same_v<ndo::type_set<int, char, bool>::get<1>, int>);
static_assert(std::is_same_v<ndo::type_set<int, char, bool>::append<float>::type, ndo::type_set<int, char, bool, float>>);
static_assert(!std::is_same_v<ndo::type_set<int, char, bool>::append<float>::type, ndo::type_set<int, char, bool, int>>);
static_assert(std::is_same_v<ndo::type_set<int, char, bool>::prepend<float>::type, ndo::type_set<float, int, char, bool>>);
static_assert(!std::is_same_v<ndo::type_set<int, char, bool>::prepend<float>::type, ndo::type_set<bool, int, char, bool>>);
using from_tup = std::tuple<int, char, bool>;
using from_tup1 = std::tuple<char, bool, float>;
static_assert(!ndo::type_set<from_tup>::contains<float>);
constexpr auto idx2 = ndo::type_set<int, char>::index<int>;
static_assert(idx2 == 0);
using v = ndo::type_set<int, char, bool>::pop_back;
using reverse = ndo::type_set<int, char, float>::reverse;
using l = ndo::type_set<int, char, bool, float>::splicer::at<2>::left;
using r = ndo::type_set<int>::splicer::at<0>::right;
using from_empty = ndo::type_set<>::append<int>::type::prepend<bool, char>::type;
static_assert(ndo::type_set<int, char, std::string, bool>::contains_from<1, bool>);
static_assert(ndo::type_set<int, char, std::string, bool>::contains_from<0, int>);
static_assert(!ndo::type_set<int, char, std::string, bool>::contains_from<1, int>);
static_assert(!ndo::type_set<int, char, std::string, bool>::contains_from<3, int>);
static_assert(!ndo::type_set<int, char, int, bool>::is_unique);
static_assert(ndo::type_set<int, char, bool>::is_unique == true);
static_assert(ndo::type_set<int>::is_unique);
static_assert(std::is_same_v<ndo::type_set<int, char, char, int>::unique, ndo::type_set<int, char>>);
static_assert(std::is_same_v<ndo::type_set<void, int, void>::unique, ndo::type_set<void, int>>);
static_assert(!ndo::satisfies_it<[]<std::integral>() {}, std::string>);
static_assert(ndo::satisfies_it<[]<std::integral>() {}, int>);
using typer = ndo::type_set<int, char, std::string>::filter<[]<std::integral>() {}>;
static_assert(std::is_same_v<typer, ndo::type_set<int, char>>);
using tp = ndo::type_set<int, char, bool>::remove_at<0>;
return 0;
};A C++20-compatible compiler is needed. Compilers from these versions on are fine:
- GCC ( >= 9 )
- Clang ( >= 17 )
- MSVC ( >= 19.28 )
- XCode ( >= 13.1.6 )
This project is built using CMAKE version 3.14, with GCC 13.2.0 x86_64-w64-mingw32 using the ISO C++20 Standard.
To build, from the root folder, input these commands if not done already.
mkdir build
cd build
cmake -G "<Preferred-Generator>" ..
cmake --build .
./NDOFeel free to examine the tests in the main file.
This project is documented under doxygen, please run doxygen to see well formatted documents with many more features.
Proper testing and frameworks are not yet supported in version ( 0.0.1-alpha ) and will be implemented in the future. This library was rooted out of personal use and desires any contributions would always be welcome as well as discussion.
Distributed under the MIT License
(See accompanying file LICENSE)
