
On Tue, May 4, 2010 at 2:58 PM, Roshan <roshan_naik@yahoo.com> wrote:
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.
Right. I understand this bit now.
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.
I understand that, and I'm not trying to get you to change the 0 <= N results semantics. I'm just indicating that the canonical way to communicate that in C++ is with a pair of iterators.
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.
It's not the N results paradigm. It's the use of a function object and an extrinsic value that gets repeatedly assigned over, instead of a pair of iterators, that hurts my head. Having to use a nonstandard idiom for iteration to get the functionality of your library is what I'm objecting to.
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".
I think you might misunderstand me. I'm pointing out that, as a reader of some code who happens not to be that code's author, I'd like to be able to inspect the code and predict to a reasonable degree what the code does. Knowing whether "while( franksChildren() ) {...}" will *evaluate* to <= 1 result, vs. <= N results, is significant. It is the same semantic difference as find() vs. find_all(). Either might fail to find what it's looking for, but having the two functions instead of just find_all() makes the code more literate. You yourself draw this conceptual distinction between assertion and generative queries in the documentation, no?
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 ++
There's no fundamental reason why you should be limited by the use of iterators. The iterator constructor invoked for the begin() iterator evaluates the relation once (i.e. it is very much like relation::operator()). If the evaluation succeeded, operator*() will return the result; otherwise, the begin() iterator evaluates as equal to the end() iterator, indicating that the sequence is empty, and that it is unsafe to either dereference or increment the iterator. operator++() simply repeats this process. Am I missing something?
for (const std::string& child : franksChildren) { .. }
wont scale when enumerating with father(f,c) where both f and c are not initialized.
Why is that? relation::operator() works in this case; why can't it work if wrapped in an iterator?
But you could use this syntax if it makes any difference :
for( relation franksChildren = father("Frank", c); franksChildren(); ) { ... }
The problem with this is that I not only have to *write* a "c" to catch the results on the stack, but "c" outlives the loop, which is the only place it's actually useful. I prefer to have my loop variable scoped to my loops. In short, we lose the concision of the new for loop syntax, and the scoping of plain old C++ 98 for loops. Anything extra is just noise. In brief, I don't think you should change anything about how relation currently works, including its interaction with lref<>. I just want you to take care of the bookkeeping variables for me, hiding the details behind an iterator interface.
* The way dynamic relations are constructed seems needlessly at odds with the way static relations are contructed. [snip] 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:
Is there a specific reason for that? Is there a reason why relation()() can't just return false? If so, why not default construct relation the same way, just as Disjunctions initializes its clauses member with False()?
I am toying with this idea of +=.
I'm just trying to keep these relation-implementation types out of users' minds. They don't ever need to know about Disjunctions, Conjunctions, etc., if they can just write |=, &=, etc. It will make the user code more expressive.
* 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.
Why is it too late?
* 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.
Don't get me wrong -- I like the use of operator^(), as I said. Just call it something else. :)
* 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.
Ah. What was the overload resolution problem?
* 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.
You wrote this in your Design and Implementation doc: "Early on, support for creating relations directly out of a boolean ILE expressions without need for an “adapter” relation like predicate was implemented, but this facility was later withdrawn as it ran into several problems including some limitations imposed by C++. The current design still leaves the door open to revisit this facility in the future (perhaps in C++0x) with an eye towards preserving compatibility."
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.
Right. And I'm asking why not. I'm mainly just curious. As an aside, I can write this: lref<int> c; // <- Oops! relation franksChildren = father("Frank", c); and I get this error: includes/refcountedptr.h:98: error: no matching function for call to std::basic_string<char, std::char_traits<char>, std::allocator<char>
::basic_string(const castor::lref<int>&)
It would be nice if it were impossible to mix these due to BOOST_STATIC_ASSERT/static_assert enforcement of type in lref<>, which would provide better feedback. Zach