Boost logo

Boost :

Subject: Re: [boost] [castor] Interest in Logic Paradigm for C++ ?
From: Zach Laine (whatwasthataddress_at_[hidden])
Date: 2010-05-04 12:06:39


On Thu, Apr 29, 2010 at 2:12 AM, Roshan Naik <roshan_naik_at_[hidden]> wrote:
> Based on suggestions from some Boost community experts, I would like to
> gauge interest to see if there is broader interest for including the Castor
> library into Boost.

This library looks really promising! I look forward to using it. I have some
suggestions about the interface and documentation.

* The semantics of generative queries strike me as wonky. I can see the appeal
of assigning the result to an uninitialized argument, when there can only be
exactly one result returned. However, there may be 0, 1, or N. That alone
lessens the appeal of returning the results in the argument. Moreover, you're
requiring users to then query the actual result relation to see if it's okay
to dereference the lref<> parameter to get at its result.

Then, you have each call to relation::operator() act as a loop iteration,
complete with assignment to the lref<>, and even resetting the lref<> to an
uninitialized state.

And now my head hurts. ;)

Since all of this is implicit, someone reading my code, or myself in a year
and a half, will probably be left scratching his head.

Further, at the call site:

relation franksChildren = father("Frank", c);

How do I know if this is semantically an assertion or a generative query, when
I'm reading someone else's code and trying to understand it? I have to know a
lot more context than I'd like. I have to look for all possible places that
"c" might come from; whether none, some, or all of them are uninitialized; and
which are and which aren't.

So, instead of:

lref<string> c;
int count=0;
relation franksChildren = father("Frank", c);
while( franksChildren() ) {
   ++count;
   cout << *c << " is Frank's child\n";
}
// c is now back to its uninitialized state
cout << "Frank has " << count << " children";

How about:

// Maybe "any_lref" is a weak name, but you get my point. Provide a tag type
// and use it to make assertion-queries explicit.
relation franksChildren = father("Frank", any_lref);
for(relation::const_iterator it = franksChildren.begin();
    it != franksChildren.end();
    ++it) {
   cout << *it << " is Frank's child\n";
}
cout << "Frank has " << franksChildren.size() << " children";

Or, in the style of Boost.ForEach or the upcoming standard:

relation franksChildren = father("Frank", any_lref);
for (const std::string& child : franksChildren) {
    cout << child << " is Frank's child\n";
}
cout << "Frank has " << franksChildren.size() << " children";

* The way dynamic relations are constructed seems needlessly at odds with the
way static relations are contructed. Instead of:

list<pair<string,string> > genderList = ...;

Disjunctions gender_dyn(lref<string> p, lref<string> g)
{
    Disjunctions result;
    list<pair<string,string> >::iterator i;
    for( i=genderList.begin(); i!=genderList.end(); ++i)
        result.push_back(eq(p,i->first) && eq(g,i->second));
    return result;
}

How about:

relation gender_dyn(lref<string> p, lref<string> g)
{
    relation result;
    list<pair<string,string> >::iterator i;
    for( i=genderList.begin(); i!=genderList.end(); ++i)
        result |= eq(p,i->first) && eq(g,i->second);
    return result;
}

... and you handle the construction of Disjunctions/Conjunctions/Whatevers for
me, under the covers?

* The asymmetry between the use of && and || with ^ is a little bothersome,
especially considering the desirability of using &=, |=, and ^= when building
relations dynamically. Could you use & and | instead of && and ||? This
would also remove the need for extra parentheses in some cases, since the
precendences will be more natural.

* What is the rationale behind providing both C++-style iterator iteration and
head/tail iteration? Are there some cases where the former is more useful
than the latter, *and* some other cases where the latter is more useful than
the former? If not, could you just standardize on one approach?

* I find at least part of the interface to sequence to be confusing. When
reading this:

relation r =
   sequence(lrl)(lri)(v.begin(), v.end())
             (3);

a newcomer to your library will certainly be completely lost.

* While I like the use of operator^() to provide cut-like effects, it shouldn't
be described as XOR as you're using it. XOR has pretty different semantics.
For one, since XOR evaluates to true when its operands are unequal, it can't
do short-circuiting any more than the equals relation can.

* Can eq_f(), eq_mf() just go away, in favor of using std::bind/tr1::bind
instead? You could just provide an overload if eq() that takes a
std::function/tr1::function as its second parameter.

* What were the limitations that caused you to retain predicate()? I can see
how you might still need it in some situations, but most of the time I'd like
to use my bool functions/function objects directly instead.

Some editorial notes:

* In 2.7 Inline Logic Reference Expressions, I don't think you meant to say:

multiply(sq,n,n) && multiply(cu,sq,n)( );

Shouldn't it be:

(multiply(sq,n,n) && multiply(cu,sq,n))( );

?

* Also in that section, you introduce predicate() by using it. It would be
better to introduce predicate() first, or at least to make it clear that it's
a library function, and will be explained more fully in section 4. This
comment applies universally, since this seems to happen somewhat frequently in
the documentation.

Zach


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