I might be asking for the impossible here, but given the lovely adaptor syntax, eg std::vector<int> vec; boost::copy( vec | boost::adaptors::reversed | boost::adaptors::uniqued, std::ostream_iterator<int>(std::cout) ); I notice it still uses the function call notation in the outermost operation( boost::copy() ), can it be written to eliminate function call syntax completely, say something like vec | boost::adaptors::reversed | boost::adaptors::uniqued | boost::adaptors::copy( std::ostream_iterator<int>(std::cout) ); or even vec | boost::adaptors::reversed | boost::adaptors::uniqued | std::ostream_iterator<int>(std::cout); Thx, - Rob.
At Wed, 5 Jan 2011 16:05:56 +0000, Robert Jones wrote:
I might be asking for the impossible here, but given the lovely adaptor syntax, eg
std::vector<int> vec; boost::copy( vec | boost::adaptors::reversed | boost::adaptors::uniqued, std::ostream_iterator<int>(std::cout) );
I notice it still uses the function call notation in the outermost operation( boost::copy() ), can it be written to eliminate function call syntax completely, say something like
vec | boost::adaptors::reversed | boost::adaptors::uniqued | boost::adaptors::copy( std::ostream_iterator<int>(std::cout) );
or even
vec | boost::adaptors::reversed | boost::adaptors::uniqued | std::ostream_iterator<int>(std::cout);
It could be done, but it might not make much sense. The boost adaptors are essentially lazy sequence transformations, but the copy operation you're asking for has a completely different nature: it's eager. If you still like that syntax, though, you could write a suitable copy function of your own in about 40 lines that would do the same job. -- Dave Abrahams BoostPro Computing http://www.boostpro.com
On Wed, Jan 5, 2011 at 5:13 PM, Dave Abrahams
At Wed, 5 Jan 2011 16:05:56 +0000, Robert Jones wrote:
I might be asking for the impossible here, but given the lovely adaptor syntax, eg
std::vector<int> vec; boost::copy( vec | boost::adaptors::reversed | boost::adaptors::uniqued, std::ostream_iterator<int>(std::cout) );
I notice it still uses the function call notation in the outermost operation( boost::copy() ), can it be written to eliminate function call syntax completely, say something like
vec | boost::adaptors::reversed | boost::adaptors::uniqued | boost::adaptors::copy( std::ostream_iterator<int>(std::cout) );
or even
vec | boost::adaptors::reversed | boost::adaptors::uniqued | std::ostream_iterator<int>(std::cout);
It could be done, but it might not make much sense. The boost adaptors are essentially lazy sequence transformations, but the copy operation you're asking for has a completely different nature: it's eager. If you still like that syntax, though, you could write a suitable copy function of your own in about 40 lines that would do the same job.
Yes.... I take your point about eager vs lazy, but on reflection is it not the case that almost any 'pipeline' of transformations has to end with 'eager' consumption? If I had int f( int ); boost::for_each( vec | boost::adaptors::reversed | boost::adaptors::uniqued, f ); and instead were able to write it as vec | boost::adaptors::reversed | boost::adaptors::uniqued | f; the final use of operator|() seems to pretty much imply eagerness in the same way that the for_each() does. I'm not sure that it would be possible to implement it unambiguously tho'. - Rob.
2011/1/6 Robert Jones
On Wed, Jan 5, 2011 at 5:13 PM, Dave Abrahams
wrote: At Wed, 5 Jan 2011 16:05:56 +0000, Robert Jones wrote:
I might be asking for the impossible here, but given the lovely adaptor syntax, eg
std::vector<int> vec; boost::copy( vec | boost::adaptors::reversed | boost::adaptors::uniqued, std::ostream_iterator<int>(std::cout) );
Lovley syntax indeed. I am new to it, and I like it.
I notice it still uses the function call notation in the outermost
operation( boost::copy() ), can it be written to eliminate function call syntax completely, say something like
vec | boost::adaptors::reversed | boost::adaptors::uniqued | boost::adaptors::copy( std::ostream_iterator<int>(std::cout) );
or even
vec | boost::adaptors::reversed | boost::adaptors::uniqued | std::ostream_iterator<int>(std::cout);
+1
It could be done, but it might not make much sense. The boost
adaptors are essentially lazy sequence transformations, but the copy operation you're asking for has a completely different nature: it's eager. If you still like that syntax, though, you could write a suitable copy function of your own in about 40 lines that would do the same job.
Yes.... I take your point about eager vs lazy, but on reflection is it not the case that almost any 'pipeline' of transformations has to end with 'eager' consumption?
If I had
int f( int );
boost::for_each( vec | boost::adaptors::reversed | boost::adaptors::uniqued, f );
and instead were able to write it as
vec | boost::adaptors::reversed | boost::adaptors::uniqued | f;
+1
the final use of operator|() seems to pretty much imply eagerness in the same way that the for_each() does.
I'm not sure that it would be possible to implement it unambiguously tho'.
- Rob.
Regards, Kris
At Thu, 6 Jan 2011 13:23:11 +0000, Robert Jones wrote:
Yes.... I take your point about eager vs lazy, but on reflection is it not the case that almost any 'pipeline' of transformations has to end with 'eager' consumption?
No, it is not. In some languages, everything is lazy, and the only thing that eventually forces evaluation is I/O. You can easily pass around, store, and compose lazy ranges to your heart's content.
If I had
int f( int );
boost::for_each( vec | boost::adaptors::reversed | boost::adaptors::uniqued, f );
and instead were able to write it as
vec | boost::adaptors::reversed | boost::adaptors::uniqued | f;
the final use of operator|() seems to pretty much imply eagerness in the same way that the for_each() does.
Only if you happen to know that f is a function and will be treated completely differently from the range adaptors. -- Dave Abrahams BoostPro Computing http://www.boostpro.com
On Thu, Jan 6, 2011 at 2:51 PM, Dave Abrahams
At Thu, 6 Jan 2011 13:23:11 +0000, Robert Jones wrote:
Yes.... I take your point about eager vs lazy, but on reflection is it
not
the case that almost any 'pipeline' of transformations has to end with 'eager' consumption?
No, it is not. In some languages, everything is lazy, and the only thing that eventually forces evaluation is I/O. You can easily pass around, store, and compose lazy ranges to your heart's content.
If I had
int f( int );
boost::for_each( vec | boost::adaptors::reversed | boost::adaptors::uniqued, f );
and instead were able to write it as
vec | boost::adaptors::reversed | boost::adaptors::uniqued | f;
the final use of operator|() seems to pretty much imply eagerness in the same way that the for_each() does.
Only if you happen to know that f is a function and will be treated completely differently from the range adaptors.
Absolutely, now we're on the same page. There must be some identifiable quality of the last element of the pipeline that indicates that it is a consumer of the elements of the range. I'm unsure that any such reasonable test could be devised, which is what makes such a facility hard to write. Just musing on this.... if the 'thing' is a function-like object that accepts arguments of the type of elements of the range (or convertible), could it be reasonably assumed to consume the range? - Rob.
At Thu, 6 Jan 2011 15:17:01 +0000, Robert Jones wrote:
the final use of operator|() seems to pretty much imply eagerness in the same way that the for_each() does.
Only if you happen to know that f is a function and will be treated completely differently from the range adaptors.
Absolutely, now we're on the same page. There must be some identifiable quality of the last element of the pipeline that indicates that it is a consumer of the elements of the range. I'm unsure that any such reasonable test could be devised, which is what makes such a facility hard to write.
Oh, I'm not worried that the implementation is hard. I'm worried about humans. That idiom that has a particular syntactic->semantic relationship, and you're talking about introducing a major inconsistency. It's not necessarily horrible, but that's my concern. -- Dave Abrahams BoostPro Computing http://www.boostpro.com
If I had
int f( int );
boost::for_each( vec | boost::adaptors::reversed | boost::adaptors::uniqued, f );
and instead were able to write it as
vec | boost::adaptors::reversed | boost::adaptors::uniqued | f;
the final use of operator|() seems to pretty much imply eagerness in the same way that the for_each() does.
Only if you happen to know that f is a function and will be treated completely differently from the range adaptors.
Ignoring the implementation issues for a moment I would like to explain my rationale for the current syntax. I consider the range adaptors to be orthogonal to algorithms. I therefore perceive a syntactic difference between the application of algorithms and adaptors to be an advantage. With your example above I find the first version of the code more instantly understandable since the most-significant operation (copy) is at the start of the line. Therefore my personal opinion is that it is undesirable to extend the pipe syntax to the application of algorithms. Now considering implementation details I believe there are other reasons to not extend the pipe syntax. When designing Boost.Range the extension of the central idioms to user types and algorithms has been made simple. The range adaptors are less simple to implement but are far less numerous than user supplied algorithms. The current arrangement provides an extremely low barrier for users wishing to implement range based algorithms. I am very interested to hear the thoughts of yourself and the rest of the boost community now that I have expanded upon some of the rationale. I am open to being convinced.
- Rob.
Regards,
Neil Groves
On Thu, Jan 6, 2011 at 11:57 PM, Neil Groves
Ignoring the implementation issues for a moment I would like to explain my rationale for the current syntax. I consider the range adaptors to be orthogonal to algorithms. I therefore perceive a syntactic difference between the application of algorithms and adaptors to be an advantage. With your example above I find the first version of the code more instantly understandable since the most-significant operation (copy) is at the start of the line. Therefore my personal opinion is that it is undesirable to extend the pipe syntax to the application of algorithms.
I'm not entirely convinced by the 'orthogonality' view - it is certainly one way of looking at adaptors vs algorithms, and is not without merit, but I do find the fully piped syntax a very natural expression of design intent. If the implementation difficulties were not prohibitive it is a syntax I'd like supported.
Now considering implementation details I believe there are other reasons to not extend the pipe syntax. When designing Boost.Range the extension of the central idioms to user types and algorithms has been made simple. The range adaptors are less simple to implement but are far less numerous than user supplied algorithms. The current arrangement provides an extremely low barrier for users wishing to implement range based algorithms.
It may be that this is not possible to do in a watertight way. I've looked at the current implementation, and at how the templated pipe operator is protected from consideration by ADL unless one operand is an adaptor. Using the fully piped notation the last element of the piped expression might be * an adaptor - implying a first class lazily evaluated range * a function accepting instances/references of value_type of the range * a function accepting a whole range * something else I haven't thought of each of which would require to processed quite differently by the pipe operator, so the templating would have to be quite clever. Assuming all that can be done, and I think it can, there's also the case where no adaptors are involved, eg. std::vector<int> v; void f(int); v | f; which doesn't go anywhere near the boost::range_detail namespace! I'm not sure that's fixable, unless you supply some help, eg. boost::range(v)| f; where boost::range() accesses the appropriate namespace. So, all in all maybe it's best left as is! I am very interested to hear the thoughts of yourself and the rest of the
boost community now that I have expanded upon some of the rationale. I am open to being convinced.
- Rob.
Robert Jones wrote:
On Thu, Jan 6, 2011 at 11:57 PM, Neil Groves
mailto:neil@grovescomputing.com> wrote: Ignoring the implementation issues for a moment I would like to explain my rationale for the current syntax. I consider the range adaptors to be orthogonal to algorithms. I therefore perceive a syntactic difference between the application of algorithms and adaptors to be an advantage. With your example above I find the first version of the code more instantly understandable since the most-significant operation (copy) is at the start of the line. Therefore my personal opinion is that it is undesirable to extend the pipe syntax to the application of algorithms.
I'm not entirely convinced by the 'orthogonality' view - it is certainly one way of looking at adaptors vs algorithms, and is not without merit, but I do find the fully piped syntax a very natural expression of design intent. If the implementation difficulties were not prohibitive it is a syntax I'd like supported.
-1 Neil's range rationale derives from the origins of (Thorsten's)range library and David Abrahams' interator adaptor library, all of which IIRC derive from Gary Powell's View Template Library. This history has probably biased my viewpoint. In my mind a(n adapted) range is a view onto some sequence of items. That 'view' may include filters, orders, item-transforms, etc. The operator| appends a transform to the view to left of the '|' returning another view. None of this actually creates/modifies/destroys any of the left most sequence's items. It's akin to creating a function object. Changing the meaning of operator| in this context to also 'execute' that function object would muddy the IMHO existing clearly defined meaning of such expressions. Jeff
participants (5)
-
Dave Abrahams
-
Jeff Flinn
-
Krzysztof Czainski
-
Neil Groves
-
Robert Jones