Boost logo

Boost :

Subject: Re: [boost] [castor] Interest in Logic Paradigm for C++ ?
From: Zach Laine (whatwasthataddress_at_[hidden])
Date: 2010-05-05 11:10:25


On Tue, May 4, 2010 at 2:58 PM, Roshan <roshan_naik_at_[hidden]> wrote:
> On Tue, 04 May 2010 10:06:39 -0600, Zach Laine
> <whatwasthataddress_at_[hidden]> wrote:
>
>> 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.
>
>
> 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


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