Boost Hana comments
First off, while I haven't thoroughly reviewed the library, I am certainly
impressed by a lot of what I see. It is obvious that this library
represents a lot of excellent work.
Here are my miscellaneous comments:
Constant type class:
--------------------
Why isn't std::integral_constant an instance?
There is no documentation of what the value method actually returns, just
that it returns something that is a constexpr. Does value(integral
Sorry for the slow answer; I completely missed your message. Also note that
I've been working on a large changeset for the past week, so some comments
are actually already fixed in my local version.
Jeremy Maitin-Shepard
First off, while I haven't thoroughly reviewed the library, I am certainly impressed by a lot of what I see. It is obvious that this library represents a lot of excellent work.
Here are my miscellaneous comments:
Constant type class: --------------------
Why isn't std::integral_constant an instance?
Good observations; that's a matter of time. std::integral_constant is an instance in my local version.
There is no documentation of what the value method actually returns, just that it returns something that is a constexpr. Does value(integral
)) return int, or does it return integral ?
It returns the underlying value of `integral
Perhaps the Constant type class should be "parameterized" by a type specifying the return type of value.
Might be a good idea; I'll think about how this would interact with the rest of the library.
Integral data type: -------------------
It would be better not to duplicate std::integral_constant. However, I recognize that the overloaded operators are useful and you wouldn't be able to do this with std::integral_constant (well, I suppose you still could, but as they wouldn't be found by ADL, they wouldn't be terribly useful). This rationale (assuming it is correct) should be documented though.
This is part of the rationale. There's also the fact that integral_constant
requires including
Logical type class: -------------------
What is really gained by making this a type class at all?
Yes; how would you branch at compile-time otherwise? The if_ method for e.g. Integrals is not equivalent to a normal C++ if-statement; it allows both branches to have different and incompatible types. Also, since we want to be able to branch on `integral`s but also on `mpl::integral_c`s, we need a type class.
It seems that and_ and or_ require that all arguments be of the same "hana datatype", though actually this isn't documented anywhere and this might not even be the precise requirement (but obviously it needs to be documented very explicitly, and this probably applies to almost every method in Hana).
Really we really should be able to mix multiple data types in a call to or_ or and_, though. Otherwise the usability is unreasonably limited. Of course there is then the question of return type. However, I would assume most users would be happy getting back either a Hana Integral or a bool, depending on whether it is a compile-time or runtime condition. Certainly this would be better than getting back a compiler error.
The arguments of `and_` and `or_` only need to be Logicals. I'll document it.
Another question: how does the performance of and_ and or_ compare to the optimized verions you showed in your MPL11 talk?
I haven't benchmarked that yet, but I expect it to be significantly less efficient. I'll have to think about a way of specifying the requirements of `Logical` in a way that makes optimizations possible, which is not the case currently.
Pair datatype: --------------
Why duplicate std::pair?
For the same reason as `std::integral_constant`; operators.
Alternatively, why duplicate list?
Perhaps list should just define first and second as well?
Product type class: ----------------
Why isn't a 2-element List, a 2-element std::tuple, etc. an instance?
`hana::list`, `std::tuple` and friends can't be made an instance of the `Product` type class because they would not satisfy its laws.
List type class: ----------------
Just as for Logical, it would be useful to be able to mix datatypes in calls to concat, zip, zip_with.
I agree, but it would then be harder to implement those operations efficiently, since you would not control the representation of all the arguments. I'll think about a way of supporting different data types while still allowing optimizations.
The list function itself shouldn't be documented as part of the type class, since it isn't logically part of it.
That's because `List` was both a data type and a type class. In my local version, `List` is only a type class and the data type is named `Tuple` instead, which makes this a non-issue.
What about get<N>, get
accessors? I understand these can be defined in terms of the other methods, but clearly they are useful on their own.
For `get<N>`, I'll provide an helper method `at_c<N>(iterable)`. I don't
understand what you mean by `get
Louis Dionne
Sorry for the slow answer; I completely missed your message.
No problem.
Jeremy Maitin-Shepard
writes: Integral data type: -------------------
It would be better not to duplicate std::integral_constant. However, I recognize that the overloaded operators are useful and you wouldn't be able to do this with std::integral_constant (well, I suppose you still could, but as they wouldn't be found by ADL, they wouldn't be terribly useful). This rationale (assuming it is correct) should be documented though.
This is part of the rationale. There's also the fact that integral_constant requires including
, which is about the size of the whole library; I find this annoying. I'll document that.
I didn't think about that. It seems to me though that most headers that do metaprogramming will also require type_traits anyway. Still, it is great to minimize dependencies when possible.
Logical type class: -------------------
What is really gained by making this a type class at all?
Yes; how would you branch at compile-time otherwise? The if_ method for e.g. Integrals is not equivalent to a normal C++ if-statement; it allows both branches to have different and incompatible types. Also, since we want to be able to branch on `integral`s but also on `mpl::integral_c`s, we need a type class.
As I see it, there are two types of Logical: compile-time and run-time. Both cases have to be handled separately, and there needs to be a way to figure out which type of logical a given argument is, but that is it. The implementation of eval_if will be the same, modulo some change of names, for hana integral, mpl::integral_c, and std::integral_constant.
It seems that and_ and or_ require that all arguments be of the same "hana datatype", though actually this isn't documented anywhere and this might not even be the precise requirement (but obviously it needs to be documented very explicitly, and this probably applies to almost every method in Hana).
Really we really should be able to mix multiple data types in a call to or_ or and_, though. Otherwise the usability is unreasonably limited. Of course there is then the question of return type. However, I would assume most users would be happy getting back either a Hana Integral or a bool, depending on whether it is a compile-time or runtime condition. Certainly this would be better than getting back a compiler error.
The arguments of `and_` and `or_` only need to be Logicals. I'll document it.
Another question: how does the performance of and_ and or_ compare to the optimized verions you showed in your MPL11 talk?
I haven't benchmarked that yet, but I expect it to be significantly less efficient. I'll have to think about a way of specifying the requirements of `Logical` in a way that makes optimizations possible, which is not the case currently.
Maybe and_ and or_ could just be optimized for hana integral_c. Since the goal is efficiency it seems it may be hard to have it more generic.
Pair datatype: --------------
Why duplicate std::pair?
For the same reason as `std::integral_constant`; operators.
Which operators? I don't see any documented. There are the first and second methods which perhaps can be found by ADL, but that hardly seems like a reasonable justification for making another pair type.
Alternatively, why duplicate list?
Perhaps list should just define first and second as well?
Product type class: ----------------
Why isn't a 2-element List, a 2-element std::tuple, etc. an instance?
`hana::list`, `std::tuple` and friends can't be made an instance of the `Product` type class because they would not satisfy its laws.
From my reading of the description of the Product type class laws, the
problem you are referring to is the uniqueness requirement on the function "make": for a tuple, you could add more than two elements, with the extra elements containing arbitrary unused values. However, I don't see any practical reason why it is useful to have a separate Product type from a Tuple type. Potentially, you could document that first and second are only valid for 2-element tuples, and make them fail for tuples with more than 2 elements. However, particularly since we are just talking about metaprogramming, it seems letting them work for any tuple would be better.
List type class: ----------------
Just as for Logical, it would be useful to be able to mix datatypes in calls to concat, zip, zip_with.
I agree, but it would then be harder to implement those operations efficiently, since you would not control the representation of all the arguments. I'll think about a way of supporting different data types while still allowing optimizations.
Okay.
The list function itself shouldn't be documented as part of the type class, since it isn't logically part of it.
That's because `List` was both a data type and a type class. In my local version, `List` is only a type class and the data type is named `Tuple` instead, which makes this a non-issue.
Okay, great.
What about get<N>, get
accessors? I understand these can be defined in terms of the other methods, but clearly they are useful on their own. For `get<N>`, I'll provide an helper method `at_c<N>(iterable)`. I don't understand what you mean by `get
` though. What does `get ` do?
Perhaps get_range would be a better name. The idea would be to return a
sequence (get<N>, get
On 20/08/2014 12:36, Jeremy Maitin-Shepard wrote:
Why isn't a 2-element List, a 2-element std::tuple, etc. an instance?
`hana::list`, `std::tuple` and friends can't be made an instance of the `Product` type class because they would not satisfy its laws.
From my reading of the description of the Product type class laws, the problem you are referring to is the uniqueness requirement on the function "make": for a tuple, you could add more than two elements, with the extra elements containing arbitrary unused values.
However, I don't see any practical reason why it is useful to have a separate Product type from a Tuple type. Potentially, you could document that first and second are only valid for 2-element tuples, and make them fail for tuples with more than 2 elements. However, particularly since we are just talking about metaprogramming, it seems letting them work for any tuple would be better.
I might be taking something out of context here, but if you want to have arbitrary-length tuples, I think it's usually more typical to define head and tail operations: tuple<>::head => undefined tuple<A>::head => A tuple::head => A tuple<>::tail => tuple<> tuple<A>::tail => tuple<> tuple::tail => tuple<B> tuple::tail => tuple "first" then becomes "head" and "second" becomes "tail::head".
Jeremy Maitin-Shepard
[...]
This is part of the rationale. There's also the fact that integral_constant requires including
, which is about the size of the whole library; I find this annoying. I'll document that. I didn't think about that. It seems to me though that most headers that do metaprogramming will also require type_traits anyway. Still, it is great to minimize dependencies when possible.
It might be true on the user side, but it isn't on the library's. Very few
Hana headers need type traits to function, and when they do they usually need
only one or two type traits, not the whole package -- hence the small
Logical type class: -------------------
What is really gained by making this a type class at all?
Yes; how would you branch at compile-time otherwise? The if_ method for e.g. Integrals is not equivalent to a normal C++ if-statement; it allows both branches to have different and incompatible types. Also, since we want to be able to branch on `integral`s but also on `mpl::integral_c`s, we need a type class.
As I see it, there are two types of Logical: compile-time and run-time. Both cases have to be handled separately, and there needs to be a way to figure out which type of logical a given argument is, but that is it. The implementation of eval_if will be the same, modulo some change of names, for hana integral, mpl::integral_c, and std::integral_constant.
I think I'm not getting your point. What you say is true, but it seems to me that this is a good use case for type classes -- many types sharing the same interface with (even only slightly) different implementations. That being said, I've experimented locally with implementing laziness and branching -- which are awfully related -- through comonads. Basically, I think comonads might provide an abstract way of representing what is a "branch" in a usual strict programming language. I'm not giving more details because I haven't figured it out completely yet, but if that works out then the Logical type class would probably change quite a bit. In other words; I think we need a type class to represent conditional branching, but I am unsatisfied too with the current solution.
[...]
Maybe and_ and or_ could just be optimized for hana integral_c. Since the goal is efficiency it seems it may be hard to have it more generic.
I don't see an easy way of optimizing and_ and or_ for hana::integral, since
they take variadic arguments. We would need to know that all the arguments are
hana::integrals, which is a bit costly to determine. Furthermore, the logical
operations I presented at C++Now were not short-circuitting, and we need and_
and or_ to short-circuit.
That being said, if you want an efficient non short-circuitting logical and_,
instead of
// MPL11 (roughly as shown at C++Now)
template
Pair datatype: --------------
Why duplicate std::pair?
For the same reason as `std::integral_constant`; operators.
Which operators? I don't see any documented. There are the first and second methods which perhaps can be found by ADL, but that hardly seems like a reasonable justification for making another pair type.
You can compare pairs using `==` and `!=`. If their elements are compile-time comparable, you get back a `hana::bool_<>` instead of a straight `bool`. Hence, the `==` for `std::pair` and `hana::pair` is not the same, and we need to provide our own pair if we want to make this possible. The operators supported by each data type will be documented.
Alternatively, why duplicate list?
Perhaps list should just define first and second as well?
Product type class: ----------------
Why isn't a 2-element List, a 2-element std::tuple, etc. an instance?
`hana::list`, `std::tuple` and friends can't be made an instance of the `Product` type class because they would not satisfy its laws.
From my reading of the description of the Product type class laws, the problem you are referring to is the uniqueness requirement on the function "make": for a tuple, you could add more than two elements, with the extra elements containing arbitrary unused values.
Exactly.
However, I don't see any practical reason why it is useful to have a separate Product type from a Tuple type. Potentially, you could document that first and second are only valid for 2-element tuples, and make them fail for tuples with more than 2 elements. However, particularly since we are just talking about metaprogramming, it seems letting them work for any tuple would be better.
There are a couple of reasons why I think Product should stay as it is: 1. If there was no Product type class, how would you interact with std::pair? It sure does not make sense to make std::pair an Iterable; how would you implement `tail`? 2. The current Product type class does not provide any instances besides Comparable. The uniqueness requirement makes it possible to provide many more useful instances. What you propose here is to make `first` and `second` methods of List or Iterable. However, the instances that we can provide for Products might not be the same as those for List or Iterable. In other words, it is useful to limit the "power" of the Product type class because it gives us stronger guarantees on its instances. 3. If we provide `first` and `second` for Iterables, why not provide `third`? 4. Product represents a category-theoretical product, which is the most general way of representing a "std::pair". I find this a neat abstraction, but of course you could argue that this is subjective.
[...]
What about get<N>, get
accessors? I understand these can be defined in terms of the other methods, but clearly they are useful on their own. For `get<N>`, I'll provide an helper method `at_c<N>(iterable)`. I don't understand what you mean by `get
` though. What does `get ` do? Perhaps get_range would be a better name. The idea would be to return a sequence (get<N>, get
, ..., get<M-1>).
Oh, now I see. I would call it `slice(sequence, from, to)`, and I would
provide a `slice_c
participants (3)
-
Gavin Lambert
-
Jeremy Maitin-Shepard
-
Louis Dionne