Created
June 26, 2015 16:33
-
-
Save landonf/260501d7ab3ba9d44992 to your computer and use it in GitHub Desktop.
C++11 HLists
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* Copyright (c) 2015 Plausible Labs Cooperative, Inc. | |
* All rights reserved. | |
*/ | |
#pragma once | |
#include <functional> | |
#include <type_traits> | |
#include <limits> | |
#include <array> | |
#include <PLStdCPP/ftl/functional.h> | |
#include <PLStdCPP/ftl/type_functions.h> | |
#include <PLStdCPP/ftl/concepts/monad.h> | |
#include <PLStdCPP/ftl/concepts/monoid.h> | |
/** | |
* Polymorphic type-safe operations over tuples containing heterogeneous values. | |
*/ | |
namespace pl { | |
namespace hlist { | |
/* Internal implementation of C++14's index_sequence */ | |
template <std::size_t ...> struct index_sequence {}; | |
template <std::size_t N, std::size_t ... Indices> | |
struct make_index_sequence : make_index_sequence<N - 1, N - 1, Indices...> {}; | |
template <std::size_t ... Indices> | |
struct make_index_sequence<std::size_t(0), Indices...> : index_sequence<Indices...> {}; | |
/** | |
* Range-based construction of index_sequence. | |
* | |
* @tparam I The initial index of the returned sequence. | |
* @tparam Size The sequence length. | |
*/ | |
template <std::size_t I, std::size_t Size, std::size_t ... Indices> | |
struct make_index_range : make_index_range<I, Size - 1, I + (Size - 1), Indices...> {}; | |
template <std::size_t I, std::size_t ... Indices> | |
struct make_index_range<I, std::size_t(0), Indices...> : index_sequence<Indices...> {}; | |
#pragma mark Selection | |
/** | |
* Return a new tuple representing the selection of `Indices' from the provided @a tuple. | |
* | |
* @tparam Ts The element types of the provided tuple. | |
* @param tuple The tuple from which to return the selected elements. | |
*/ | |
template <std::size_t ... Indices, typename ... Ts> static constexpr auto select (const std::tuple<Ts...> &tuple) -> decltype(std::make_tuple(std::get<Indices>(tuple)...)) { | |
return std::make_tuple(std::get<Indices>(tuple)...); | |
} | |
/** | |
* Return a new tuple representing the selection of `Indices' from the provided @a tuple. | |
* | |
* @tparam Ts The element types of the provided tuple. | |
* @param tuple The tuple from which to return the selected elements. | |
*/ | |
template <std::size_t ... Indices, typename ... Ts> static constexpr auto select (const std::tuple<Ts...> &tuple, const index_sequence<Indices...> &) -> decltype(std::make_tuple(std::get<Indices>(tuple)...)) { | |
return select<Indices...>(tuple); | |
} | |
/** | |
* Return the head of the given @a tuple. | |
* | |
* @param tuple The tuple from which to return the first element. | |
*/ | |
template <typename ... Ts> static constexpr auto head (const std::tuple<Ts...> &tuple) -> typename std::tuple_element<0, std::tuple<Ts...>>::type { | |
return std::get<0>(tuple); | |
} | |
/** | |
* Return the tail of the given @a tuple. | |
* | |
* @param tuple The tuple from which to return the tail elements. | |
*/ | |
template <typename ... Ts> static constexpr auto tail (const std::tuple<Ts...> &tuple) -> decltype(select(tuple, make_index_range<1, sizeof...(Ts) - 1>())) { | |
return select(tuple, make_index_range<1, sizeof...(Ts) - 1>()); | |
} | |
#pragma mark Map | |
/** | |
* @internal | |
* Type-safe mapping via polymorphic application of F. | |
*/ | |
template<typename Fn, typename ... Ts> struct Mapper { | |
template<std::size_t ... Indices> static constexpr auto map (const std::tuple<Ts...> &tuple, const Fn &fn, const index_sequence<Indices...> &) -> | |
decltype(std::make_tuple(fn(std::get<Indices>(tuple))...)) | |
{ | |
return std::make_tuple(fn(std::get<Indices>(tuple))...); | |
} | |
}; | |
/** | |
* Map polymorphic function `F` over all values of @a tuple, returning a new tuple containing the well-typed results. | |
* | |
* @tparam Fn A templated function-like struct that accepts tuple elements as its single argument. | |
* @param tuple The tuple over which `F` will be applied. | |
*/ | |
template <typename Fn, typename ... Ts> constexpr auto map (const std::tuple<Ts...> &tuple, const Fn &f) -> decltype(Mapper<Fn, Ts...>::map(tuple, f, make_index_sequence<sizeof...(Ts)>())) { | |
return Mapper<Fn, Ts...>::map(tuple, f, make_index_sequence<sizeof...(Ts)>()); | |
} | |
#pragma mark Apply | |
/** | |
* @internal | |
* Type-safe mapping via application of (fn1, f2, ...) and (val1, val2, ...) tuples. | |
*/ | |
template<typename ... Fn> struct Applier { | |
template<std::size_t ... Indices> static constexpr std::tuple<typename std::decay<typename Fn::result_type>::type...> apply (const std::tuple<Fn...> &funcs, const std::tuple<typename std::decay<typename Fn::argument_type>::type...> &values, const index_sequence<Indices...> &) { | |
return std::make_tuple(std::get<Indices>(funcs)(std::get<Indices>(values))...); | |
} | |
}; | |
/** | |
* Type-safe 1:1 function application. | |
* | |
* @param funcs The functions to be applied to @a values. | |
* @param values The values to which @a funcs will be applied. | |
*/ | |
template <typename ... Fn> constexpr const std::tuple<typename std::decay<typename Fn::result_type>::type...> apply (const std::tuple<Fn...> &funcs, const std::tuple<typename std::decay<typename Fn::argument_type>::type...> &values) { | |
return Applier<Fn...>::apply(funcs, values, make_index_sequence<sizeof...(Fn)>()); | |
} | |
#pragma mark Fold | |
/** | |
* @internal | |
* Type-safe folding via polymorphic application of F. | |
*/ | |
template<typename F, typename S, typename ... Ts> struct Folder { | |
/* Terminal leftFold implementation */ | |
template<std::size_t Idx> static constexpr typename std::enable_if<Idx == sizeof...(Ts), S>::type leftFold (const S &state, const std::tuple<Ts...> &tuple, const F &fn) { | |
return state; | |
} | |
/* Recursively defined left fold */ | |
template<std::size_t Idx> static constexpr typename std::enable_if<Idx < sizeof...(Ts), S>::type leftFold (const S &state, const std::tuple<Ts...> &tuple, const F &fn) { | |
return leftFold<Idx+1>(fn(state, std::get<Idx>(tuple)), tuple, fn); | |
} | |
}; | |
/** | |
* Type-safe polymorphic left fold over @a tuple. | |
* | |
* @param state Initial state. | |
* @param tuple Tuple over which the left fold will be computed. | |
* @param fn A struct providing a unary function-call operator that may be applied to all elements of @a tuple. | |
*/ | |
template <typename F, typename S, typename ... Ts> constexpr typename std::decay<S>::type leftFold (const S &state, const std::tuple<Ts ...> &tuple, const F &fn) { | |
return Folder<F, typename std::decay<S>::type, Ts...>::template leftFold<0>(state, tuple, fn); | |
} | |
#pragma mark Zip | |
/** | |
* @internal | |
* Type-safe zipping via polymorphic application of the given zipper function F. This is essentially a specialization of | |
* map() across two tuples. | |
*/ | |
template<typename LHS, typename RHS> struct Zipper { | |
static_assert(ftl::is_same_template<std::tuple<>, LHS>::value, "The `lhs' argument is not a tuple."); | |
static_assert(ftl::is_same_template<std::tuple<>, RHS>::value, "The `rhs' argument is not a tuple."); | |
static_assert(std::tuple_size<LHS>::value == std::tuple_size<RHS>::value, "The `lhs' and `rhs' tuple operands must be identically sized"); | |
template<typename Fn, std::size_t ... Indices> static auto zipWith (const LHS &lhs, const RHS &rhs, const Fn &fn, const index_sequence<Indices...> &) -> | |
decltype(std::make_tuple(fn(std::get<Indices>(lhs), std::get<Indices>(rhs))...)) | |
{ | |
return std::make_tuple(fn(std::get<Indices>(lhs), std::get<Indices>(rhs))...); | |
} | |
}; | |
/** | |
* @internal | |
* Polymorphic zipper function; maps (L, R) pairs to std::pair<L, R>. | |
*/ | |
static constexpr struct zip_pair_fn { | |
template <typename L, typename R> std::pair<L, R> constexpr operator() (const L &lhs, const R &rhs) const { | |
return std::make_pair(lhs, rhs); | |
} | |
} zip_pair {}; | |
/** | |
* Type-safe polymorphic zipWith over @a lhs and @rhs tuple operands. | |
* | |
* @param lhs The first tuple operand. | |
* @param rhs The second tuple operand. | |
* @param fn A struct providing a binary function-call operator that may be applied to all pairs in @a lhs and @a rhs. | |
*/ | |
template <typename Fn, typename LHS, typename RHS> constexpr auto zipWith (const LHS &lhs, const RHS &rhs, const Fn &fn) -> | |
decltype(Zipper<LHS, RHS>::zipWith(lhs, rhs, fn, make_index_sequence<std::tuple_size<RHS>::value>())) | |
{ | |
return Zipper<LHS, RHS>::zipWith(lhs, rhs, fn, make_index_sequence<std::tuple_size<RHS>::value>()); | |
} | |
/** | |
* Type-safe zip over equally sized @a lhs and @rhs tuple operands, producing a new tuple | |
* composed of std::pair<lhs..., rhs...> values. | |
* | |
* @param lhs The first tuple operand. | |
* @param rhs The second tuple operand. | |
*/ | |
template <typename LHS, typename RHS> constexpr auto zip (const LHS &lhs, const RHS &rhs) -> | |
decltype(zipWith(lhs, rhs, zip_pair)) | |
{ | |
return zipWith(lhs, rhs, zip_pair); | |
} | |
#pragma mark Array Conversion | |
/** | |
* @internal | |
* | |
* Conversion of std::tuple values to std::array. | |
*/ | |
template <typename T> struct ToArray { | |
template<std::size_t ... Indices> constexpr static std::array< | |
typename std::tuple_element<0, T>::type, | |
std::tuple_size<T>::value | |
> to_array (const T &tuple, const index_sequence<Indices...> &) { | |
return {{ std::get<Indices>(tuple)... }}; | |
} | |
}; | |
/** | |
* Convert a std::tuple to a std::array. | |
*/ | |
template <typename T> constexpr static auto to_array (const T &tuple) -> decltype(ToArray<T>::to_array(tuple, make_index_sequence<std::tuple_size<T>::value>())) { | |
return ToArray<T>::to_array(tuple, make_index_sequence<std::tuple_size<T>::value>()); | |
} | |
#pragma mark Sequence | |
/** | |
* @internal | |
* | |
* Sequencing (essentially a fold) over a tuple of Monad<Monoid> values. | |
*/ | |
template<typename M> struct Sequencer { | |
/* Perform a sequence with the supplied initial value. */ | |
template <typename ... Ms> static M sequence (const M &zero, const std::tuple<Ms...> &operand) | |
{ | |
typedef ftl::Value_type<M> V; | |
typedef ftl::monad<M> TMonad; | |
typedef ftl::monoid<V> VMonoid; | |
const auto operands = to_array(operand); | |
auto accum = zero; | |
for (size_t i = 0; i < operands.size(); i++) { | |
const auto &next = operands[i]; | |
accum = accum >>= [&next](const V &a) { | |
return [&a](const V &n) { | |
return VMonoid::append(a, n); | |
} % next; | |
}; | |
} | |
return accum; | |
} | |
/* Perform a sequence with the monoid<V>-supplied zero value as the initial zero value. */ | |
template <typename ... Ms> static M sequence (const std::tuple<Ms...> &operand) { | |
typedef ftl::Value_type<M> V; | |
typedef ftl::monad<M> TMonad; | |
typedef ftl::monoid<V> VMonoid; | |
const auto zero = TMonad::pure(VMonoid::id()); | |
return sequence(zero, operand); | |
} | |
}; | |
/** | |
* Given a tuple containing monadic-wrapped monoidal values, sequence all values, producing a single accumulated value. | |
* | |
* @param initial The initial value to which all sequenced values will be appended. | |
* @param tuple A tuple containing monadic values to be sequenced. | |
*/ | |
template <typename M, typename ... Ms> static auto sequence (const M &initial, const std::tuple<M, Ms...> &tuple) -> decltype(Sequencer<M>::sequence(tuple)) { | |
return Sequencer<M>::sequence(initial, tuple); | |
} | |
/** | |
* Given a tuple containing monadic-wrapped monoidal values, sequence all values, producing a single accumulated value. | |
* | |
* @param tuple A tuple containing monadic values to be sequenced. | |
*/ | |
template <typename M, typename ... Ms> static auto sequence (const std::tuple<M, Ms...> &tuple) -> decltype(Sequencer<M>::sequence(tuple)) { | |
return Sequencer<M>::sequence(tuple); | |
} | |
/** | |
* Given an empty tuple with a known type, sequence returns an empty result. | |
* | |
* @param tuple An empty tuple to sequence. | |
*/ | |
template < | |
typename M, | |
typename V = ftl::Value_type<M>, | |
typename R = ftl::Requires<ftl::Monad<M>{}>, | |
typename RV = ftl::Requires<ftl::Monoid<V>{}> | |
> constexpr M sequence (const std::tuple<> &empty) { | |
return ftl::monad<M>::pure(ftl::monoid<V>::id()); | |
} | |
/** | |
* Given an empty tuple, sequence returns the initial value. | |
* | |
* @param initial The initial value to which all sequenced values will be appended. | |
* @param tuple An empty tuple to sequence. | |
*/ | |
template <typename M> constexpr const M sequence (const M &initial, const std::tuple<> &empty) { | |
return initial; | |
} | |
} | |
}; | |
#pragma mark - |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment