Skip to content

Instantly share code, notes, and snippets.

@insooth
Created January 30, 2018 12:17
Show Gist options
  • Save insooth/6019402654455787c022353c3edabc45 to your computer and use it in GitHub Desktop.
Save insooth/6019402654455787c022353c3edabc45 to your computer and use it in GitHub Desktop.
Maybe Monad and Currying
#include <iostream>
#include <algorithm>
#include <iterator>
#include <thread>
#include <iomanip> // dec
#include <cassert>
#include <cstddef> // size_t
#include <type_traits> // remove_reference_t
#include <utility> // forward, {make_,}index_sequence
#include <tuple> // tuple{,_size}, get
#include <boost/optional.hpp> // C++17: use std::optional
#include <boost/optional/optional_io.hpp> // printing
/*
Every filter can be understood as a function that takes an input and returns an output.
Chain of filters of matching in-out types is plain function composition (like g . f).
For non-composable functions, additional operator shall be used in lieu of . operator.
There is always ONE input and ONE output. Computations producing value of type T
that may fail shall return optional<T>. Result type CANNOT be void.
Want more data on input/output -- use std::tuple.
*/
using T = int; // for exposition only!
template<class... Fs>
struct Chain
{
using S = std::tuple<Fs...>; // sequence
using R = T; // TODO: compute this
S value; // the actual sequence
template<class T> struct is_tuple : std::false_type {};
template<class... T> struct is_tuple<std::tuple<T...>> : std::true_type {};
template<class T> struct is_optional : std::false_type {};
template<class T> struct is_optional<boost::optional<T>> : std::true_type {};
// Makes possible to apply tuple to the function that takes "exploded" tuple. Use C++17 apply.
// -- {
template<class F, class T, std::size_t... Is>
constexpr decltype(auto) mbind_impl(F&& f, T&& t, std::index_sequence<Is...>)
{
return f(std::get<Is>(t)...);
}
/** Applies tuple t to function f. */
template<class F, class T>
constexpr decltype(auto) mbind(F&& f, T&& t
, std::enable_if_t< is_tuple<std::remove_reference_t<T>>::value
&& ! is_optional<std::remove_reference_t<T>>::value>* = {})
{
return mbind_impl(std::forward<F>(f), std::forward<T>(t)
, std::make_index_sequence<std::tuple_size<std::remove_reference_t<T>>::value>{});
}
// -- }
// implementation of monadic bind -- {
/** Applies non-tuple and non-optional t to function f. */
template<class F, class T>
constexpr decltype(auto) mbind(F&& f, T&& t
, std::enable_if_t< ! is_tuple<std::remove_reference_t<T>>::value
&& ! is_optional<std::remove_reference_t<T>>::value>* = {})
{
return f(std::forward<T>(t));
}
/** Applies non-tuple optional t to function f. Return boost::none if t failed. */
template<class F, class T>
constexpr decltype(auto) mbind(F&& f, T&& t
, std::enable_if_t< ! is_tuple<std::remove_reference_t<T>>::value
&& is_optional<std::remove_reference_t<T>>::value>* = {})
{
return t ? boost::make_optional(f(t.get())) : boost::none;
}
// -- }
// -- { implementation of monadic do
template<std::size_t I, std::size_t Total, class As>
constexpr decltype(auto) mdo(As&& as
, std::enable_if_t<(I < Total)>* = {})
{
std::cout << "stage: " << I << " of " << Total << " value: " << as << "\n";
return mdo<I + 1, Total>(mbind(std::get<I>(value), std::forward<As>(as)));
}
template<std::size_t I, std::size_t Total, class As>
constexpr auto mdo(As&& as
, std::enable_if_t<I == Total>* = {})
{
std::cout << "stage: " << I << " of " << Total << " value: " << as << "\n";
return as;
}
// -- }
template<class As>
constexpr decltype(auto) operator() (As&& as)
{
static_assert(std::tuple_size<S>::value > 0, "Empty chain");
return mdo<0, std::tuple_size<S>::value>(std::forward<As>(as));
// decltype(auto) r = apply(std::get<0>(value), std::forward<As>(as));
// decltype(auto) r2 = apply(std::get<1>(value), r);
// return r2;
}
};
// Flow is: g . f (g after f) where f may fail.
struct F
{
bool fail = false;
boost::optional<T> operator() (T t) { return fail ? boost::none : boost::optional<T>{t + 1}; }
};
struct G
{
T operator() (T t) { return t + 1; }
};
// IMPORTANT NOTE: if something in the middle is optional, then output is always wrapped into optional (lifted)
int main()
{
Chain<F, G> chain; // TODO: add interface
// std::get<0>(chain.value).fail = true;
std::cout << chain(100) << "\n";
return 0;
}
/*
http://coliru.stacked-crooked.com/a/4f21ac40d6183437
g++ -std=c++14 -O2 -Wall -pedantic -DNDEBUG -pthread main.cpp && ./a.out
stage: 0 of 2 value: 100
stage: 1 of 2 value: 101
stage: 2 of 2 value: 102
102
*/
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment