Variant and visitation (was: [optional] Safe optional)
On 18 November 2014 15:02, Matt Calabrese
For a while I assumed exactly that, but he actually specifically voiced that he does not like visitation on variant and thinks of it as a hack. It's sad :/
FWIW: I think he is (a) correct, but (b) we have nothing better to replace it, so we still need it.
We should really start a thread about this. I'm very curious to see an actual objective rationale both for why visitation over a closed set of types known at compile time is in any way a "hack,"
It's the inversion of control that people just don't like.
along with a realistic alternative.
As I already said, I don't know of one, and having it is better than not having it. The discussion in Urbana on n4218 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4218.pdf (variant), which didn't propose visitation, basically amounted to we don't like visitation but we need it unless something better comes along.
It really is a fundamental operation of a discriminated union. As someone who uses variants pretty much as the "default" for run-time polymorphism in day-to-day coding, I have never seen an actual explanation for what someone might consider bad about variant visitation, neither from Bjarne nor anyone else.
While I don't want to speak for Bjarne, I believe he would rather have some form of Pattern Matching http://www.stroustrup.com/OpenPatternMatching.pdf and possibly variant as a language feature. That being said, no one has proposed it. -- Nevin ":-)" Liber mailto:nevin@eviloverlord.com (847) 691-1404
2014-11-19 0:40 GMT+03:00 Nevin Liber
On 18 November 2014 15:02, Matt Calabrese
wrote: For a while I assumed exactly that, but he actually specifically
voiced
that he does not like visitation on variant and thinks of it as a hack. It's sad :/
FWIW: I think he is (a) correct, but (b) we have nothing better to replace it, so we still need it.
We should really start a thread about this. I'm very curious to see an actual objective rationale both for why visitation over a closed set of types known at compile time is in any way a "hack,"
It's the inversion of control that people just don't like.
As a guy, who's been keeping an eye on Boost.Variant for last two years, I was planning to add support for generalized lambdas as a visitors for variant: apply_visitor( [](auto v){ std::cout << v; }, variant_variable ); This looks more friendly that defining a visitor structure, so maybe users will like it better. Here's another example auto sum = apply_visitor( [](auto v1, auto v2) { return v1 + v2; }, variant_variable1, variant_variable2 );
along with a realistic alternative.
As I already said, I don't know of one, and having it is better than not having it.
The discussion in Urbana on n4218 http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4218.pdf (variant), which didn't propose visitation, basically amounted to we don't like visitation but we need it unless something better comes along.
Hmmm, I see that paper for the first time. I like it but there are a few issues that must be clarified and fixed. Was there a discussion of that paper at std-proposals? -- Best regards, Antony Polukhin
On 18 Nov 2014, at 22:59, Antony Polukhin
2014-11-19 0:40 GMT+03:00 Nevin Liber
: It's the inversion of control that people just don't like.
As a guy, who's been keeping an eye on Boost.Variant for last two years, I was planning to add support for generalized lambdas as a visitors for variant:
apply_visitor( [](auto v){ std::cout << v; }, variant_variable );
Alternatively, it could be done like this: apply_visitor(variant_variable, [](int i) { std::cout << “int: “ << i; }, [](float f) { std::cout << “float: “ << f; }, otherwise([](auto v) { std::cout << “something: “ << v; }); I’ve recently written something very similar for boost::any, using runtime checks instead of course. Sebastian
Le 18/11/14 23:52, Sebastian Redl a écrit :
On 18 Nov 2014, at 22:59, Antony Polukhin
wrote: 2014-11-19 0:40 GMT+03:00 Nevin Liber
: It's the inversion of control that people just don't like.
As a guy, who's been keeping an eye on Boost.Variant for last two years, I was planning to add support for generalized lambdas as a visitors for variant:
apply_visitor( [](auto v){ std::cout << v; }, variant_variable ); Alternatively, it could be done like this:
apply_visitor(variant_variable, [](int i) { std::cout << "int: " << i; }, [](float f) { std::cout << "float: " << f; }, otherwise([](auto v) { std::cout << "something: " << v; });
I've recently written something very similar for boost::any, using runtime checks instead of course.
I have been playing with make_overload/match (See
https://github.com/viboes/tags/blob/master/test/any/include_pass.cpp#L259)
boost::any a = 2;
boost::any b = std::string("2");
functional::match_all<void>(std::make_tuple(select
On Tue, Nov 18, 2014 at 2:52 PM, Sebastian Redl < sebastian.redl@getdesigned.at> wrote:
On 18 Nov 2014, at 22:59, Antony Polukhin
wrote: 2014-11-19 0:40 GMT+03:00 Nevin Liber
: It's the inversion of control that people just don't like.
As a guy, who's been keeping an eye on Boost.Variant for last two years, I was planning to add support for generalized lambdas as a visitors for variant:
apply_visitor( [](auto v){ std::cout << v; }, variant_variable );
Alternatively, it could be done like this:
apply_visitor(variant_variable, [](int i) { std::cout << “int: “ << i; }, [](float f) { std::cout << “float: “ << f; }, otherwise([](auto v) { std::cout << “something: “ << v; });
I’ve recently written something very similar for boost::any, using runtime checks instead of course.
The problem with that is it doesn't generalize easily to n-ary visitation. I have a separate variant and visitation implementation that I've been planning to propose for standardization, but I don't know if it will happen especially since others are on their way. My approach is: // Called dispatch because of negative connotations of visitor dispatch( overloads( []( int,some_type, float ) {}, []( auto, auto, auto {} ), a_variant, pass_through( not_a_variant ), another_variant ); All "overloads" does is form an overload set via base-class chaining and bringing in operator() with "using" (appropriately wraps function pointers so they work as well). Return types are deduced by a slightly modified common_type mechanism. -- -Matt Calabrese
Le 19/11/14 00:06, Matt Calabrese a écrit :
On Tue, Nov 18, 2014 at 2:52 PM, Sebastian Redl < sebastian.redl@getdesigned.at> wrote:
On 18 Nov 2014, at 22:59, Antony Polukhin
wrote: 2014-11-19 0:40 GMT+03:00 Nevin Liber
: It's the inversion of control that people just don't like.
As a guy, who's been keeping an eye on Boost.Variant for last two years, I was planning to add support for generalized lambdas as a visitors for variant:
apply_visitor( [](auto v){ std::cout << v; }, variant_variable ); Alternatively, it could be done like this:
apply_visitor(variant_variable, [](int i) { std::cout << “int: “ << i; }, [](float f) { std::cout << “float: “ << f; }, otherwise([](auto v) { std::cout << “something: “ << v; });
I’ve recently written something very similar for boost::any, using runtime checks instead of course.
The problem with that is it doesn't generalize easily to n-ary visitation.
I have a separate variant and visitation implementation that I've been planning to propose for standardization, but I don't know if it will happen especially since others are on their way. My approach is:
// Called dispatch because of negative connotations of visitor dispatch( overloads( []( int,some_type, float ) {}, []( auto, auto, auto {} ), a_variant, pass_through( not_a_variant ), another_variant ); I'm wondering what should be the result of this function, a variant? or should all the overloads return the same type? This function is close to Haskell fmap/bind.
If variant can contain a non-a-value, we need to determine what is the result when there is not a value for some of the parameters and so the result should be a variant. I know that boost::variant contains always a value, but we can have the none_t as one of them.
All "overloads" does is form an overload set via base-class chaining and bringing in operator() with "using" (appropriately wraps function pointers so they work as well). Return types are deduced by a slightly modified common_type mechanism.
I should be read the post completely. I like the common_type approach. An alternative is to return a variant of the results.You could always convert a variant to the common_type it it exists. Vicente
On Tue, Nov 18, 2014 at 3:39 PM, Vicente J. Botet Escriba < vicente.botet@wanadoo.fr> wrote:
// Called dispatch because of negative connotations of visitor
dispatch( overloads( []( int,some_type, float ) {}, []( auto, auto, auto {} ), a_variant, pass_through( not_a_variant ), another_variant );
I'm wondering what should be the result of this function, a variant? or should all the overloads return the same type? This function is close to Haskell fmap/bind.
It depends on the situation. I return a common_type-like deduction that's reference-aware, but some cases you want it to return a discriminated union, and other cases you want it to just be a user specified result. It's very usage-dependent.
If variant can contain a non-a-value, we need to determine what is the result when there is not a value for some of the parameters and so the result should be a variant. I know that boost::variant contains always a value, but we can have the none_t as one of them.
IMO boost is correct with the never empty guarantee and a standard version would be wise to follow suit. If you put in a type that you happen to treat as a null type for your specific case, then that's fine. It's nothing special in that sense apart from how personally interpret it. Visitation simply passes it along as one would expect. I should be read the post completely. I like the common_type approach. An
alternative is to return a variant of the results.You could always convert a variant to the common_type it it exists.
The only problem with a default variant-as-result approach is that in practice I've personally found that it's not as frequently useful (I know someone else on this list who has use-cases for the variant return though -- perhaps he'll respond). As well, if you get back a variant/discriminated union of the result types, collapsing that result in a separate step effectively requires another visitation. Ultimately the user should specify the kind of deduction they want or provide an explicit result type at the time they perform the visitation. I just find common_type to be most common for my personal needs. -- -Matt Calabrese
Cool, thanks for starting this thread.
On Tue, Nov 18, 2014 at 1:40 PM, Nevin Liber
It's the inversion of control that people just don't like.
Perhaps harsh, but this just sounds like a general dislike of high-order functions, which is somewhat absurd to me. How is it any more problematic from simply dispatching a function over fully known types at compile time, or a high-order function that takes a function object and n arguments, then forwards them along to the function object /without/ visitation? All variant visitation logically does extra is transform the arguments based on their discriminator before passing them along. Perhaps to make things more clear, what if instead of type transformations of variant to underlying type they were simply value transformations. In other words, would you have a problem with a high order function that takes a function object and 10 ints, but it incremented each int by 5 before passing each one along to the other function object? Would there be a problem with this? What if instead of incrementing by 5, it cast each argument to float? If people don't have a problem with any of these situations I simply do not see why they'd have a problem with something like apply_visitor. Further, visitation of a variant gives you /exactly/ the same overload resolution that you'd get for the underlying type of the variant type(s) being known at compile time (I.E. ordering, template pattern matching, etc.). Because of that, it is exactly as capable as compile-time overload resolution, plain and simple. It has no more or fewer problems in terms of capability with respect to basic overload resolution because that's exactly how it is implemented. I wouldn't consider it lacking unless it is also considered that basic overload resolution is lacking. While /that/ may be true, it doesn't make sense to expect visitation to change that (nor should it, IMO). People understand basic overload resolution so they understand the matching that takes place when doing visitation. All that said, I do agree with problems of visitation when you do not have a closed set of types, but that simply is not the case with a variant, which is why it can be as powerful as it is in practice. Visitation of a variant really does do exactly what the user wants (and nothing more). It's also telling that the visitation is implemented in essentially the same way that someone would manually implement it if the abstraction were not there. Anyway, I've heard this argument before, and I just don't understand it. As someone who really does use variants every single day, I have not understood the criticisms at all. It just sounds as though /valid/ criticisms of visitation in something like a class hierarchy are now being erroneously applied to visitation in the context of a discriminated union, which just doesn't really make sense. We're not talking about visitation into a class hierarchy or visitation into an "any." The discussion in Urbana on n4218
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4218.pdf (variant), which didn't propose visitation, basically amounted to we don't like visitation but we need it unless something better comes along.
It really is a fundamental operation of a discriminated union. As someone who uses variants pretty much as the "default" for run-time polymorphism in day-to-day coding, I have never seen an actual explanation for what someone might consider bad about variant visitation, neither from Bjarne nor anyone else.
While I don't want to speak for Bjarne, I believe he would rather have some form of Pattern Matching < http://www.stroustrup.com/OpenPatternMatching.pdf> and possibly variant as a language feature. That being said, no one has proposed it.
I've read that paper before and will read it again tonight, but briefly IIRC it was concerned with open sets of types. If you applied the approach to variants all you'd get is a less efficient solution than visitation and I simply don't buy the gains. Again, I'll go back and reread the paper as I don't want to be too critical without it fresh in my mind, but IMO it just did not apply to discriminated unions, which is what we have with a variant. -- -Matt Calabrese
Le 18/11/14 23:59, Matt Calabrese a écrit :
Cool, thanks for starting this thread.
On Tue, Nov 18, 2014 at 1:40 PM, Nevin Liber
wrote: It's the inversion of control that people just don't like.
Perhaps harsh, but this just sounds like a general dislike of high-order functions, which is somewhat absurd to me. How is it any more problematic from simply dispatching a function over fully known types at compile time, or a high-order function that takes a function object and n arguments, then forwards them along to the function object /without/ visitation? All variant visitation logically does extra is transform the arguments based on their discriminator before passing them along.
I think this is the same complain we have often about future.then respect to await expressions. People want to write their code on line. Even if lambdas provide something better than functions there is yet too much noise. In Haskell there is the do-notation to cover the same intent. Haskell has already a match on types and there is no need for visitors. I hope C++ will add Sum types and Pattern matching soon. Independently of this features, I think that we can have a more generic visitation function that will be quite close to pattern matching on types.
Perhaps to make things more clear, what if instead of type transformations of variant to underlying type they were simply value transformations. In other words, would you have a problem with a high order function that takes a function object and 10 ints, but it incremented each int by 5 before passing each one along to the other function object? Would there be a problem with this? What if instead of incrementing by 5, it cast each argument to float? If people don't have a problem with any of these situations I simply do not see why they'd have a problem with something like apply_visitor.
I haven't :)
Further, visitation of a variant gives you /exactly/ the same overload resolution that you'd get for the underlying type of the variant type(s) being known at compile time (I.E. ordering, template pattern matching, etc.). Because of that, it is exactly as capable as compile-time overload resolution, plain and simple. It has no more or fewer problems in terms of capability with respect to basic overload resolution because that's exactly how it is implemented. I wouldn't consider it lacking unless it is also considered that basic overload resolution is lacking. While /that/ may be true, it doesn't make sense to expect visitation to change that (nor should it, IMO). People understand basic overload resolution so they understand the matching that takes place when doing visitation.
Agreed.
All that said, I do agree with problems of visitation when you do not have a closed set of types, but that simply is not the case with a variant, which is why it can be as powerful as it is in practice. Visitation of a variant really does do exactly what the user wants (and nothing more). It's also telling that the visitation is implemented in essentially the same way that someone would manually implement it if the abstraction were not there.
Anyway, I've heard this argument before, and I just don't understand it. As someone who really does use variants every single day, I have not understood the criticisms at all. It just sounds as though /valid/ criticisms of visitation in something like a class hierarchy are now being erroneously applied to visitation in the context of a discriminated union, which just doesn't really make sense. We're not talking about visitation into a class hierarchy or visitation into an "any."
Visitation in any is more expensive, but I believe it is a better alternative to any_cast, when you know that you can have one of N types.
The discussion in Urbana on n4218
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n4218.pdf (variant), which didn't propose visitation, basically amounted to we don't like visitation but we need it unless something better comes along.
It really is a fundamental operation of a discriminated union. As someone who uses variants pretty much as the "default" for run-time polymorphism in day-to-day coding, I have never seen an actual explanation for what someone might consider bad about variant visitation, neither from Bjarne nor anyone else.
While I don't want to speak for Bjarne, I believe he would rather have some form of Pattern Matching < http://www.stroustrup.com/OpenPatternMatching.pdf> and possibly variant as a language feature. That being said, no one has proposed it.
I've read that paper before and will read it again tonight, but briefly IIRC it was concerned with open sets of types. If you applied the approach to variants all you'd get is a less efficient solution than visitation and I simply don't buy the gains.
Again, I'll go back and reread the paper as I don't want to be too critical without it fresh in my mind, but IMO it just did not apply to discriminated unions, which is what we have with a variant.
I don't know where Dr. BS wants to go, but if we have the possibility on the language to define Sum types (variant), pattern matching should be optimized for them. I think Sum types are the missing on the trilogy + Product types (tuple/struct) + Sum types (variant/union) + closures/lambda functions Oh, I forgot, I like also inheritance ;-) Best, Vicente
participants (5)
-
Antony Polukhin
-
Matt Calabrese
-
Nevin Liber
-
Sebastian Redl
-
Vicente J. Botet Escriba