Boost logo

Boost :

Subject: Re: [boost] [variant] match()
From: Matt Calabrese (rivorus_at_[hidden])
Date: 2015-01-09 13:35:21


On Fri, Jan 9, 2015 at 9:52 AM, Eelis <eelis_at_[hidden]> wrote:

> On 2015-01-09 10:06, Antony Polukhin wrote:
>
>> I like the idea of `overload` more than the `match` idea.
>>
>
> I see many people prefer going through apply_visitor() with overload().
> Coming from a functional programming perspective, this is kinda backwards.
>

I disagree completely. Please explain what you consider apply_visitor and
overload to be "backwards"? All apply_visitor (or apply, or visit, or
dispatch, or whatever the name) is is a high order function that takes a
function object and invokes it with the trailing arguments upon
transformation. There is nothing at all backwards about this from a
functional programming perspective or any perspective.

> What we need is sum types. C++ unions are sort of sum types, but they're
> crap, so variant<> is our C++ workaround.
>

While I agree that they are important, I would hardly call variant a
"workaround" to not having language-level discriminated sum types, it's
just a library implementation of that kind of sum type. In general, I am of
the strong opinion that if a type can be efficiently implemented either as
a library component or as a language feature, all else being equal, one
should prefer it as a library component. In the case of something like
variant, there are some benefits that can come from language support, but
there are also some trade-offs with respect to a C++ implementation that,
IMO, are not really a good idea to try to standardize at the core language
level. Even if we had them, I have little doubt that there would still
exist libraries that make alternative trade-offs anyway.

However, somewhere along the line it was decided that variant<> was to be
> accessed /by type/ (either through get<T>() or apply_visitor()), which
> means that variant<T, T> does not work well at all.
>

That's not why a variant has no repeated types. It is also not expected to
be the only sum type abstraction is applicable to all situations.

> This means variant<> fails to be the building block that sum types
> can/should be

Not true. It is trivial to build a generalized discriminated union on top
of variant. All you do is have the discriminated_union<T0, T1, T2, ...>
template contain a variant<wrapper<T0, 0>, wrapper<T1, 1>, wrapper<T2,
2>...>. It is also trivial to go the other way around -- build a variant on
top of discriminated_union. That said, in practice, I've personally found
it best to build them both from scratch on top of lower-level primitives
and to have them both model the same concept, allowing each to be visited
in the same way.

 FWIW I have my own library implementation of, and was planning to propose
for standardization:

// Non-discriminated, variadic
template<class...>
class union_;

// Non-discriminated, variadic, and guarantees destructibility
template<class...>
class descructible_union;

// The type that you want
template<class...>
class discriminated_union;

// Similar to boost
template<class...>
class variant;

> Really, variant<> should have been index-based all along, so that you can
> just do get<0> and get<1> on a variant<T, T> (or a variant<A, B> where A
> might be B) without losing information. Analogously, match(v, [](T){},
> [](T){}) on such a variant would Just Work (again, without losing
> information), while apply_visitor() with overload() breaks down.
>

You seem to be not getting that discriminated_union and variant each are
used in different scenarios. One quick scenario for where you want a
variant is:

// Holds one of these collidable shapes.
variant<circle, square, triangle> a, b;

struct in_collision_t {
    bool operator()(circle, circle) const;
    bool operator()(circle, square) const;
    bool operator()(circle, triangle) const;
    // etc.
} constexpr in_collision{};

// Use it when you don't have variants
const bool collide = in_collision( circle{}, square{} );

// Use it when you do have variants
const bool collide_polymorphic = apply( in_collision, a, b );

I use this type of functionality a lot. Much more so, actually, than a
general discriminated_union. IMO, both are very useful abstractions to
have, but it would be in error to say that one is "better" than the other.
They are simply different abstractions that are implemented in a similar
manner.

-- 
-Matt Calabrese

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