
On Wed, Jul 2, 2008 at 4:45 AM, Dean Michael Berris <mikhailberis@gmail.com> wrote:
On Tue, Jul 1, 2008 at 8:28 PM, Giovanni Piero Deretta <gpderetta@gmail.com> wrote:
On Tue, Jul 1, 2008 at 12:50 PM, Dean Michael Berris <mikhailberis@gmail.com> wrote:
Isn't the whole point of creating an expression template based-approach to create these (maybe using proto I guess) and allow:
1. The same placeholders used in Phoenix+Proto to be used in the pipe syntax in range_ex (or an imaginary new RangeEx implementation). 2. Allow for efficient (i.e., compiler optimized/assisted) implementations of the composition of these new views/ranges/iterators.
Of course. My point is that you need very few beyond what is available in Phoenix. Just the ability to have a placeholder which doesn't make the whole expression lazy ( i.e. foo(_) should be the same as unlambda(bind(foo, _1)) ).
When you say the whole expression lazy, do you mean even the for_each?
Why don't you want the range generation functions (or the pipe syntax) return a lazy range? Perhaps something that only gets activated when begin(range) and end(range) are invoked?
Wait, there is a bit of confusion. There are two kind of lazyness: the first kind are lazily evaluated ranges, like those that use transform_iterator, filter_iterator, etc... the other kind are lazily evaluated functions like those in Phoenix (i.e. higher order functions that return other functions). Let's take a map function. It can be lazy in two ways: map(range, mapper) [1] returns a lazy range (i.e. the moral equivalent of two transform_iterators). map(_1, mapper) [2] returns a lazy unary function that takes a range and returns another (lazy range). There is a third syntax that can be used: range | map(mapper) [3] Now, in the usual phoenix world, the 'map' in [1] and [2] would actually be two different functions (functions in phoenix are always lazy even if all arguments are provided). Map in [3] must be again a different function. This means that a lazy range algorithm should provide all three variants to satisfy all the use cases. Unless... Maps in [1] and [2] can actually be the same function by adding "lazyness on demand" (which is not hard to do). Also lazy functions let's us implement the third syntax easily: Let's assume that 'operator|(r, f)' is a global operator that returns 'f(r)'. This doesn't work quite right though: range | map(_1, mapper) Because map(_1, mapper) doesn't simply return an unary function, but it is actually an expression template, which will try to make operator| 'its own'. You need to stop the expression template machinery. In boost.lambda you use 'unlambda', in phoenix I think that the magic keyword is 'lambda[]': range | lambda[ map(_1, mapper) ] It should works, but the 'lambda' is a bit ugly. If we introduced a new placeholder, like _, we could make the syntax more lightweight: range | map(_, mapper) [4] I.e. _ instructs phoenix to automatically wrap the lazy function in a lambda block. Note that this syntax is quite similar to the syntax in [3]. if we followed FC++ lead and swapper the order of the range and mapper argument in 'map', we could add a rule that says that any missing argument is automatically substituted with a _ (in practice, currying, in fact FC++ itself introduced the '_' syntax for generalized currying): range | map(mapper, _) -> range | map(mapper) [5] Nice! Unfortunately, automatic currying is (very arguably) error prone, and most importantly, it makes it impossible to have variadic functions (for example ,in sort(range), how do you know if this is a curried expression or you are using the default comparator?). I think that [4] is desirable, but [5] is not worth it.
?
Although I do like how this looks granted that you are using C++0x's auto -- in today's C++, the reason I used the STL was to avoid having to compute the actual type produced by the pipe syntax.
You need to know the result type of the reducer even in your example, so it is not specific to the 'sugared' syntax :)
for_each ( range(source) | take(10) | map(mapper()), cout << arg1 << endl ); // with Phoenix
Assuming that there's a boost::for_each(range<T>, F) -- I don't think I need to know the result type of the reducer in that example. Am I missing something?
There is no reducer (i.e. a fold) in the above expression. If you want to reduce a range you need to know the result type of the reducer function if you want to actually save the final result (which is usually the case :) ).
Maybe this is why Dave is asking for a better BOOST_TYPEOF. ;-)
Well, as long as the result type of the final fold has been registered, it should work out of the box.
Of course, registered manually you mean? Or do you rely on compiler-specific 'typeof'?
manually if there is no native typeof available.
Okay, but shouldn't this composition with transform_iterator be done with something that 'comes out of the box'?
It is ok to provide a zip+transform in a single function, but it is not a primitive. Let's call it zip_with, its result type should be transformed_range<zipped_range<RangeTuple> , Mapper>, we do not need an explicit zippedWith_range (and in fact it would make deforestation harder).
Shouldn't the deforestation already have happened in the zipping and application of the function to yield another tuple?
let say you have the rewrite rule: map<map<Range,A>,B> -> map<Range, compose<A,B> > If zip_with returns a map<zip<R1, R2>, A>, you can simplify the expression map(zip_with(r1, r2, a), b) as: map<map<zip<R1, R2>, A>, B> -> map<zip<R1, R2>, compose<A,B> > without any other special rule. If zip_with were to return a special zip<R1, R2, A>, you need a specific rule to handle this case. I.e. zip_with shouldn't be a primitive, but should be constructed on top of map and zip. HTH, -- gpd