From: Giovanni Piero Deretta (gpderetta_at_[hidden])
Date: 2008-07-02 06:25:04
On Wed, Jul 2, 2008 at 4:45 AM, Dean Michael Berris
> On Tue, Jul 1, 2008 at 8:28 PM, Giovanni Piero Deretta
> <gpderetta_at_[hidden]> wrote:
>> On Tue, Jul 1, 2008 at 12:50 PM, Dean Michael Berris
>> <mikhailberis_at_[hidden]> 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
>> 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) 
returns a lazy range (i.e. the moral equivalent of two transform_iterators).
map(_1, mapper) 
returns a lazy unary function that takes a range and returns another
There is a third syntax that can be used:
range | map(mapper) 
Now, in the usual phoenix world, the 'map' in  and  would
actually be two different functions (functions in phoenix are always
lazy even if all arguments are provided). Map in  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  and  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) 
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
. 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
range | map(mapper, _) -> range | map(mapper) 
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  is desirable, but  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
> 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.
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk