Boost logo

Boost :

From: Daniel Walker (daniel.j.walker_at_[hidden])
Date: 2008-04-14 20:40:35


On Sun, Apr 13, 2008 at 4:26 PM, dan marsden <danmarsden_at_[hidden]> wrote:
> Hi All
>
> The Egg review has run for 2 weeks now with zero reviews, which is obviously
> disappointing. There has been some good and pretty thorough discussion, and
> a couple of promises of reviews. As such, I'm extending the review period
> by 1 week until 20th April 2008.

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! :-)

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?

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?
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; 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.

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. 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.

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.

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. 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.

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< .. >
{
..
}

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.
(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?
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"? 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.

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.

Cordially,
Daniel Walker


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