
On Wed, 05 May 2010 09:10:25 -0600, Zach Laine <whatwasthataddress@gmail.com> wrote:
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.
Having to use a nonstandard idiom for iteration to get the functionality of your library is what I'm objecting to.
True.. its is canonical to traditional C++ given the existing paradigm mix. Iterators were designed as the "glue" between algorithms/containers/streams. However in LP, all three of them are represented by relations... so iterators are are not that useful. Initially (few years back) I pursued some iterator based designs (among others) which fell apart very quickly. I only recall they didn't fit in very well. All the details escape me now.. But the core idea of being able to dereference an iterator to get a *single* value is at odds with mode of operation here. Coroutines eventually emerged as the more appropriate solution. Introducing iterators based generation of solutions also gives the false impression that this could somehow be used in conjunction with STL's iterator based algorithms... as the *iterator would only produce true/false regardless of the relation. Your interest in iterators seems to be stemming from the desire to use a for each (or range based for loop) like construct to go over all solutions. These are designed to simplify working with iterators... I feel it is more appropriate to extend these looping mechanisms to accomodate coroutines rather than the other way around.
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. [..snip..] It is the same semantic difference as find() vs. find_all().
Ah! When you say find().. you are explicitly requesting first result (even if there is more than one) and when you say find_all() you want all results. In LP too it is explicit : First result only (even if there are more): if(franksChildren()) ... All results: while(franksChildren()) ...
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?
I will need to see a more fleshed out design to comment on this. And see how some of the existing samples in the tutorial would look. This is deeply ingrained decision and not an orthogonal one. BTW how can operator * on the iterator by used to access the multiple in/out parameters ? Too time consuming to list all issues. One of them ... It is likely to imply that when relations are defined imperatively as coroutines, they will be required to define custom iterator objects... which are a huge pain (way too many members!). Also the upcoming lambda support will complement the coroutine style definitions nicely. It is much easier to imperatively define a relation/coroutine using lambda as compared to iterator objects... // psuedocode relation blah(x,y) { return [=] { .. ; yield true; ..; } }
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()?
Conceptually, the three classes Dis/Con/ExDis-junctions correspond to con/dis/exdis-junctive forms of clause definitions. The type relation has no such association, it is totally free form. The type relation has no such affiliation. So with the three classes, performing += is more naturally suggestive of the implied precedences of the internal operators within the final expression after a series of += (i.e. push_back) operations. I chose not build an implicit semantics at the lowest level concept of type relations. If relation is defaulted to False(). Then realtion&=blah() ... would be False()&&blah(). Which is a problem. But with relation|=blah would be False()||blah() which is not a problem. They could also be just blah(). But it is not distinguishable if relation was default initialized to False or explicitly initialized to False() ... only in the latter case you want to preserve it. I think there were some minor efficiency issues as well... which will take me some effort to recall now. I felt it was not a great idea to build any intelligence into such a low level concept as type relation. That said, i think this still an open case.... just needs to more time to actually experience the trade offs. I think it was good to be conservative decision for an early design stage. It easy to add this into relation .. but removing is near impossible.
Why is it too late?
Compatibility and lack of significant real benefits. The precedence benefits are only if your clauses are in disjunctive normal form. Not otherwise. I have been recently considering having both && and &. Also || and |. As they both are useful in different situations.
Don't get me wrong -- I like the use of operator^(), as I said. Just call it something else. :)
Suggest a better name :) it might happen.
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?
If the obj has a member bool operator()() defined... we cannot say whether eq_f semantics or eq semantics should be applied. It is possible today to apply eq() semantics to objects that have operator().
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.
Oh. An ILE like (i==j) is function obj that returns bool and takes no args. Thus it type erases to relation. But it isn't a relation as it is a subroutine and not a coroutine. No general way to determine if its a truly a relation.
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.
In this case the best place is to probably place the assertion inside father itself. Isn't it ? Boy... I am getting a rigorous mental workout answering all your insightful probing questions. :) -Roshan