/* * Copyright (c) 2015 Plausible Labs Cooperative, Inc. * All rights reserved. */ #pragma once #include #include #include #include #include #include #include #include /** * Polymorphic type-safe operations over tuples containing heterogeneous values. */ namespace pl { namespace hlist { /* Internal implementation of C++14's index_sequence */ template struct index_sequence {}; template struct make_index_sequence : make_index_sequence {}; template struct make_index_sequence : index_sequence {}; /** * Range-based construction of index_sequence. * * @tparam I The initial index of the returned sequence. * @tparam Size The sequence length. */ template struct make_index_range : make_index_range {}; template struct make_index_range : index_sequence {}; #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 static constexpr auto select (const std::tuple &tuple) -> decltype(std::make_tuple(std::get(tuple)...)) { return std::make_tuple(std::get(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 static constexpr auto select (const std::tuple &tuple, const index_sequence &) -> decltype(std::make_tuple(std::get(tuple)...)) { return select(tuple); } /** * Return the head of the given @a tuple. * * @param tuple The tuple from which to return the first element. */ template static constexpr auto head (const std::tuple &tuple) -> typename std::tuple_element<0, std::tuple>::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 static constexpr auto tail (const std::tuple &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 struct Mapper { template static constexpr auto map (const std::tuple &tuple, const Fn &fn, const index_sequence &) -> decltype(std::make_tuple(fn(std::get(tuple))...)) { return std::make_tuple(fn(std::get(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 constexpr auto map (const std::tuple &tuple, const Fn &f) -> decltype(Mapper::map(tuple, f, make_index_sequence())) { return Mapper::map(tuple, f, make_index_sequence()); } #pragma mark Apply /** * @internal * Type-safe mapping via application of (fn1, f2, ...) and (val1, val2, ...) tuples. */ template struct Applier { template static constexpr std::tuple::type...> apply (const std::tuple &funcs, const std::tuple::type...> &values, const index_sequence &) { return std::make_tuple(std::get(funcs)(std::get(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 constexpr const std::tuple::type...> apply (const std::tuple &funcs, const std::tuple::type...> &values) { return Applier::apply(funcs, values, make_index_sequence()); } #pragma mark Fold /** * @internal * Type-safe folding via polymorphic application of F. */ template struct Folder { /* Terminal leftFold implementation */ template static constexpr typename std::enable_if::type leftFold (const S &state, const std::tuple &tuple, const F &fn) { return state; } /* Recursively defined left fold */ template static constexpr typename std::enable_if::type leftFold (const S &state, const std::tuple &tuple, const F &fn) { return leftFold(fn(state, std::get(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 constexpr typename std::decay::type leftFold (const S &state, const std::tuple &tuple, const F &fn) { return Folder::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 struct Zipper { static_assert(ftl::is_same_template, LHS>::value, "The `lhs' argument is not a tuple."); static_assert(ftl::is_same_template, RHS>::value, "The `rhs' argument is not a tuple."); static_assert(std::tuple_size::value == std::tuple_size::value, "The `lhs' and `rhs' tuple operands must be identically sized"); template static auto zipWith (const LHS &lhs, const RHS &rhs, const Fn &fn, const index_sequence &) -> decltype(std::make_tuple(fn(std::get(lhs), std::get(rhs))...)) { return std::make_tuple(fn(std::get(lhs), std::get(rhs))...); } }; /** * @internal * Polymorphic zipper function; maps (L, R) pairs to std::pair. */ static constexpr struct zip_pair_fn { template std::pair 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 constexpr auto zipWith (const LHS &lhs, const RHS &rhs, const Fn &fn) -> decltype(Zipper::zipWith(lhs, rhs, fn, make_index_sequence::value>())) { return Zipper::zipWith(lhs, rhs, fn, make_index_sequence::value>()); } /** * Type-safe zip over equally sized @a lhs and @rhs tuple operands, producing a new tuple * composed of std::pair values. * * @param lhs The first tuple operand. * @param rhs The second tuple operand. */ template 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 struct ToArray { template constexpr static std::array< typename std::tuple_element<0, T>::type, std::tuple_size::value > to_array (const T &tuple, const index_sequence &) { return {{ std::get(tuple)... }}; } }; /** * Convert a std::tuple to a std::array. */ template constexpr static auto to_array (const T &tuple) -> decltype(ToArray::to_array(tuple, make_index_sequence::value>())) { return ToArray::to_array(tuple, make_index_sequence::value>()); } #pragma mark Sequence /** * @internal * * Sequencing (essentially a fold) over a tuple of Monad values. */ template struct Sequencer { /* Perform a sequence with the supplied initial value. */ template static M sequence (const M &zero, const std::tuple &operand) { typedef ftl::Value_type V; typedef ftl::monad TMonad; typedef ftl::monoid 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-supplied zero value as the initial zero value. */ template static M sequence (const std::tuple &operand) { typedef ftl::Value_type V; typedef ftl::monad TMonad; typedef ftl::monoid 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 static auto sequence (const M &initial, const std::tuple &tuple) -> decltype(Sequencer::sequence(tuple)) { return Sequencer::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 static auto sequence (const std::tuple &tuple) -> decltype(Sequencer::sequence(tuple)) { return Sequencer::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, typename R = ftl::Requires{}>, typename RV = ftl::Requires{}> > constexpr M sequence (const std::tuple<> &empty) { return ftl::monad::pure(ftl::monoid::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 constexpr const M sequence (const M &initial, const std::tuple<> &empty) { return initial; } } }; #pragma mark -