Boost logo

Boost :

From: Giovanni Piero Deretta (gpderetta_at_[hidden])
Date: 2008-04-10 09:10:12


[trimming out everything we agree]

On Thu, Apr 10, 2008 at 2:32 AM, shunsuke <pstade.mb_at_[hidden]> wrote:
> Giovanni Piero Deretta wrote:
> > Btw, can you explain me (again, please :P) why
> >
> > result_of<T_curry2(F)> >::type const c = {{{}};
> >
> > isn't enough? I guess I should see static_ implementation.
>
> Because, in general, a higher-order function must support stateful functions.
> `static_` makes a default-constructed function object on-the-fly
> and forwards arguments to it. ( I call it "staticalization". )
> Thus, `static_` can offer the static-initialization.
>

Duh! now it is obvious. Thanks again for the expalination.

>
> > What about collapsing static_ and result_of in a single class, and
> > allowing arbitrary composition:
> >
> > apply<compose, apply<compose, my_fun1, my_fun2>, my_fun3>::type c = {{}};
> >
> > The apply name is already taken by MPL, but maybe something of the sort.
>
> I chose to minimize the set of names.
>
> static_< result_of<compose(result_of<compose(my_fun1, my_fun2)>::type, my_fun3)> >::type
> const c = {{}};
>
> is enough. Users don't need to remember yet another metafunction `apply`.
> (result_of expression is a little combersome, though.)
> If you need static-initialization, wrap a result_of expression by `static_`.
> Isn't it simple?

Just two things:

  - using result_of requires the 'round lambda syntax' which means
that I can't easily use them in mpl expressions (I can't put
mpl::placeholders in place of argument types). Of course I can round
trip via boost.FunctionTypes but is very verbose (and probably
slower).

 - having to specify ::type requires prefixing the expression with
typename in templates if the type is dependent.

It is not necessarily egg job to fix this, but it would be nice to
have this. It is ok if I have to specify static_ in addition.

>
> Also, it is only the outmost metafunction(i.e. static_) that knows when "staticalization" should be
> performed.
> Otherwise, all the metafunction have to perform "staticalization", which is wasteful.

Ok, now that I understood how static_ works, agree 100%.

> >> > Also I do not like the T_* convention to name function types. I
> >> > liked best the old op_* convention.
> >>
> >> LexicallyTypedObject concept is not restricted to FunctionObjects.
> >> "T_" is short of "typeof".
> >> This concept is a workaround for "missig decltype" in C++03.
> >>
> >
> > What about using the prefix 'typeof_' or a postfix '_type'? I really
> > dislike that uppercase T.
> > Maybe even a postfix _t would be more than enough.
>
> I prefer prefix, because result_of expression and function-call expression are in sync:
> result_of<T_plus(int, int)>::type r =
> plus(1, 2);
> I thought 'typeof_' was too long.
> Anyway we should decide by majority vote.

+1 for separate namespace. If that is controversial, +1 for typeof_ instead.

>
> >> > Even better would be
> >> > to segregate function object types to their own namespace, a-la fusion
> >> > or proto. (i.e. the functional namespace).
> >>
> >> See this:
> >>
> >> namespace poost {
> >> namespace op {
> >> struct foo {};
> >> }
> >>
> >> op::foo const foo = {};
> >>
> >> namespace nested {
> >> namespace op {
> >> struct bar {};
> >> }
> >> op::bar const bar = {};
> >>
> >> void test()
> >> {
> >> op::foo my_foo = foo; // doesn't compile
> >> }
> >> }
> >> }
> >>
> >> Thus, I've rejected segregated-namespace-way.
> >
> > uh?
> > ...
> > void test() {
> > poost::op::foo my_foo = foo; // it compiles!
> > }
> >
> > Seems a very simple fix (and arguably the right thing in the first place!)
>
> A result_of expression becomes long.

You can always namespace aliases. (I also *always* namespace qualify
my function objects, not only their types).

> This is a decision from my experince.

I had a different experience, but probably your is more extensive.

>
> >> > Making a function object pipable should just be a matter of deriving
> >> > it from an (empty) egg::pipable, which would manifest the intention of
> >> > the object to participate in pipelines.
> >>
> >> I'm not sure it has an advantage over higer-order functions.
> >
> > Less intellectual overhead :). I need to know less things to
> > appreciate what 'pipable' means. Concept should be as simple as
> > possible IMHO. (you can of course add "... but not simpler" :) )
>
> I want to define PipableFunctionObject without dependency on Egg library.
>

I appreciate this need, but IMHO it is not worth it. The pipable
support library could be a single, self contained header.

>
> > I meant Egg of course. What I wanted to say is:
> >
> > Egg provides a templated operator| in egg namespace. There is a single
> > implementation for this operator and is:
> >
> > namespace pibable_ {
> >
> > struct pipable {}; // ADL hook
> >
> > template<typename Lhs, typename Rhs>
> > typename
> > boost::lazy_enable_if<bost::is_base_and_derived<pipable, Rhs>,
> > boost::result_of<Rhs(Lhs&)> >::type
> > operator|(Lhs& lhs, Rhs rhs) {
> > return rhs(lhs);
> > }
> >
> > template<typename Lhs, typename Rhs>
> > typename
> > boost::lazy_enable_if<bost::is_base_and_derived<pipable, Rhs>,
> > boost::result_of<Rhs(const Lhs&)> >::type
> > operator|(const Lhs& lhs, Rhs rhs) {
> > return rhs(lhs);
> > }
> > }
> >
> > Any unary function object that wants to model the pipable concept must
> > derive from pipable_::pipable. This will trigger ADL and will find the
> > operator | when necessary. (In c++0x of course you would put
> > operator| in the global namespace and make it a template function
> > constrained on the (non auto) concept Pipable)
>
> I can't always use inheritance -- for stateful static initialization.

ADL kicks in even for template argument types (i.e. namespace of
template arguments are considered associated namespaces), so this
works :

 namespace egg {
     namespace pipable_support {
         struct pipable {};

         template<typename T, typename T2>
         void operator |(T, T2);
     };
     using pipable_support::pipable;
 }
 template<typename T = egg::pipable> struct my_fun_t {};

 int main() {
     my_fun_t<> my_fun = {}; // statically initialized!

     1|my_fun;
 }

No need for inheritance. There might also be other tricks.

> Also, how can we make `_1+_2` pipable?
> A higher-order function is needed, after all.

of course: you write pipable(_1 + _2), to add 'pipability' to non
pibable objects on the fly.

>
> > This of course only work with unary callable entities. to make a
> > non-unary entity unary, apply the appropriate curry/bind grease :).
> > This will also get rid of the need for Ambi.
>
> Though I'm not sure what you mention,
> curry/bind is not can-do-everything.
> It must capture arguments by-copy.
>

use ref/cref if you want capture by reference. In almost all my use
cases, it is fine to capture the object by copy (usually a function
object).

BTW, on a completely unrelated topic: it would be nice if bind/lambda
allowed to specify the default capture behaviour, like C++0x lambdas
(I think that with proto this wouldn't be hard to do).

>
> > As an extension (I'm not proposing it, it is just for the sake of
> > discussion), '|' could be a synonym for compose (IIRC it is spelled
> > '$' in haskell):
>
> A PipableFunctionObject supports '$' using `operator|=`.

I think that, in my scenario, |= would more like the Haskel '.'
operator (i.e. compose).

> It might be different what you expect, though.

Of course in egg currently | and |= do not have compose like behaviour.

>
>
> > if lhs and rhs are callable entites (in practice you detect pipability):
> > a | b
> > is the same as:
> > compose(b, a);
> >
> > else if only b is a callable entity:
> > a | b
> > is the same as
> > compose(b, always(a));
> >
> > else the expression is illegal.
> >
> > [IIRC in haskel values are in practice treated as nullary functions,
> > so the use of 'always' here would mimic the functional comunity usage]
>
> IMO, this seems to introduce unneeded complexity.
> "If 'a' is callable,... else if 'b' is pipable,... else if 'c' is a placeholder..."
> seems a bad switch statement.

you only need to handle pipable:

  template<typename Lhs, typename Rhs>
  enable_if_pipable_rhs_and_not_lhs<Rhs, Lhs, result_of<Rhs(Lhs)> >::type
  operator|(Lhs lsh, Rhs rhs) {
     return rhs(lhs);
  }

  template<typename Lhs, typename Rhs>
  enable_if_pipable_rhs_and_lhs<Rhs, Lhs, result_of<compose(Rhs, Lhs)> >::type
  operator|(Lhs lhs, Rhs rhs) {
      return compose(rhs, lhs);
  }

Apply appropriate magic to handle lvalues and rvalues lhs in the first
function. The implementation of enable_if_pipable_rhs_and_[not_]lhs is
obvious.

>
> > This means that you can create pipelines of transforms:
> >
> > map(my_range, my_first_view|my_second_view)
> >
> > Would be the same as:
> >
> > map(my_range, compose(my_second_view, my_first_view));
>
> I understand it.
>
>
> > or
> >
> > map(my_range, my_first_view) | protect(lazy(_1, my_second_view))
>
> Sorry, I couldn't understand this expression.

Of course, it is wrong :). It is missing the second 'map':

  map(my_range, my_first_view) | protect(lazy(map)(_1, my_second_view))

You need protect here, or | would be part of the lambda expression.

>
>
> >> Sorry, I couldn't understand this proposal.
> >> What does `x | foo(_, 10, "bar")` mean?
> >
> > hum, let me see, in egg syntax it should be:
> >
> > compose(lazy(foo)(_1, 10, "bar), always(x))
>
> I understand it.
>
>
> > but see below:
> >
> >> Also, f(_, 10, _)("baz", "bar"); seems supported by egg::lazy.
> >
> > yes, the only difference is that the result is not a lambda expression
> > (as if there was an implicit protect):
> > see the difference between:
> >
> > lazy(foo)(lazy(bar)(_1, _2)); // ll::bind(foo, ll::bind(bar, _1))
>
> I understand it.
>
>
> > and:
> >
> > lazy(foo)(protect(lazy(bar)(_1, _2))); // ll::bind(foo, protect(ll::bind(bar, _1)))
> >
> > with my syntax (actually this is lifted directly from the generalized
> > currying in FC++), you could spell the latter:
> >
> > foo(bar(_,_)); // s
>
> I'm lost.
> How can I use this `foo(bar(_,_))` ?

I was imprecise:
   foo(bar(_,_))

Actually corresponds to:

  lazy(foo)(protect(lazy(bar)(_1, _2))) (); // we are calling it!

The point is that unlike lambda expressions, '_' does not cause the
full expression to be a lazy expression (up to the protect)
I'll try to be more clear later.

>
>
> > this is important if egg were (as one would expect) to register its
> > operator| with boost::lambda:
> >
> > map(my_range, _1 | lazy(is_less)(_1, 10)); // does not work!
> >
> > map(my_range, _1 | protect(lazy(is_less)(_1, 10))) ; //ok
> >
> > map(my_range, _1 | is_less(_, 10)); // also ok
>
> I'm lost again.
> Is `_1 | lazy(is_less)(_1, 10)` translated into
> `compose(lazy(is_less)(_1, 10), _1)` ?

I think so, expect that I'm not sure if the result of compose is still
a lambda expression. Of course, using a plain _1 here is pointless,
but with more complex expressions it make more sense:

   map(range, lazy(multiply)(_1, 5) | is_less(_, 100));

If you were to use _1 instead of _ you would need to use protect:

  map(range, lazy(multiply)(_1, 5) | protect(lazy(is_less)(_, 100)));

>
>
> > A nice name for the generalized curry operation is of course curry:
>
> BTW, strictly speaking, this is not a currying.
> This is a partial application.
>

I still can't understand this subtlety :(.

>
> > auto is_less = curry(is_less_non_curriable);
> >
> > assert( is_less(_, 10)(7) == true );
> >
> > This is trivially implementable with lazy + protect, but an ah-hoc
> > implementation might be simpler and easier on the compiler (no need
> > for full blown lambda support). Also, i spell the missing parameters
> > '_' because that's what FC++ used, 'deferred' might also be a good
> > name.
> >
> > What do you think?
>
> I'm still lost.
> Why not `lazy_ex(is_less_non_curriable)(_, 10)(7)` ?
> (Assume `lazy_ex` supports "non-numbered placeholder".)

Almost the same, except that you would need to wrap the call to
lazy_ex around protect(). If lazy_ex were to apply protect implicitly
would be exaclty the same. BTW, I do not consider non-numbered
placeholders necessary, I was just mimicking FC++ syntax.

The protect is important, you do not need the full power of lambda
(ie.e function composition) , so you want to stop 'lambdification'
immediately. This is why a better name would be a lazy_simple :)

> >
> >> > Or better, modify boost.lambda
> >> > so that any Polymorphic function object work with it (shouldn't be
> >> > hard), so that we do not need an ad-hoc concept.
> >>
> >> It seems hard.
> >
> > I do not think so, in fact I think that there are patches around. On
> > the other hand, I'm not the one doing it, so I shouldn't complain.
>
> IIRC, Daniel Walker was trying it.

Yes.

> But, strictly speaking, it seems impossible to detect whether
> a FunctionObject is ResultOf conforming or Lambda conforming.
> We need C++0x Concept. (has_sig_template or has_result_template is not enough.)

Uh? Why? doens't your result_of work seamlessy with both the sig and
result_of protocol? where is the problem?

> >> I rarely use Boost.Bind and Boost.Lambda in production code.
> >
> > Why not? I do all the time.
>
> I don't need a hammer to break an egg. :-)

:). I agree that Lambda is quite hammer like, but bind is simple and quite

> >> If reviewers regard it as an inadequate component,
> >> I will simply remove nestN from Egg.
> >> BTW, how can we write nested lambda using phoenix?
> >
> > lambda[ for_each(_1, lambda(_a = _1)[ front(_a) += _1] )(range)
> >
> > [at least It should work, given appropriate definitions of for_each and front]
>
> It seems different motivation.
> nestN returns a function which returns a function which returns a....
> e.g. nest3(....)(1)(2,3)(4);

Never tried it, but I'm quite sure that the phoenix lambda syntax also
let you return lambdas:

 auto always = lambda[ lambda(_a = _1)[ _1 ] ];

 auto always_10 = always(10);

 assert(alsways_10() == 10);

should work (modulo modifying phoenix to work with rvalues).
Disclaimer: I haven't tryed it! But even if it doesn't work with
phoenix, it shouldn't be terribly hard to make this syntax work.

>
>
> >> bll_N is a model of LexicallyTypedObject
> >> so that you don't need to remember the name "placeholderN_type".
> >
> > so now I have to remember two types instead of one :)
>
> You have to remember placeholder1_type is const-qualified.
>

Ah, that's a significan difference. I didn't catch it from the documentation.

>
> > Unless lambda registers its types of course. Anyways, It would be
> > enough for me if this worked:
> >
> > BOOST_AUTO(foobar, compose(foo, bar));
>
> I will consider Boost.Typeof support.
>

This is just really low priority though, I do not consider it a must-have.

-- 
gpd

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