[range][algorithms] How about making the range algorithms pipeable as the range adaptors are?
It would be syntactically really nice if the stl-algorithms wrapped in
boost range were pipeable, as with the adaptors, making it seamless to
combine them in left-to-right statements
For non-mutating algorithms I think it's an obvious evolvement
const auto& max_pos = some_range | filtered(..) | max_element;
const auto& num_elements = some_range | filtered(..) | count(42);
...instead of inside out as it is now
const auto& max_pos = max_element(some_range | filtered(..));
const auto& num_elements = count(some_range | filtered(..), 42);
For non-mutating algorithms it might not be an obvious solution, as the
range beneath is modified, but I still think it should be possible
boost::erase(my_vector | sort | unique);
...instead of
boost::erase(unique(sort(my_vector));
*Implementation example with std::accumulate:*
// Tag to be recognized by the global pipe operatortemplate <typename
value_type>struct accumulate_argument_tag {
accumulate_argument_tag(const value_type& value) : value_(value) {}
const value_type& value_;
};
struct accumulate_tag {
// Put the regular algorithm as an operator() function where the
first argument is the range
template
On 19-09-2013 18:17, Viktor Sehr wrote:
It would be syntactically really nice if the stl-algorithms wrapped in boost range were pipeable, as with the adaptors, making it seamless to combine them in left-to-right statements
For non-mutating algorithms I think it's an obvious evolvement const auto& max_pos = some_range | filtered(..) | max_element; const auto& num_elements = some_range | filtered(..) | count(42);
...instead of inside out as it is now const auto& max_pos = max_element(some_range | filtered(..)); const auto& num_elements = count(some_range | filtered(..), 42);
The pipe syntax usually just change the iterator type. If we want to chain algorithms in a facy way, I think a new syntax should be used to minimize confusion between the two concepts. Say const auto& max_pos = some_range | filtered(..) >> max_element could have been nice. But then we run into precedence problems. Maybe >>= would do. -Thorsten
On Thu, Sep 19, 2013 at 6:03 PM, Thorsten Ottosen < thorsten.ottosen@dezide.com> wrote:
On 19-09-2013 18:17, Viktor Sehr wrote:
It would be syntactically really nice if the stl-algorithms wrapped in boost range were pipeable, as with the adaptors, making it seamless to combine them in left-to-right statements
For non-mutating algorithms I think it's an obvious evolvement const auto& max_pos = some_range | filtered(..) | max_element; const auto& num_elements = some_range | filtered(..) | count(42);
...instead of inside out as it is now const auto& max_pos = max_element(some_range | filtered(..)); const auto& num_elements = count(some_range | filtered(..), 42);
The pipe syntax usually just change the iterator type. If we want to chain algorithms in a facy way, I think a new syntax should be used to minimize confusion between the two concepts. Say
const auto& max_pos = some_range | filtered(..) >> max_element
could have been nice. But then we run into precedence problems. Maybe >>= would do.
IMHO the pipe syntax should just be a shorthand for (left associative) function application, i.e.: x | f should simply be a equivalent to f(x) This way there would be no fear of confusing concepts and it would be of more general application and would interact in a powerful way with partial application (i.e. bind). I think that $ would be the haskell equivalent. -- gpd
Giovanni Piero Deretta wrote:
On Thu, Sep 19, 2013 at 6:03 PM, Thorsten Ottosen < thorsten.ottosen@dezide.com> wrote:
IMHO the pipe syntax should just be a shorthand for (left associative) function application, i.e.:
x | f
should simply be a equivalent to
f(x)
This way there would be no fear of confusing concepts and it would be of more general application and would interact in a powerful way with partial application (i.e. bind).
+1 - I would like to see it as a utility library for composing unary function objects whose arguments and return types match up. It's to interesting and useful to be part of boost range. Taking it out of boost.range would also simplify boost range. Robert Ramey .
IMHO the pipe syntax should just be a shorthand for (left associative) function application, i.e.:
x | f
should simply be a equivalent to
f(x)
+1 -
I would like to see it as a utility library for composing unary function objects whose arguments and return types match up. It's to interesting and useful to be part of boost range. Taking it out of boost.range would also simplify boost range.
Is it possible to implement this in general without having to write a wrapper for each function to be used in this way? I don't think so because operator overloading requires at least one of the arguments to be user-defined, and x could be a built-in type and f a plain function pointer. I suppose we could have a utility library that defines a macro to generate the wrapper, but then libraries like Boost.Range would still need to provide code that calls this macro. Regards, Nate
"I think a new syntax should be used to minimize confusion between the two concepts" IMHO the pipe syntax should just be a shorthand for (left associative) function application, i.e.: x | f should simply be a equivalent to f(x)
Actually what I really want is a global pipe operator which works like that, I just aimed for boost::range::algorithms because range::adaptors already utilized the syntax.
I suppose we could have a utility library that defines a macro to generate the wrapper You mean a macro like: "MAKE_PIPEABLE(boost::accumulate, accumulated);"?
Regard /Viktor
I wonder if it's possible to make a wrapper which doesn't require the pipeable function name to be different, or in a separate namespace, compared to the original function. I can't figure out myself how to make such a wrapper.
I wonder if it's possible to make a wrapper which doesn't require the pipeable function name to be different, or in a separate namespace, compared to the original function. I can't figure out myself how to make such a wrapper.
Yes, its possible to have a wrapper that makes the function pipable in a way that the function can be piped or called normally without piping it in. In PStade Egg it was called [ambi] (http://p-stade.sourceforge.net/boost/libs/egg/doc/html/boost_egg/function_ad...), but you have to specify the number of parameters of the function. Using the callable technique from Eric Niebler, we can actually detect arity even in C++03. So a general purpose pipable_adaptor is very possible. I created one for a library I am writing, you can see the source code for pipable here: https://github.com/pfultz2/Zen/blob/master/zen/function/pipable.h Paul
In PStade Egg it was called [ambi] ( http://p-stade.sourceforge.net/boost/libs/egg/doc/html/boost_egg/function_ad... ), but you have to specify the number of parameters of the function.
But ambiN only works with function objects, or am I wrong?
I created one for a library I am writing, you can see the source code for pipable here: https://github.com/pfultz2/Zen/blob/master/zen/function/pipable.h
In the example at the top the syntax seem different from the test in the bottom of the file, how how you specify a pipeable wrapper for, for example boost::range::accumulate? /Viktor
In PStade Egg it was called [ambi] (
http://p-stade.sourceforge.net/boost/libs/egg/doc/html/boost_egg/function_ad...
), but you have to specify the number of parameters of the function.
But ambiN only works with function objects, or am I wrong?
Yes, but function objects is a much better way to declare polymorphic functions(unless they require an explicit template parameter).
I created one for a library I am writing, you can see the source code for pipable here: https://github.com/pfultz2/Zen/blob/master/zen/function/pipable.h
In the example at the top the syntax seem different from the test in the bottom of the file,
The example is showing how to make a function pipable in place, and the test
at the bottom is testing its use with `static_`.(of course, I just realized
there is a mistake in the example) I probably should add an in place test as
well. The example doesn't use `static_` to help keep the example simple
without having to figure what `static_` does in order figure out what pipable
does. However, using `static_` is more likely how it will be used in practice.
For example, say we define our sum function object, like this:
struct sum_f
{
template<class T>
T operator()(T x, T y) const
{
return x+y;
}
};
If we want to make this work as a function we need to static initialize it:
sum_f sum = {};
Which works fine, but if we want to make it pipable, we could try this:
pipable_adaptor
how how you specify a pipeable wrapper for, for example boost::range::accumulate?
Thats a really interesting example, since it has two overloads of different
arity. This requires using some template constraints to avoid incorrect arity
detection. Something like this(of course assuming we have the traits
`is_range` and `is_range_of`):
struct accumulate_f
{
template<class X>
struct result;
template
/Viktor
_______________________________________________ Unsubscribe & other changes: http://lists.boost.org/mailman/listinfo.cgi/boost
participants (6)
-
Giovanni Piero Deretta
-
Nathan Ridge
-
paul Fultz
-
Robert Ramey
-
Thorsten Ottosen
-
Viktor Sehr