Boost logo

Boost :

From: Paul Mensonides (pmenso57_at_[hidden])
Date: 2006-02-16 08:29:48


> -----Original Message-----
> From: boost-bounces_at_[hidden]
> [mailto:boost-bounces_at_[hidden]] On Behalf Of Oliver Kullmann

> > What is a "plain example"?
> >
>
> An example which shows one thing, without any additional
> fuss, so that it immediately clear what the example is doing,
> and how it is achieved.

Those are the ones that you (and I) call contrived. There are many of those.

> If I enter the current documentation, then first I get to the
> short introduction page with a link to the chapter from the
> mpl book, which is based on earlier chapter in the book, and
> thus cannot be used as an introduction.

It isn't really based on an earlier chapter.

> So then I continue to Topics (the next item on the list),
> where the natural choice seems to look for "Motivation": But
> here again there is no plain example, but we need to know at
> this point what the "is_function<> metafunction in Boost" is
> (no attempt is made to explain this thing).
> So the solution presented down the page doesn't motivate
> anything for anybody who is not familiar with all of Boost
> (it is not even said where we could find this function in
> Boost, i.e., to what sub-library it belongs).

Where does the documentation refer to an "is_function<> metafunction in Boost"?
(The documentation uses some similar things as examples.) Incidentally, I
didn't write the first three of the documents in the "Topics" section, but I did
write the rest of them.

> Such examples I call "non-plain". A plain example would
> consider for example the problem of how to create an
> enumeration out of a variable list of identifiers. The point
> is here that the example is *simple*, that is, atomic --- not
> much reference to anything else is needed.

And I'd call that a bad example that promotes a bad use of the library. Taking
this one apart, you don't gain anything by defining a simple enum like this.
Instead, you might gain something if you define not only the enum, but also
surrounding code that makes the enum more useful than a built in enum (e.g. I/O
support, etc.). But when you do that, the example is suddenly much more
complicated. As I said before, this kind of thing does not boil down to simple
use cases--which is exactly what this kind of example is. Instead, when the
abstractions provided are brought all the way down to concrete use cases, those
use cases are either bad uses or are complex. This is almost invariably true.
If it wasn't, you wouldn't need preprocessor metaprogramming to do it.

> > > and thus I skipped that and went to Reference, where I found
> > > BOOST_PP_SEQ_FOR_EACH, making some still mysterious remarks about
> > > "The next available BOOST_PP_FOR repetition."
> > > etc. But at various places it somehow says that "reentry problems"
> > > whatever this means are solved now.
> >
> > There is an article in the topics section about this
> (titled "Reentrancy").
> >
>
> How does the reader of the documentation get there? After I
> found that the Motivation does not motivate much for me, I
> went on to "Known problems" --- alright, I understand that,
> but it doesn't give me much. So well, going to techniques :
> Again the very first example doesn't say what it is supposed
> to do, and so it continues; all examples are part of some
> discussion within an expert round of users (who don't bother
> to even spell out the problems they are discussing).

Expert users are the primary targets of the documentation. This is a generative
_metaprogramming_ library. There are all kinds of things that you can do with
metaprogramming--one of them is write code that is total crap. The target of
the library is advanced programmers, because only advanced programmers have
accumulated the design experience necessary to evaluate the tradeoffs involved.
Furthermore, many Boost authors can readily identify with the underlying problem
that something like 'is_function' takes in stride (and what it really is an
example of). That's dealing with variable arity--a very common area in which
the library is used.

> So let's skip techniques. Next comes "Incompatibilities". It
> speaks about the previous Boost release 1.28, so it seems not
> of relevance anymore (especially since I'm a new user).

No, that one isn't relevant to you.

> So let's skip this, going to "reentrancy". From the headline
> it's not clear what could be meant by it (if you don't know
> it). Alright, the first few paragraphs there name the problem.
> But actually not explicitely, it doesn't say that C or C++ in
> version soandso has this "feature", but it speaks of "the
> preprocessor", which might mean a specific compiler, some
> special features or this very library itself.

The preprocessor is defined by the standard, and that is what the documentation
is referring to. Many real world preprocessors approximate it closely, and some
are horribly broken.

> It also speaks
> of recursion, which in the usual sense of the word (that is,
> in mathematics) is not what is really meant.

I'm not sure I follow this. "Recursion", as I'm using the term in the
documentation and as it is commonly used in computer science, is the ability for
an interface to use itself.

> I want to make the point here, that as a documentation author
> one has similar responsibilities to a car driver: One must
> anticipate problems. Readers always have a certain special
> angle how the view it, and one must add many hints and
> redundancies to help them becoming aware of the incongruence
> of their point of view with the authors point of view.

I don't disagree with that, but I also can't accommodate every possible point of
view.

> My
> main assumption was, that C and C++ respects the programmer,
> and thus does not disallow potentially "dangerous
> constructions". So in my mind I glanced over the problem with
> "recursion", and basically thought that this is no problem
> for C and C++ (the whole process might go wrong, but we are
> in control).
> Coming from this point of view, I then see "recursion", guess
> they mean something different, and moreover I have already
> seen somewhere that "reentrancy" (whatever this means) is
> solved by the new version of the library anyway,

The documentation doesn't say that *anywhere*.

> so I don't
> need to bother (not to forget that when I come to this point
> in the documentation, then I already "learned" that
> apparently most parts of the documentation have to be skipped).

Or read with an attempt to understand what they are saying...

> > > Only when looking it up
> > > in the rationale of C99 I found a clear statement about the issue
> > > (while the C++ standard somehow supposes you know already the
> > > issue).
> >
> > It is pretty clear in both standards (6.10.3.4/2 in C and
> 16.3.4/2 in C++).
> >
>
> I'm not aware of *any* book on C or C++ discussing this
> issue. I have quite a collection of them, read (or have read)
> CUJ, CVu and Overload, but the preprocessor seems to be a tabu.
> This I think one should keep in mind for the documentation.

So, what you're saying is: 1) there is no 20+ years of existing literature as
there is for C and C++, so 2) this library should provide the equivalent of 20+
years of literature. The bottom line is that the responsibility of the
documentation stops after documenting itself. Anything beyond that is a
freebie--the documentation is going above an beyond the call, so-to-speak. I'm
not against that, but I'm hearing nothing but "everything is poorly done".

> Alright, leaves the standard(s). I worked through all
> examples there, but none of them shows the "recursion
> problem" (at least in the C++ standard). Sure, finally one
> gets to the point where you understand what they are talking
> about, but it takes (too) long. You start reading this chapter

I agree that the standard is not elucidating. I disagree that it is my
_responsibility_ (via the documentation) to elucidate it, regardless of how
useful it might be to do so. Frankly, the lack of recursion is an fundamental
aspect of the way macro expansion works. If you don't know that (and I don't
mean all the details down to the subatomic level), you probably aren't *ready*
to use this library.

> And so on. Among all of that you have to find the right few sentences.

Yep. That's because it is a technical specification, not an explanation. Yes,
it is difficult to follow and get all the details. Yes, the elucidation that
you want would be useful. No, it is _not_ my responsibility to do so--even
though I'm already in the process of doing exactly that for macro expansion. On
top of all this, I'm doing it in my spare time for free. Just like I'm spending
hours on this conversation (and putting off other things that I'm working on).

> First, text read by humans is different from text read by
> computers. Due to the inherent imprecision of natural
> language, and the necessity for the reader to anticipate
> meaning (a well-know psychological fact that without
> anticipation no understanding), the reader constantly goes
> into wrong directions, and only redundancies (saying
> important things at many places (with different words)) can help him.

Technical documentation needs to be concise. I shouldn't have to say the same
thing twice. If the reader doesn't understand something later, they can go back
and re-read it.

> And second, if the documentation would be organised like
> software, this might actually help:
> Every file(!) (not just every translation unit) is supposed
> to include for example <algorithm> again and again ---
> accordingly one would at least expect at every reference for
> some construction (corresponding to a source code file) a
> *link* to the relevant general sources here ("including"
> that what needs to be known to understand the reference).

I agree with this--as long as it is about library-defined issues. E.g. I don't
have a problem with providing cross-references to material on algorithm states.
I do have a problem providing cross-references to basic preprocessor
functionality every time it might possibly be relevant (which is just about
everywhere).

> > > Second of all it seems the current facilities in the
> library should
> > > be enhanced (though I don't understand how FOR and FOR_r
> is supposed
> > > to be used): BOOST_PP_SEQ_FOR_EACH is such a natural and
> easy thing,
> > > the use of it should be promoted, while FOR and its
> derivates look
> > > ugly.
> >
> > Hmm. FOR is far more general. SEQ_FOR_EACH can be
> implemented with
> > FOR, but not vice versa.
> >
>
> sure --- in other words, FOR_EACH is easier to use!

Sure, as are a whole lot of other things that are just as easy to use as
FOR_EACH. IOW, it is nothing special; it is one of many, etc., etc..

> > It will only break if SEQ_FOR_EACH or LIST_FOR_EACH arbitrarily
> > decides to use the other--which won't happen. I design the
> algorithms
> > in the library, and I'm well aware of vertical dependencies
> and their implications.
> >
>
> I was referring to the documentation, which seems not to
> speak about this issue.

The documentation says that certain constructs are reentrant. It doesn't say
for every non-reentrant macro that it is non-rentrant--because that is the
status quo, the "normal" situation which is not defined by the library and thus
is not the responsibility of the library to document.

> > > According to what I understand from the documentation,
> the natural
> > > solution, offering for example BOOST_PP_SEQ_FOR_EACH_2,
> which uses
> > > macros guaranteed not to occur in BOOST_PP_SEQ_FOR_EACH, has been
> > > deprecated in favour of a more complicated solution.
> >
> > You don't understand the implications of what you're saying.
>
> sure

I'm not saying that to be derogatory, but I am saying that out of frustration
because you immediately started pronouncing what should be and what I should do
without knowing any of the implications of your pronouncements nor any of the
rationale behind the design of the library or what is and isn't in the
documentation. You didn't ask *why* something is the way it is. Instead, you
ran into problems, and then said that everything should be better (so that you
wouldn't have run into problems). I'm also not belittling the problems that you
ran into. Preprocessor metaprogramming can be tricky, is very foreign to
typical programming models, and has a steep learning curve. Sure, the
documentation isn't perfect, but sometimes learning curves are inherently steep.

> > In order to do
> > that, I'd need to define hundreds of macros to implement
> SEQ_FOR_EACH
> > alone. It isn't as simple as just replicating the
> interface macro a
> > few times. Instead, I'd have to reimplement the entire
> algorithm from
> > scratch--intentionally avoiding the use of any other
> higher-order algorithm.
> >
>
> the whole thing looks quite complicated; why not adding a FAQ
> page to the documentation (I was looking for one, but didn't
> find one) answering for example my above request in this way?
> I think it would have helped me.

Because I don't want to implement the library in the documentation. These are
implementation details and if I introduced those into the documentation, the
documentation would be orders of magnitude more complex.

> > I'm not sure what you're referring to by "more complicated
> solution".
> > If you specify what kind of thing your referring to, I'll expound.
> >
>
> The documentation says
>
> In particular, the library must provide reentrance for
> BOOST_PP_FOR, BOOST_PP_REPEAT, and BOOST_PP_WHILE. There are
> two mechanisms that are used to accomplish this: state
> parameters (like the above concatenation example) and
> automatic recursion.
>
> Thus it seems only for these loops you have a special
> mechanism for reentrancy, and this special, "generic" mechanism"
> is what I mean with "more complicated" : for the user it's
> easy to use BOOST_PP_SEQ_FOR_EACH and
> BOOST_PP_SEQ_FOR_EACH_2, but it's complicated to use the
> generic method.

Except that you're wrong. You run into massive dependency nightmares if you do
that. It isn't simpler--it creates problems that are very difficult for users
to track down.

> > The documentation has many examples, but you call them either
> > complicated or contrived.
>
> Let's go to the example section:
>
> First we have "array_arithmetic.c": It says "This example
> implements over 2200 functions".
> Sounds impressive, but is not an example (sounds more like an
> extension of the library).

No, it's an example. The library is a code generator, not a library of
generated code.

> Among those six examples only "catch_builtin.cpp" seems to be
> close to an example (which is something you look at and you
> understand).

I disagree with your definition of an example. Any code that uses an interface
is an example of using that interface. Whether or not it is a good example is
another issue. However, the library includes many very simple examples--such as
the example for SEQ_FOR_EACH:

#define SEQ (w)(x)(y)(z)
#define MACRO(r, data, elem) BOOST_PP_CAT(elem, data)

BOOST_PP_SEQ_FOR_EACH(MACRO, _, SEQ) // w_ x_ y_ z_

That is a simple example that clearly shows what SEQ_FOR_EACH does without
focusing on an overarching (complicated) problem to be solved. The library is
littered with such examples.

> In the reference section there are actually simple examples,
> but as far as I can see always only one example: I believe a
> systematical declination of the elementary cases would be
> very helpful (always staying simple, just handling simple
> sequences, but with example
> 5 to BOOST_PP_SEQ_FOR_EACH for example on would show a nested
> loop ("without added meaning", that is, no template stuff etc.)).

Why would I show a nested loop in an example for SEQ_FOR_EACH when SEQ_FOR_EACH
cannot be called from the repeated macro invoked by SEQ_FOR_EACH?

> > What exactly to you want? This kind of stuff doesn't boil down to
> > _simple_ use cases.
>
> My understanding is that use cases are not very helpful. What
> we need is *understanding*.

Yes.

> ??? What does this want to say to us? What are "graphs" etc.
> ??? But shouldn't one know all these things!! And it's a very
> nice fundamental property of graphs, just *using* addition in
> two ways.

The difference between this mock example and the library's examples is
significant. The templates (or anything else) that appear in the examples
aren't what the examples are about. They are *output* of the example--not part
of the functionality of the example.

> sum_{i=1}^n i = n (n+1) /2.
>
> Hope you get what I mean.

I get what you mean, but I think the simple examples are already there. I just
don't think you invested much effort. It basically seems that if you saw
"template", you just immediately ignored the example.

> > Rather, typical use cases are parts of overarching designs
> that are
> > complex--which is precisely the reason that preprocessor
> > metaprogramming enters the picture at all.
>
> As I said, I don't want use cases, I just want to use the
> preprocessing library (for my own purposes).

Okay, but your example of using the library to generate an enum is a use-case.
Sure, it's a simple one, but it is only simple because it is naive to the point
of being destructive.

> > Furthermore, I have a responsibility
> > (as does any library author) not to promote naive or poor
> uses of the
> > library--which is almost always the case with examples that
> implement
> > something simple.
>
> So the documentation is the guard, meant to be as a kind of
> torture, and only those worthy souls which get through shall
> use the library?

This actually makes me smile.

No, the documentation is not meant to be that, but the learning curve is
inherently steep, and such learning requires the learner to expend a certain
amount of effort--moreso than when learning something simpler.

What I said was true. I have a responsibility not to promote naive or poor uses
of the library. The user that is learning the library has not yet attained the
degree of aptitude required to separate the way something is achieved (the point
of the example) from what is being achieved (the medium of the example).

> Speaking for myself, I never ever found any interest in those
> "real world scenario's"
> (for this library and others), since their "real world" is not mine.
> Of course, other might disagree, and having a healthy mix of
> simple and more complicated examples sounds like a good thing.

It is a good thing. What you seem to be suggesting, as far as examples are
concerned, is somewhere in the middle. Something that I would call a trivial
use case. But for the reasons that I already mentioned, such examples can be
dangerous. Thus, I tried to either make them full-fledged and concrete or
utterly simple and contrived. (There were a few times that I got bored of
writing ultra-simple examples, so I made them a little more elaborate, but those
are almost always in the interfaces targeted towards more advanced users.)

> > The library 1) shouldn't have to announce this problem
>
> I meant the documentation

As did I.

> > and 2) already does
> > announce this problem--it just doesn't do it hundreds of times in
> > hundreds of different places. If you are writing code in
> the language
> > defined by the preprocessor, you should *at least* know the
> basics of
> > that language. The library is not the language, it is merely a
> > systematic set of reusable constructs written in that
> language. The
> > documentation's only responsibility is to document itself.
> >
>
> yes, but neither you are an angel (so that you could give a
> precise definition of the whole
> thing) nor am I (so that I could read it); instead we are
> human beings nd mst fnd r wy thrgh ntcptn (there are nicer
> examples; but hopefully this works a bit:
> just looking at it I hope you thought huh, but knowing that I
> left out the consonants it shouldn't be too hard; I have seen
               ^^^^^^^^^^ vowels?

> a very nice series of examples in this style, where you were
> guided to read more and more strange stuff, and at the end
> you were presented with a plain English sentence --- and you
> couldn't read it anymore!).

You're right that documentation has to be more than just a formal definition.
However, the point remains that it isn't the job of the documentation of a
library written in language X to document language X.

> > Regarding examples in the
> > documentation, I don't think there is a way to please you
>
> perhaps there are ways: I believe actually in small steps,
> massaging something reasonable here and there so that it gets
> better. If some of the above thoughts would lead to some
> additions here and there (a few remarks, hints) in the
> documentation, that would mean progress.

Out of curiousity--do you believe there are certain things that have inherently
steep learning curves? What you're suggesting here is basically that I flatten
the learning curve by gradually introducing complexity. My argument is that
there are times that complexity cannot be avoided or compartmentalized into
small, easily understood, chunks. There are certain steps along the road that
are transactional--all or nothing.

> > Regarding the technical implications:

> I can understand the general point of view. But this general
> point of view might not necessarily be the point of a user
> (who in general will just occasionally use the library).
>
> Perhaps it might be a good idea to add the above explanation
> somewhere to the documentation?

IOW, add implementation details to the documentation? As far as the reentry
stuff is concerned, all that is necessary for users is to know is that macros
cannot be used inside themselves (i.e. common knowledge). The places where that
rule is broken (meaning that the library makes it look like it is breaking the
rule) are explicitly noted. Users need not ever deal with state
parameters--they only need to know that macros will not expand inside
themselves. Thus, the macro passed to (e.g.) SEQ_FOR_EACH, that is invoked by
SEQ_FOR_EACH, cannot use SEQ_FOR_EACH. Otherwise, the library makes it work
automatically. You aren't required to use SEQ_FOR_EACH_R--or any interface that
requires you to mess around with an algorithm state. Instead, you can ignore it
completely.

> So if I understand it correctly, you currently have three
> independent looping constructs BOOST_PP_FOR, BOOST_PP_REPEAT,
> BOOST_PP_WHILE, while all other loops are implemented in
> terms of these?

There are actually more than that, but the library makes it appear that there
are only a few algorithmic states (e.g. 'r' for FOR, 'z' for REPEAT, and 'd' for
WHILE), and therefore that there are only a few algorithms implemented this way.

It used to be the case that, because SEQ_FOR_EACH was implemented in terms of
FOR, you couldn't use SEQ_FOR_EACH inside FOR. You *had* to use SEQ_FOR_EACH_R.
I removed that kind of limitation. You cannot use SEQ_FOR_EACH inside itself,
but you need not deal with the algorithm states (like 'R') at all if you don't
want to. This makes the library a great deal easier to use.

> Then it would make sense to concentrate on the documentation
> of these three macros, giving more examples (showing also how
> to simulate nesting), and emphasising for e.g. FOR_EACH its
> special character.

It isn't FOR_EACH that is special, it is the ones that are explicitly designed
to be reentrant that are special.

> As far as I can see from the material provided on more
> powerful pp-libraries, the Boost pp-library won't be further
> developed, but at some time replaced?

Not necessarily. However, it is near what I can do with it either because of
portability issues or efficiency concerns with various preprocessors. I have
tried adding various things to it, but it is extremely difficult to keep the
library stable on some very popular compilers.

Things are improving. As far as broken preprocessors are concerned, there are
only a few remaining roadblocks--the most significant being VC++. As far as
efficiency is concerned, the major roadblock has always been EDG-based
preprocessors (notoriously slow). I haven't seen proof of this, but
*supposedly* the EDG preprocessor has been modified to use memory much more
efficiently during macro expansion, and the previous way that it was using
memory was *supposedly* the source of the major slowdown. (This is in an EDG
version that is not yet available in any other compiler.)

The speed issue is not as big an issue as the utter broken-ness of VC++ in
particular. When and if VC++ and the other two broken preprocessors, Sun and
IBM, fix their preprocessors, the library won't be replaced so much as heavily
refactored. Some other refactoring is due anyway in preparation for variadics.
As an immediate example, SEQ_FOR_EACH's interface needs to change from:

SEQ_FOR_EACH(macro, data, seq)

to:

SEQ_FOR_EACH(macro, seq, data)

in preparation for it becoming:

SEQ_FOR_EACH(macro, seq, ...)

Of course, when and if that happens--which depends on the vendors--a lot of the
limitations that you've run into will be gone. SEQ_FOR_EACH would be just as
reentrant as anything else.

Regards,
Paul Mensonides


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