eclipse-ocl / org.eclipse.ocl

Eclipse Public License 2.0
0 stars 0 forks source link

[language] Inconsistent Set signature deduction #172

Closed eclipse-ocl-bot closed 13 hours ago

eclipse-ocl-bot commented 13 hours ago

| --- | --- | | Bugzilla Link | 184329 | | Status | RESOLVED WONTFIX | | Importance | P3 normal | | Reported | Apr 26, 2007 16:49 EDT | | Modified | Jun 01, 2015 14:17 EDT | | Version | 1.1.0 | | Blocks | 318248 | | Reporter | Ed Willink |

Description

In:

Sequence{}->append(e1)->append(e2)

where e1 and e2 are elements distinct classes E1, E2, with a distant common super type E.

Sequence{}->append(e1) is resolved as\ Sequence(E1)::append(E1) : Sequence(E1)

but the subsequent\ Sequence(E)::append(E) : Sequence(E)\ for Sequence(E1)::append(E2) is reported not found.

eclipse-ocl-bot commented 13 hours ago

By Christian Damus on May 03, 2007 09:36

Actually, I'm not sure that this expression should be valid. OCL declares, for Sequence(T)

append(object: T) : Sequence(T)

The argument type is the same as the source element type, so only for Sequence(E) would it be possible to add elements of type E1 and E2.

The only guidance offered by the OCL specification on the subject of empty collections is in 8.3.7, in which it indicates that the element type of an empty collection literal is OclVoid. This implies that the following is not valid:

Sequence{}->append(e1)

because E1 does not conform to OclVoid.

Doing the following works:

let s : Sequence(E) = Sequence{} in s->append(e1)->append(e2)

because OclVoid does conform to E, so the initialization to empty set is OK.

eclipse-ocl-bot commented 13 hours ago

By Ed Willink on May 03, 2007 14:59

I'm not sure which (if either) of the append deductions is wrong.

My initial reaction in checking the error that I had for this expression was that the expression was bad. (The example came from a user with an evidently different OCL.)

But then I read the difference between OclAny and OclVoid which on the one hand are bottom and top types, but OclVoid is also described as a minor tweak to provide a common type for sets - completely inconsistent descriptions.

However, if

Set(OclVoid)::append(E1)

provokes a search to discover the solution

Set(E1)::append(E1)

then I think Set(E1)::append(E2)

should similarly search to discover

Set(E)::append(E)

Fundamentally there is a collection operation for which the expression is valid,\ as demonstrated by priming the answer for your let example.

eclipse-ocl-bot commented 13 hours ago

By Christian Damus on May 03, 2007 15:16

Yeah, this is one of those grey areas. I think that the resolution of Set(OclVoid)::append(E1) as Set(E1)::append(E1) is just wrong, a bug introduced by the support for initializing a variable to an empty set as in the let example.

OCL would be much more usable if the append and similar operations could progressively generalize a collection's element type. After all, each result is a new collection, so this should be entirely safe (OCL generic collections are very different from Java generic collections).

However, I think the right thing to do will be to make Set{}->append(e1) fail to parse.

eclipse-ocl-bot commented 13 hours ago

By Christian Damus on May 24, 2007 12:26

Will have to consider what to do about this in the next release.

eclipse-ocl-bot commented 13 hours ago

By Ed Willink on Jun 24, 2009 12:57

This probably changes between OCL 2.0, 2.1 so we probably want to parse and then do different thinks semantically.

eclipse-ocl-bot commented 13 hours ago

By Ed Willink on Oct 26, 2009 03:07

I have submitted a resolution to Issue 12953 that requires

a) the type of Sequence{} to be chosen to avoid errors in context

b) introduces the option of Sequence(E){}

OCL 2.0 and OCL 2.1 (8.3.7 CollectionLiteralExp last invariant) define the type of Sequence{} as Sequence(OclVoid) making Sequence{}->append(e1) an error.

The validator should enforce this for 2.0/2.1 and allow a ParsingOptions to support the relaxed resolution.

b) has been demonstrated as part of Bug 292112.

eclipse-ocl-bot commented 13 hours ago

By Ed Willink on Nov 12, 2009 02:16

Bug 288569 has been marked as a duplicate of this bug.

eclipse-ocl-bot commented 13 hours ago

By Ed Willink on May 04, 2011 12:17

OCL 2.3 specified that unspecified collection element types be deduced to be well formed. This is partially implemented by the Pivot model using a concept of an UnderspecifiedType. It is a nightmare.

cf Java or C++, where it is possible to know that XXX is a type or non-type (.class suffix for Java, typename prefix for C++). It is not possible in OCL, since XXX may be a type or an unnamed opposite property end. This makes overload resolution difficult, since argument types are needed to choose an overload but the overload is needed to choose the source and consequent argument types.

Now that OCL supports collection literals, it may be possible to deprecate underspecified types. Discussion planned for OCL 2011 workshop.

eclipse-ocl-bot commented 13 hours ago

By Ed Willink on Jul 18, 2013 06:02

(In reply to comment #8)\ Following a newsgroup coment I tried the folowing JUnit tests

assertQueryResults(null, "Set{1,2,3,4}",\ "let reals : Set(Real) = Set{1.0,2.0,3.0},\ nats : Set(UnlimitedNatural) = Set{2,3,4}\ in reals->union(nats)");

assertQueryResults(null, "Set{1,2,3,4}",\ "let reals : Set(Real) = Set{1.0,2.0,3.0},\ nats : Set(UnlimitedNatural) = Set{2,3,4}\ in nats->union(reals)");

The first passes, the second fails because the operation cannot be found. This seems really unhelpful.

Branch edw/184329 contains a quick hack to define the source parameterisation as an UnspecifiedType with the actualType as an upper bound. This gets the unit tests passing, but 43 other tests fail. Not entirely surprising for a hack. The template parameter analysis needs revisiting to accommodate ranges properly.

eclipse-ocl-bot commented 13 hours ago

By Laurent Goubet on Jul 18, 2013 07:34

(In reply to comment #9)

assertQueryResults(null, "Set{1,2,3,4}", "let reals : Set(Real) = Set{1.0,2.0,3.0}, nats : Set(UnlimitedNatural) = Set{2,3,4} in reals->union(nats)");

assertQueryResults(null, "Set{1,2,3,4}", "let reals : Set(Real) = Set{1.0,2.0,3.0}, nats : Set(UnlimitedNatural) = Set{2,3,4} in nats->union(reals)");

Ed, I'm seeing this from afar but ... the failure in your example seems perfectly natural to me?

Unlimited is a Real, but Real is not Unlimited, so adding unlimited naturals in a set of "Real" seems legit, but adding Reals in a set of unlimited seems very wrong. Much like I can add Cats in a set of Animals, but not Animals in a set of Cats.

I do not think that it is a good idea to try and support this second example (or I didn't understand something :D).

eclipse-ocl-bot commented 13 hours ago

By Ed Willink on Jul 18, 2013 08:34

Hi Laurent:

I've shared your view at times, which is perhaps why this is still open.

But in OCL x->union(y) is not adding y to x; it is creating a union of x and y.

[One might hope for a smart future implementation that does spot that x has gone stale and so reuses it and performs addition and perhaps type evolution]

My first JUnit test was actually Set(String)::union(Set(Integer)) for which a Set(OclAny) is a consistent result of using the Set(OclAny)::union(Set(OclAny)) operation that exists in the library.

When writing complex OCL expressions, I have found it irritating to have to do

nats.oclAsType(Real)->asSet()->union(reals)

in order to workaround the analyzer's refusal to use an available operation.

[It's not as irritating as it used to be since the hovertext now gives you better insight into the problem.]

eclipse-ocl-bot commented 13 hours ago

By Laurent Goubet on Jul 18, 2013 09:55

Ed,

But in OCL x->union(y) is not adding y to x; it is creating a union of x and y.

Well, that's true in all languages offering "union", even still, when I see a "union" method, I'd expect a signature such as guava's :

public static Sets.SetView union(Set<? extends E> set1, Set<? extends E> set2)

Which properly ensures that the two sets' elements contain equivalent types. Your example about Strings and integers is pretty good about that. What would you do here :

Set{1, 2, 3}->union(Set{'1', '2', '3'))

Integers are not Strings, Strings are not Integer. Nor can you compare one with the other. The "union" of two sets is a Set containing all objects of the first, and all objects of the second that are not contained in the first. For this, you need to be able to compare the elements of the second set with the elements of the first.

In this case, even if 1 and '1' look suspiciously the same, they are not the same. A user will have trouble seeing this if the typing of the result is "OclAny" (and what about the completion after this union, which will be totally unhelpful in debugging what's happening).

When writing complex OCL expressions, I have found it irritating to have to do

nats.oclAsType(Real)->asSet()->union(reals)

Why do you have to re-transform to a Set after the casting to Real? This seems like a worst issue.

Intuitively, I think that :

let \ reals : Set(Real) = Set{1.0,2.0,3.0},\ nats : Set(UnlimitedNatural) = Set{2,3,4}\ in\ nats->union(reals)

is wrong and should not be allowed, but

let \ reals : Set(Real) = Set{1.0,2.0,3.0},\ nats : Set(UnlimitedNatural) = Set{2,3,4}\ in\ nats.oclAsType(Real)->union(reals)

should work (or fail in invalid if "nats" does not contain only "Real" values).

Just my 5 cents though.

eclipse-ocl-bot commented 13 hours ago

By Ed Willink on Jul 18, 2013 10:24

(In reply to comment #12)

But in OCL x->union(y) is not adding y to x; it is creating a union of x and y.

Well, that's true in all languages offering "union", even still, when I see a "union" method, I'd expect a signature such as guava's :

public static Sets.SetView union(Set<? extends E> set1, Set<? extends E> set2)

Yes, similarly in C++. But that is because those languages support object mutation. OCL doesn't. So while other languages may provide indications of what is intuitively good/bad they nmay not be good guide.

What would you do here :

Set{1, 2, 3}->union(Set{'1', '2', '3'))

No problem: Set(OclAny){1, 2, 3, '1', '2', '3'}; we are not going to help the user by sometimes making 1 and '1' equal.

nats.oclAsType(Real)->asSet()->union(reals)

Why do you have to re-transform to a Set after the casting to Real? This seems like a worst issue.

Because it is really nats->collect(oclAsType(Real))->asSet()->union(reals)

for which the collect returns a Bag rather than a Set. Without asSet() it would again give Unsupported Operation but for Bag.

Using not-yet standard syntax you might be able to do

nats->oclAsType(Set(Real))->union(reals)

NB ->oclAsType since it is an operation on the collection rather than its elements

The 'generous' collection creation of most derived conformant element type may give some surprises downstream, but the error messages will make some sense since they will indicate that the most derived conformant type is not what the user expected. Currently we get an obstinate refusal to provide a reasonable operation.

eclipse-ocl-bot commented 13 hours ago

By Ed Willink on Jun 01, 2015 14:17

Major changes will not happen in the Classic Ecore binding. The Pivot binding supersedes it for Mars.