Boost logo

Boost :

From: shunsuke (pstade.mb_at_[hidden])
Date: 2008-04-15 01:35:37


Daniel Walker wrote:
> OK, I still don't want to give a formal review, because I haven't
> looked at the whole library and I don't want to be unfair. However, I
> sent some comments off-list to Shunsuke, and he asked me to post them
> here. As he pointed out to me, it's the Boost way! :-)

Thank you, Daniel.

> I really do think that Egg is technologically solid. If the list is
> slow to show interest, I think that's not a reflection of some
> technical concern, but perhaps it's a sign of usability hurdles... In
> other words, how do you convince users that using the library would
> make their lives easier? How do you sell the library?

I probably failed to sell the library.

> Approaching the library for the first time, as a new user as much as a
> potential reviewer, it seemed to me that Egg overlaps a great deal
> with existing Boost libraries and with C++0x. I constantly found
> myself asking: "Why would I want to learn this? Can't I just use bind?

egg::lazy is useful.
`lazy_plus(lazy_plus(_1, 3), lazy_plus(_2, _1))` is much more
readable than an expression using bind.

> Yes, this is better than writing a function, but how is this any
> better than just writing a class with operator()?" That last question
> was the show stopper for me;

It depends on some conditions:
   * C++0x is available for you?
   * Your FunctionObject should be Polymorphic?

> I don't have the time, energy or
> inclination to learn a new paradigm for writing functors unless there
> is a strong motivating need or big potential payoff. Remember,
> fundamentally, the raison d'etre of Boost is to increase productivity.
> At least, that's what the FAQ says. ;-) And in reality, if this wasn't
> the case, no one would have ever started using Boost, it wouldn't be
> famous today, and there would be no demand for Boost libraries to be
> adopted into the standard. It's because of the Boost libraries' great
> utility that they're so widely employed and admired.

According to my experience, directly or indirectly,
Egg has been increasing productivity.
FWIW, Egg is used to implement a Range Library:
   http://p-stade.sourceforge.net/oven/index.html

> Each of the Egg components are interesting, but how are they helpful?
> The name 'Egg' is a great example of what I'm talking about. It's an
> interesting name, but I can't tell from the name how the library would
> be useful to me.

"egg" didn't show a bad impression in [interest] phase,
so I kept the name.

> The same is true for many of Egg's "selling points,"
> for lack of a better term. For example, static initialization is
> interesting, but in the highly unusual circumstance where I would find
> myself using global or static objects of any sort (let alone
> functors), I can already write "const my_functor f = my_functor()" and
> most modern compilers will optimize away any significant run time
> overhead. Also, as I understand it, supporting list initialization
> does nothing to help the initialization order fiasco. That problem is
> solved by avoiding constructor dependencies between static objects,
> regardless of what sort of constructor they're built with. If I'm
> wrong about these points, Shunsuke, you may want to make a
> stronger/clearer explanation of the feature in the documentation. But
> it could be that global/static functors are such an obscure use-case
> that potential users simply won't care one way or the other. What
> might be useful is to have some sort of global object manager, but
> that's a different library, call it Boost.GOM ;-), and that doesn't
> directly relate to functional programming.

I actually thought so.
(The old Egg supported only dynamic-initialization.)
I was upset when I saw a document about static-initialization in Proto.
I quote this: http://lists.boost.org/Archives/boost/2008/04/135452.php

> Not that Egg shouldn't have a static initialization feature; it's just
> to say that static initialization may not be a compelling selling
> point. The same is true for the forwarding problem workarounds. The
> by_ref strategy simply means passing arguments by reference, by_cref
> means by const reference, etc. That's such a simple idea that it seems
> convoluted to wrap it in some framework of forwarding strategies. I
> just don't understand what it buys me. Whatever happened to
> boost::call_traits? Would the forwarding strategies find a better home
> there? Or are they really more of a backend detail? Maybe they should
> be put in a special "Advanced Features" section of the documentation
> for "power users." Either way, I don't feel like it's an attractive
> selling point; to me it made me feel as if Egg requires you to jump
> through hoops to do a simple thing like change function parameters
> from refs to const refs.

I actually noted "You can skip reading Forwarding Strategies
when first reading the documentation." in QuickStart section.
Probably I should place it in top of ForwardingStrategies section.

> On both of these points, I haven't thoroughly looked into the
> documentation, the implementation, or played around with actual code
> at all, which is part of the reason that I didn't think it would be
> fair for me to write a review. However, I did play around with the
> code snippet in the Overview section of the documentation. So, I
> believe, here's how you would write my_plus using Egg:
>
> struct little_plus
> {
> template<class Me, class X, class Y>
> struct apply
> : boost::remove_cv<X>
> { };
>
> template<class Re, class X, class Y>
> Re call(X &x, Y &y) const
> {
> return x + y;
> }
> };
> typedef egg::function<little_plus> my_plus;
>
> ... and here's how you would write it normally:
>
> struct my_plus
> {
> template<class> struct result;
> template<class Me, class X, class Y>
> struct result<Me(X,Y)>
> : boost::remove_cv<X>
> { };
>
> template<class X, class Y>
> typename result<my_plus(X,Y)>::type
> operator()(X x, Y y) const
> {
> return x + y;
> }
> };
>
> To my eye, I can't see how Egg makes things that much easier. It
> doesn't look particularly helpful.

I was feeling annoyed that I had to write `typename result<my_plus(X,Y)>::type`.
Also, your `result<>` implementation is wrong.
It is error-prone to write ResultOf-conforming FunctionObject without some indirection.

> Now, here's how you would write it
> in C++0x:
>
> struct my_plus
> {
> template<class X, class Y>
> typename boost::remove_cv<X>::type
> operator()(X x, Y y) const
> {
> return x + y;
> }
> };
>
> So, as a new user, I'm going to spend my time learning C++0x! Why
> should I instead sacrifice time learning Egg? If that question remains
> unanswered for other potential users, then they will never get beyond
> the introduction in the documentation, and you will attract no new
> users.

It might depend on when C++0x will be available for users.
FWIW, many Egg components will be useful even in C++0x.

> Meanwhile. there's all this great technology in Egg going to waste.
> I'm not sure if the situation is as bad as I'm describing it, and if
> so I'm not sure if I have any good suggestions to help. Perhaps, I
> would again suggest looking at Boost.Iterator as an example.
> Boost.Iterator has all sorts of gizmos, tricks, workarounds, etc. in
> the backend; I get dizzy thinking about it! I don't want to know about
> a single one of them, but I'm grateful for all of them. From my
> perspective, as a user, I know std::iterator and the classic SGI
> Iterator concepts. Then, I see Boost.Iterator makes these concepts
> easier to program for (by providing archetypes to test against) and
> more expressive (by expanding the number of concepts). Inheriting from
> std::iterator is fragile and doesn't do much for you. Boost.Iterator
> provides iterator_facade, so I inherit from it instead and get stuff
> for free. Switching from std::iterator to iterator_facade is fairly
> intuitive, and the new iterator classes are generally easier to write.
> So, for all these reasons, I choose to learn and use Boost.Iterator.
>
> Maybe the key is that Boost.Iterator starts from something everyone is
> already familiar with - the standard library's iterator utilities.
> Since people are familiar with the standard and its shortcomings,
> Boost.Iterator is more intuitive and accessible. So, maybe Egg should
> start from the same place.
>
> In the Quick Start section of the documentation, as a motivating
> example you use a code snippet to show the problems with function
> templates. However, most of these problems are solved by the standard
> AdaptableBinaryFunction concept. So, the question is not whether Egg
> is more useful than a function template, but whether Egg is more
> useful than an AdaptableBinaryFunction. So, your motivating example
> should start not from ...
>
> template< .. >
> .. make_filtered( .. )
> {
> ..
> }
>
> ... but from ...
>
> template< .. >
> struct make_filtered : std::binary_function< .. >
> {
> ..
> }

You pointed an interesting comparison.
I could say "As std::iterator<> is fragile, std::unary/binary/nary_function<> is fragile.".
E.g. It would be difficult to express `make_filtered(make_filtered(src, &is_not_X), &is_not_Y)`
without PolymorphicFunctionObject concept.

> Again, people are already familiar with the standard library and its
> shortcomings. By framing your library in terms of solving problems
> with the standard, rather than solving problems with static
> initialization, the forwarding problem, result_of/lambda
> compatibility, etc., I think your library would appear more useful.

Probably I should have emphasized advantages of PolymorphicFunctionObjects.
I assumed users were already familiar with it while using Boost.Lambda.

> (The other more technical/mundane issues can be left in the backend;
> users would be grateful for them without having to be specifically
> aware of them.) Of course, the obvious problem with the standard is
> that there's no std::nary_function<>. I think most C++ programmers can
> relate to that problem. Can't function_facade be viewed as a solution?

The problem is that std::unary/binary/nary_function<> is not Polymorphic.
E.g. they are not usable when you write an overloaded function.

> Ideally, you could start not only with the C++98 library but also with
> TR1/C++0x. Boost.Function has already been accepted into TR1. Now,
> couldn't the differed callback std::function<> be viewed as simply a
> specific instance of an Egg "function adaptor"?

Yes. std::function<> can be regarded as one of the FunctionAdaptors.
It is similar to egg::mono, but it performs type-erasure.

> I think it would be
> exciting to see a new rewrite/drop-in-replacement/extension of
> Boost.Function (or maybe call it Boost.Functor) that would move
> function objects beyond both C++98 and C++0x. Egg could be a first
> step in that direction. It's mostly a matter of refocusing your design
> objectives and refactoring the code accordingly.

Sorry, I couldn't understand what you want.
Any (imaginary) example?

> So, those are just some thoughts that occurred to me while I was
> fiddling around with Egg over the weekend. In it's current state, I
> have trouble getting excited about it. But I can see Egg has the
> potential to become something that would appeal to me. Regardless, I
> admire Shunsuke's openness and courage in submitting his work for
> public scrutiny, and I hope my comments are helpful and encouraging in
> at least some small way.

It is really helpful.

Regards,

-- 
Shunsuke Sogame

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