
On Tue, 04 May 2010 10:06:39 -0600, Zach Laine <whatwasthataddress@gmail.com> wrote:
On Thu, Apr 29, 2010 at 2:12 AM, Roshan Naik <roshan_naik@yahoo.com> 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.
There is no requirement to inspect the lref. If evaluation of the relation succeeded.. the lref will be initialized. The idea that a relation can have 0 or more values/solutions is embedded in the theoretical definition relation. It is not a Castor concept. It is fundamental to this paradigm. I will being talking a bit about the concept of a "relation" and how it relates to concept of a "function" in my BoostCon talk. Most other ideas such as "direction-less" or "bi-directional" arguments merely follows from it.
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.
Having been a traditional programmer, my head hurt too when I encountered this paradigm. Give yourself some time to settle with it.
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.
It is neither an assertion or a generative query. franksChildren is also a relation (like any other) which can be used to assert or generate. It is merely constraining the first argument to the father relation. You read as "Frank is father of c".
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";
Good question. There is a fundamental issue with the iterator/enumerator model which makes it unsuitable. The key difference is that in the iterator model divides the following into separate steps: 1) "is there more solutions" : begin()!=end() 2) "get me next solution" : operator ++ and you basically repetitively ask "is there more" and if yes.. "compute it". In many cases this is ok. But in general you cannot expect that it is possible to answer the "is there more" question without actually performing the computation to see if there is more. It may sound a bit like the halting problem. The model used in the logic paradigm is to repetitively ask "get me the next solution if there is one". The two steps are combined into one. The suggested syntax : for (const std::string& child : franksChildren) { .. } wont scale when enumerating with father(f,c) where both f and c are not initialized. But you could use this syntax if it makes any difference : for( relation franksChildren = father("Frank", c); franksChildren(); ) { ... }
* 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; }
The motivating issue for Disjunctions/Conjunctions/ExDisjuntions is that this statement is not allowed: relation result; // must be initialized As a relation must be defined. But this is semantically different: Disjunctions d; // OK This represents a collection of empty disjunctive clauses and evaluates to false if there aren't any. I am toying with this idea of +=. relation gender_dyn(lref<string> p, lref<string> g) { Disjuntions result; for( ... ) result += eq(p,i->first) && eq(g,i->second); return result; } The reason it was initially not implemented that way was because there is push_front() and push_back() methods for dynamic relations. The &= or |= operators do not have a symmetric equivalents to prepend. It now appears that push_front() is used much less in practice.. so I am thinking it may be worth it to simplify the syntax for the common push_back case with +=.
* 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 ||?
In retrospect, I would like to have used & and | instead of && and ||. But I think its too late now. For this case, += can be used for push_back() instead of three different ones.
This would also remove the need for extra parentheses in some cases, since the precendences will be more natural.
indeed.. thats the main reason for the bitwise operators over the logicals. But, the precedence issue is true when clauses are specified in conjunctive normal form ... which happens to be common in examples in the tutorial.
* 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.
I do not recommend using sequence() anymore. It has become too complex internally than I hoped it would. Its purpose would probably be more obvious to people with Prolog background... to do some pattern matching style unification for lists. In the context of LP within C++, I currently feel that such a mechanism is not as useful as it is in a pure LP environment like Prolog.
* 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.
Guilty as charged. The more natural definition ExOr ^ is not very useful at all in the context of LP. Rather than being pedantic about it... the semantics were slightly modified to short-circuiting which is not the same as the pure definition. It was a usability/readability decision. Since there were only 3 primitive operators to begin with, I felt teaching it wont be an issue. I didn't want to introduce an strange looking operator with a brand new name.
* 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.
That leads to overload resolution issues and also I prefer eq_f separate from eq for clarity of semantics. Though I am thinking of collapsing eq_f and eq_mf into eq_f with some bind mechanism. Boost bind doesn't do the trick as it will not automatically dereference the lrefs involved at the time of evaluation. These were primarily provided to improve readability over the equivalent syntax involving bind.
* 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.
Not sure what you mean. predicate is an adapter relation used to turn bool functions/function objects into relations which can then be combined with other relations. You cannot be use them directly.
Some editorial notes:
Yes thanks very much.