Skip to content

PraisePancakes/endo

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

66 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

logo

Endo : C++ Modern Functional Library

Embrace Functional Programming in C++

Table of contents

Introduction

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.

Algebraic

Below are some short examples showing nice things you can do with algbraic types that Endo provides.

Maybe

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;
};

output :

hello : 5
false

Either

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;
  
};

output :

doesnt have it : uh oh error
get it : 0

Algorithms

Below are some short examples showing nice things you can do with Endo algorithms.

ndo::spread

#include <iostream>
#include "ndo.hpp"

int main() {
   auto ns = ndo::spread(5, 10);
    for (auto e : ns) {
        std::cout << e << std::endl;
    }
};

output :

    5, 6, 7, 8, 9, 10

ndo::map_from

#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;
}
    

output :

2, 3, 4, 5

ndo::zip

#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;
}

output :

ZIPPED
4 hi a false

UNZIPPED
4 hi a false

FLAT ZIPPED
4 hi a false

Functional

Endo supports functional composition and partial application via binding.

Currying

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;
    };

Composition

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;
}

Y-Combinator

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.

Type Set

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;
};

Requirements

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 )

Build

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 .
  ./NDO

Feel free to examine the tests in the main file.

Documentation

This project is documented under doxygen, please run doxygen to see well formatted documents with many more features.

Disclaimer

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.

License

Distributed under the MIT License (See accompanying file LICENSE)

About

C++ Modern Functional Library

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors