Boost logo

Boost :

Subject: Re: [boost] [Hana] Announcing Hana's formal review next week (June 10th)
From: Louis Dionne (ldionne.2_at_[hidden])
Date: 2015-06-20 17:19:24


Abel Sinkovics <abel <at> elte.hu> writes:

> [...]

> It would be useful to add a
> step-by-step guide to the documentation for extending the library with
> custom algebraic data-types - for example a "how to build your own
> Optional/Either/etc" (Note: I was reading Optional's implementation as
> an example to build my own data types).
>
> Once I learned how to build my own data-types, I could easily create the
> ones I needed and they worked intuitively (to me). So it seems to be
> well supported and should be better documented.

This should be part of the (still incomplete) section of the tutorial on
"Extending the library". I guess using a concrete example like Optional
is a good idea. Thanks for the heads up.

> It is great that the library makes it possible to have static if_ and
> switch_ in the body of a function. I think a few things should be
> pointed out about them (probably in the documentation):
>
> - when entering a branch of an if_ (or switch_) you are calling a
> different function which is not (always) the same as having a block
> inside your function body. You might (and are very likely to) be
> affected by how you pass arguments to this function (capture in the
> lambda, pass by value, pass by rvalue reference, etc). This is something
> to be aware of when using these constructs.

Definitely. I will add this to the documentation.

> - if you use these structures (especially if you have 2 or 3 nested
> layers of them in the same function), the same variable might have
> different names on each level (eg. to make sure that a branch of the if_
> gets instantiated only when it is needed and safe to do so). This can be
> confusing.

There's actually a different way to do it, but it is not documented in the
tutorial. It is documented in the reference of `eval_if` [1]. I will try to
add it to the tutorial.

> [...]
>
> Even though I haven't tried it, but based on the documentation the Lazy
> type seems to be an interesting way to approach lazy evaluation for
> metaprograms. The idea of the lazy monad seems very interesting. One
> thing I'd fix in Lazy's documentation: eval_if is mentioned in Lazy's
> doc, however there is no link to it and it was not trivial to find
> eval_if's doc. I'd make the places mentioning eval_if links to its
> description.

Taking a note to add links to eval_if. More generally, this should always
be handled properly but the documentation tool, but this does not happen
with Doxygen :-(.

> Why is string not a model of the Constant concept? I tend to think of
> strings as values and since they contain characters only, they should be
> representable at runtime. (the question is probably how they should be
> represented).

String used to be a model of Constant. The problem is that the `value`
function, which extracts the value from the Constant, should preserve the
semantics of the original Constant. However, since String's value was
(formerly) a `char const*`, that structure was not preserved. For example,
it would be true that

    "abcd"_s < "abcde"_s

while

    value_of("abcd"_s) < value_of("abcde"_s)

would not necessarily hold, because it would compare two `char const*`.
The proper workaround would be to have a compile-time string class, and
the `value()` of a Hana String should be a constexpr object of that type.

However, if you need a `char const*` from a Hana String, you can use
`to<char const*>("abcd"_s)`, which is a non structure-preserving conversion.

> What is the reason behind using tag dispatching in Hana? More
> specifically, why is tag dispatching used instead of for example
> enable_if? For example:
>
> template <class T, class = std::enable_if<is_a<Tuple, T>>>
> auto head(T t);
>
> template <class T, class = std::enable_if<is_a<String, T>>>
> auto head(T t);
>
> ...
>
> It is not clear to me why tag dispatching is preferred over this (or a
> similar) approach. (eg. does tag dispatching perform better?) It might
> be explained in the documentation.

I wanted a two-layer dispatching system because the first layer (the
functions you call) are actually function objects, which allows using them
in higher-order algorithms. They can also do some compile-time sanity checks,
which improves error messages.

Now, we could just as well write:

    template <class T, class = std::enable_if<is_a<Tuple, T>>>
    auto head_impl(T t);

    template <class T, class = std::enable_if<is_a<String, T>>>
    auto head_impl(T t);

and have head() call head_impl() after doing its sanity checks. However,
when checking whether a type is a model of some concept, we basically check
that some key functions are implemented. For example, `models<Iterable, T>`
checks whether the is_empty, head and tail functions implemented for T. AFAIK,
the only way to detect this with the above approach is to basically check
whether the following expressions are valid in a SFINAE-able context:

    head_impl(std::declval<T>())
    is_empty_impl(std::declval<T>())
    tail_impl(std::declval<T>())

But this requires doing the actual work. With tag dispatching, we can just
ask whether head_impl<T>, is_empty_impl<T> and tail_impl<T> are defined, and
nothing happens until we actually call head_impl<T>::apply(...). This is one
of the reasons why I went with tag-dispatching, but frankly this is also a
slightly arbitrary decision. Providing customization points is not exactly
easy; there are a lot of different solutions. Also, the heterogeneous context
definitely makes the task of providing customization points harder, since
you can't rely on the actual type of the objects to dispatch.

> [...]
>
> Multiple types are models of the Monad concept, however, the
> documentation does not explain how the monadic operations are
> implemented for the different types (eg. what chain is doing for lists,
> etc). I believe it is not (always) trivial and should be explained in
> the documentation (I had simliar things in Metamonad and decided to add
> it to the documentation).

Technically, the implementation of `flatten` for Sequences is documented in
the Sequence concept. From there, you could derive the implementation of
`chain` and all the other functions from Monad. Of course, doing so is not
easy and I will add this to the documentation.

> I really like it that for some elements (eg. Functor's transform
> operation) the documentation contains benchmarks.

In the future, I plan to provide benchmarks for almost every algorithm.
That used to be the case, but I changed the benchmarking system and didn't
have the time to rewrite them all.

> [...]

Thanks a lot for your review, Abel!

Regards,
Louis

[1]: http://goo.gl/c0qgsb


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk