$include_dir="/home/hyper-archives/boost/include"; include("$include_dir/msg-header.inc") ?>
Subject: Re: [boost]  [Hana] Formal review for Hana
From: Louis Dionne (ldionne.2_at_[hidden])
Date: 2015-06-16 09:04:39
Zach Laine <whatwasthataddress <at> gmail.com> writes:
> [...]
>
> There are some things that I consider worth changing, however:
>
> - The use of '.' in names instead of '_' (Louis has already said that he
>   will change this).
Yes, I'm currently changing this. You can refer to [1].
> - The inclusion of multiple functions that do the same thing.  Synonyms such
>   as size()/length() and fold()/fold.left() sow confusion.  As a maintainer
>   of code that uses Hana, it means I have to intermittently stop and refer
>   back to the docs.  Hana is already quite large, and there's a lot there
>   to try to understand!
The synonyms were added as a balm in the few cases where there was a rather
strong disagreement on what I wanted and what other people wanted. Perhaps
I should have held my position harder, or I should have done the contrary.
If the aliases seem to annoy a significant number of persons, I'll consider
removing them (and keeping IDK which one yet).
> [...]
>
> - I'd like to see first() / last() / after_first() / before_last() instead
>  of head() / last() / tail() / init() (this was suggested by Louis offline),
>   as it reinforces the relationships in the names.
Actually, I thought we had agreed on front/back drop_front/drop_back :-).
Anyway, this is tracked by this issue [2]. It's not a 100% trivial change
because that will mean that `drop` is replaced by `drop_front`, which can
also take an optional number of elements to drop. Same goes for `drop_back`,
which will accept an optional number of elements to drop from the end of the
sequence.
> - I find boost::hana::make<boost::hana::Tuple>(...) to be clunkier than
>   boost::hana::_tuple<>, in many places.
You can use boost::hana::make_tuple(...).
>   Since I want to write this in some cases, it would be nice if it was
>   called boost::hana::tuple<> instead, so it doesn't look like I'm using
>   some implementation-detail type.
Right; since `_tuple<...>` is a well specified type, I guess it could have
a proper name without a leading underscore. More generally, I have to clean
up some names I use. For example, you can create a Range with
    - boost::hana::make_range(...)
    - boost::hana::make<boost::hana::Range>(...)
    - boost::hana::range(...)
    - boost::hana::range_c<...>
And the type of any of these objects is called _range<...>, although it is
implementation-defined. I opened this issue [3] to track this cleanup process.
>   * Implementation
>
> Very good; mind-bending in some of the implementation details.  However,
> there are some choices that Louis made that I think should be changed:
>
> - The inclusion of standard headers was forgone in an effort to optimize
>   compile times further (Hana builds quickly, but including e.g.
>   <type_traits> increases the include time of hana.hpp by factors).
>   This is probably not significant in most users' use of Hana; most users
>   will include Hana once and then instantiate, instantiate, instantiate.
>   The header-inclusion time is just noise in most cases, and is quite fast
>   enough in others.  However, rolling one's own std::foo, where std::foo is
>   likely to evolve over time, is asking for subtle differences to creep in,
>   in what users expect std::foo to do vs. what hana::detail::std::foo does.
>   What happens if I use std::foo in some Hana-using code that uses
>   hana::detail::std::foo internally? Probably, subtle problems.  Moreover,
>   this practice introduces an unnecessary maintenance burden on Hana to
>   follow these (albeit slowly) moving targets.
The current develop branch now uses `<type_traits>` and other standard
headers. I am glad I have finally made the move, since this was also
preventing me from properly interoperating with `std::integral_constant`
and other quirks. All for the better!
> - I ran in to a significant problem in one case that suggests a general
>   problem.
>
>   In the linear algebra library I partially reimplemented, one declares
>   matrices like this (here, I use the post-conversion Hana tuples):
>
>   matrix<
>       hana::tuple<a, b>,
>       hana::tuple<c, d>
>   > m;
>
>   Each row is a tuple.  But the storage needs to be a single tuple, so
>   matrix<> is actually a template alias for:
>
>   template <typename Tuple, std::size_t Rows, std::size_t Columns>
>   matrix_t { /* ... */ };
>
>   There's a metafunction that maps from the declaration syntax to the
>   correct representational type:
>
>   template <typename HeadRow, typename ...TailRows>
>   struct matrix_type
>   {
>       static const std::size_t rows = sizeof...(TailRows) + 1;
>       static const std::size_t columns = hana::size(HeadRow{});
>
>       /* ... */
>
>       using tuple = decltype(hana::flatten(
>           hana::make<hana::Tuple>(
>               HeadRow{},
>               TailRows{}...
>           )
>       ));
>
>       using type = matrix_t<tuple, rows, columns>;
>   };
>
>   The problem with the code above, is that it works with a HeadRow tuple
>   type that is a literal type (e.g. a tuple<float, double>).  It does not
>   work with a tuple containing non-literal types (e.g. a
>   tuple<boost::units::quantity<boost::units::si::time>, /* ... */>).
>
>   This is a problem for me.  Why should the literalness of the types
>   contained in the tuple determine whether I can know its size at compile
>   time?  I'd like to be able to write this as a workaround:
>
>   static const std::size_t columns = hana::size(hana::type<HeadRow>);
That is the incorrect workaround. The proper, Hana-idiomatic way to write
what you need is:
    namespace detail {
        auto make_matrix_type = [](auto rows) {
            auto nrows = hana::size(rows);
            static_assert(nrows >= 1u,
            "matrix_t<> requires at least one row");
            auto ncolumns = hana::size(rows[hana::size_t<0>]);
            auto uniform = hana::all_of(rows, [=](auto row) {
                return hana::size(row) == ncolumns;
            });
            static_assert(uniform,
            "matrix_t<> requires tuples of uniform length");
            using tuple_type = decltype(hana::flatten(rows));
            return hana::type<matrix_t<tuple_type, nrows, ncolumns>>;
        };
    }
    template <typename ...Rows>
    using matrix = typename decltype(
        detail::make_matrix_type(hana::make_tuple(std::declval<Rows>()...))
    )::type;
By the way, see my pull request at [4]. I'm now passing all the tests, and
with a compilation time speedup!
>   I'd also like Hana to know that when it sees a type<> object, that is
>   should handle it specially and do the right thing.  There may be other
>   compile-time-value-returning algorithms that would benefit from a similar
>   treatment.  I was unable to sketch in a solution to this, because
>   hana::_type is implemented in such a way as to prevent deduction on its
>   nested type (the nested type is actually hana::_type::_::type, not
>   hana::_type::type -- surely to prevent problems elsewhere in the
>   implementation).
This is done to prevent ADL to cause the instantiation of `T` in `_type<T>`.
For this, `decltype(type<T>)` has to be a dependent type in which `T` does
not appear.
>   As an aside, note that I was forced to name this nested '_' type
>   elsewhere in a predicate.  This is not something I want my junior
>   coworkers to have to read:
>
>   template <typename T>
>   constexpr bool some_predicate (hana::_type<T> x)
>   { return hana::_type<T>::_::type::size == some_constant;}
You made your life harder than it needed to be. First, you should have
written
    `decltype(hana::type<T>)::type::size`
instead of
    `hana::_type<T>::_::type`
This `_` nested type is an implementation detail. But then, you realize that
this is actually equivalent to `T::size`. Indeed, `decltype(type<T>)::type`
is just `T`. So basically, what you wanted is
    template <typename T>
    constexpr bool some_predicate (hana::_type<T> x)
    { return T::size == some_constant;}
which looks digestible junior coworkers :-).
>   Anyway, after trying several different ways to overcome this, I punted on
>   using hana::size() at all, and resorted to:
>
>   static const std::size_t columns = HeadRow::size;
>
>   ... which precludes the use of std::tuple or my_arbitrary_tuple_type with
>   my matrix type in future.
I think my workaround should make this OK.
> - I got this error:
>
>   error: object of type 'boost::hana::_tuple<float, float, float, float>'
>   cannot be assigned because its copy assignment operator is implicitly
>   deleted
This is a known problem, tracked by this issue [5].
>   This seems to result from the definitions of hana::_tuple and
>   hana::detail::closure.  Specifically, the use of the defaulted special
>   member functions did not work, given that closure's ctor is declared like
>   this:
>
>   constexpr closure_impl(Ys&& ...y)
>       : Xs{static_cast<Ys&&>(y)}...
>   { }
>
>   The implication is that if I construct a tuple from rvalues, I get a
>   tuple<T&&, U&&, ...>.  The fact that the special members are defaulted is
>   not helpful, since they are still implicitly deleted if they cannot be
>   generated (e.g. the copy ctor, when the members are rvalue refs).  I
>   locally removed all the defaulted members from _tuple and closure, added
>   to closure a defined default ctor, and changed its existing ctor to this:
>
>   constexpr closure_impl(Ys&& ...y)
>       : Xs{::std::forward<Ys>(y)}...
>   { }
>
>   ... and I stopped having problems.  My change might not have been entirely
>   necessary (I think that the static_cast<> might be ok to leave in there),
Actually, `std::forward<Y>(y)` is equivalent to `static_cast<Y&&>(y)`. I'm
using the latter because I measured a compile-time speedup of about 13.9%
when removing it. This is because Hana uses perfect forwarding quite a bit,
and that was the cost of instantiating `std::forward` (lol).
So the only thing you changed really is to remove all the defaulted members.
>   but something needs to change, and I think tests need to be added to cover
>   copies, assignment, moves, etc., of tuples with different types of values
>   and r/lvalues.
That is true. Added this issue [6].
>   * Documentation
>
> Again, this is overall very good.  Some suggestions:
>
> - Make a cheatsheet for the data types, just like the ones for the
>   algorithms.
Great idea. See this issue [7].
> - Do the same thing for the functions that are currently left out (Is this
>   because they are not algorithms per se?).  E.g. I used
>   hana::repeat<hana::Tuple>(), but it took some finding.
I only put the __most__ useful algorithms in the cheatsheet, to avoid
crowding it too much. I can add some more.
>   * Tests
>
> - The tests seem quite extensive.
>
>   * Usefulness
>
> Extremely useful.  I find Hana to be a faster, *much* easier-to-use MPL, and
> an easier-to-use Fusion, if only because it shares the same interface as the
> MPL-equivalent part.  There are other things in there I haven't used quite
> yet that are promising as well.  A lot of the stuff in Hana feels like a
> solution in search of a problem -- but that may just be my ignorance of
> Haskell-style programming.  I look forward to one day understanding what
> I'd use hana::duplicate() for, and using it for that. :)
Lol. The Comonad concept (where duplicate() comes from) is more like an
experimental to formalize Laziness. See my blog post about this [8].
> That being said, I really, really, want to do this:
>
> tuple_1 foo;
> tuple_2 bar;
>
> boost::hana::copy_if(foo, bar, [](auto && x) { return my_predicate(x); });
>
> Currently, I cannot.  IIUC, I must do:
>
> tuple_1 foo;
> auto bar = boost::hana::take_if(foo, [](auto && x) { return
> my_predicate(x); });
>
> Which does O(N^2) copies, where N = boost::hana::size(bar), as it is
> pure-functional.  Unless the optimizer is brilliant (and it typically is
> not), I cannot use code like this in a hot section of code.  Also, IIUC
> take_if() cannot accommodate the case that decltype(foo) != decltype(bar)
I don't understand; take_if() is not an algorithm provided by Hana.
> What I ended up doing was this:
>
> hana::for_each(
>     hana::range_c<std::size_t, 0, hana::size(foo)>,
>     [&](auto i) {
>         hana::at(bar, i) =
>             static_cast<std::remove_reference_t<decltype(hana::at(bar,
> i))>>(
>                 hana::at(foo, i)
>             );
>     }
> );
>
> ... which is a little unsatisfying.
>
> I also want move_if().  And a pony.
Oh, so you're trying to do an element-wise assignment? Since Hana expects
functions to be pure in general and assignment is a side effect, this cannot
be achieved with a normal algorithm. However, Hana provides some algorithms
that alow side-effects. One of them is for_each, and the other is `.times`
(from IntegralConstant). I suggest you could write the following:
    hana::size(foo).times.with_index([&](auto i) {
        using T = std::remove_reference_t<decltype(bar[i])>;
        bar[i] = static_cast<T>(foo[i]);
    });
> - Did you attempt to use the library? If so:
>   * Which compiler(s)
>
> Clang 3.6.1, on both Mac and Linux.  My code did actually compile (though
> did not link due to the already-known bug) on GCC 5.1.
Cool!
>   * What was the experience? Any problems?
>
> I had to add "-lc++abi" to the CMake build to fix the link on Linux, but
> otherwise no problems.
That is curious, because the Travis build is on linux and does not need that.
Did you set the path to your libc++ installation on Linux? The README states
that you should use
    -DLIBCXX_ROOT=/path/to/libc++
when generating the CMake build system. If it does not work as expected,
I'll open up an issue because I really want to track down these problems,
which make the library more painful to tryout.
Thanks for the review, Zach. I also appreciate all the comments you provided
privately; they were super useful. And you were right about the dots in the
names; I should have changed them before the review. :-)
Regards,
Louis
[1]: https://github.com/ldionne/hana/issues/114
[2]: https://github.com/ldionne/hana/issues/66
[3]: https://github.com/ldionne/hana/issues/122
[4]: https://github.com/tzlaine/Units-BLAS/pull/1
[5]: https://github.com/ldionne/hana/issues/93
[6]: https://github.com/ldionne/hana/issues/123
[7]: https://github.com/ldionne/hana/issues/124
[8]: http://ldionne.com/2015/03/16/laziness-as-a-comonad/