Hi,
I've implemented a (very small) core of Metaparse using Hana to try the
library out. The aim was not to evaluate how such a base metaprogramming
library would affect the design of Metaparse, but to have a non-trivial
example to try the library out with.
As I have experience with compile-time only computations, I've been
focusing on that aspect of Hana.
A great thing about Hana is that it provides a syntax for template
metaprograms that is familiar for C++ developers without large overhead
for the compilation process. It makes functions over types look like
"regular" C++ functions.
However, this advantage might be a disadvantage in some cases. With the
Boost.MPL approach, the code evaluated at compile-time has a
significantly different syntax than the code executed at runtime. It is
easy to tell which is which, while with the new approach, this is not
(always) that simple, which might lead to confusion in more complex
code. So I have some concerns about it in larger metaprograms.
The library's design is strongly influenced by Haskell, which is in my
opinion a good thing given the functional nature of metaprogramming in C++.
Metaparse uses types that are built with Haskell's algebraic data-types
in mind. (Given a type and a number of constructors). This concept works
well with the "pattern matching" partial template specialisation
provides. I could represent such data-types in Hana (and the library
offers a few as well, eg. Optional is the Hana representation of
Haskell's Maybe). 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.
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.
- 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.
- in some cases (and I have seen such cases in my small experiment) you
don't need lambdas. If calculating both branches of the if_ is always
safe and has no large overhead, you might just "inline" the two return
values. Eg. instead of
if_(<condition>, []() { return 1_c; }, []() { return 2_c; })
you can have
if_(<condition>, 1_c, 2_c)
This makes the code simpler. (My "real" example is inside a more
complex function, so I came up with this artificial example, I hope it
still demonstrates the idea).
Because of using if_ with lambdas (in most cases), I ended up leaving
almost every block in my functions with "return", so there were
"return"s all over my code. I just wanted to point this out, I have no
idea how to improve this. I guess it is something similar to having
"typename" everywhere in MPL-style metaprograms. :)
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.
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).
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
We encourage your participation in this review. At a minimum, kindly state: - Whether you believe the library should be accepted into Boost Yes.
- Your name Ábel Sinkovics
- Your knowledge of the problem domain. I have several years of template metaprogramming experience. I have implemented MPL extensions supporting Haskell-style functional programming (Mpllibs.Metamonad). I have no experience with heterogeneous containers.
You are strongly encouraged to also provide additional information: - What is your evaluation of the library's: * Design Clean. It makes things (eg. datatype) explicit that have been implicitly present in earlier metaprogramming libraries. Some design decisions (pointed out above) should be better documented.
* Implementation I haven't looked at the implementation of the library in detail. I was using the implementation of some parts as examples for extending the library. The code was well segmented (which definitions/specialisations implement what aspects) and easy to understand.
* Documentation I find the reference useful and the tutorial is explaning the concept of the library (and the new metaprogramming technique) well.
* Tests The library seems to be well covered with tests. I haven't looked at
However, I felt that the documentation could be extended with step-by-step tutorials on how to do common things (eg. defining your new data types, how to use things like if_, etc). 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). I really like it that for some elements (eg. Functor's transform operation) the documentation contains benchmarks. them in detail.
* Usefulness I think the library is very useful as a metaprogramming base library to build on top of. However, given its limited compiler support, libraries built on top of it will probably have to wait until compilers catch up before they can be widely adopted.
- Did you attempt to use the library? If so: Yes.
* Which compiler(s)? Clang 3.6.0
* What was the experience? Any problems? It was working, I had no problems.
- How much effort did you put into your evaluation of the review? I've read the tutorial and reimplemented the core of a template metaprogramming library using Hana and tried a few other random things out.
Regards, Ábel