Skip to content

Instantly share code, notes, and snippets.

@MORTAL2000
Forked from landonf/hlist.hpp
Created May 3, 2016 20:42
Show Gist options
  • Save MORTAL2000/817e47ce5bd5c293606822b36549bc0f to your computer and use it in GitHub Desktop.
Save MORTAL2000/817e47ce5bd5c293606822b36549bc0f to your computer and use it in GitHub Desktop.

Revisions

  1. @landonf landonf created this gist Jun 26, 2015.
    324 changes: 324 additions & 0 deletions hlist.hpp
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,324 @@
    /*
    * 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 -