0

I just hit the very basic problem in OOP and I cannot see any working solution except postponing appropriate check until run time.

It is pretty clear notion of an action "do something with value of the same type" no matter what means given language provides. Consider Equals in C# which in simplest form is "is this object is equal to that object". Or Comparable<T> also in C#. No matter where you start such hierarchy the following is valid:

Comparable<T> --> Animal --> Cat
                       +---> Dog

And the problem:

Animal cat = new Cat();
Animal dog = new Dog();
cat.Compare(dog); // ERR

How to design a language from scratch in such way, that the above line would give an error in compile time?

So far I introduced type Self, which gives clear meaning what you would like to do within a type:

type Animal ... compare(cmp Self) ... 
type Cat ... compare(cmp Self) ...
type Dog ... compare(cmp Self) ...

but it is not enough to avoid the problem with cross passing objects.

Can it be done? Maybe it was already done? How?

I already considered such extreme measures as two kinds of inheriting methods -- with virtual mechanism and not -- and calling them appropriately, but this in turn excludes the generic types (unless generic types are implemented as in C++ -- as templates). With only 2 level of inheritance it could be done by forbidding using top level type except for generic constraints -- of course it is too limiting.

greenoldman
  • 1,506
  • 1
  • 14
  • 27
  • 1
    A `Cat` *is* an `Animal`, so why would you want that to error? This sounds like language syntax for violating the LSP. – Andrew Feb 10 '16 at 17:53
  • Not all languages support subtyping you know. Even OO languages... – Telastyn Feb 10 '16 at 18:02
  • If you consider this an error, you might look into generic types. Generic types are types that take (type) parameters. Using generic types with different parameters results in separate class hierarchies, which means you'll get errors when trying to mix them. – Erik Eidt Feb 10 '16 at 18:15
  • @AndrewPiliser, I would like to give the control to the user -- but currently all languages I know cover only one case -- accept that argument. – greenoldman Feb 10 '16 at 18:18
  • @Telastyn, sure thing, I considered it as well, but every design behaviour leads to some consequence, like here no notion of protocol/interface with virtual dispatch. – greenoldman Feb 10 '16 at 18:19
  • @ErikEidt, I faill to see how it could be done (I don't want to drop virtual dispatch). – greenoldman Feb 10 '16 at 18:22

1 Answers1

3

Just to clarify, IComparable<T> defines that an object has a CompareTo(T) method that can be called.

It does not require that the object (on which the method was called) and the argument (that was passed into the method) are of the same most derived type.

In general, if you call IComparable<T> between a Cat and a Dog, it should return false.

This is consistent with the behavior of the non-generic, non-interface Object.Equals(Object).

In fact, if someone passes in ((Animal)null) as the argument (null, as in non-existence, does not have a most defined type), given that the method target (the object on which the method is called) is non-null, it should also return false.

There are several approaches (not exhaustive). These are not rules or guidelines - these are options for anyone to consider.

Approach 1 (at compile time)

  • Animal should not inherit from an abstract class Comparable.
    As a result, it would not be comparable - it would only have the most basic Object.Equals(Object) method.
  • Instead, only the most derived types, namely Cat and Dog, should implement the corresponding interface IComparable<Cat> and IComparable<Dog>, respectively.
  • This means it would not be possible to call IComparable<T>.CompareTo(T) when given two Animal instances without performing additional type comparison (see below) and type cast.

Approach 2 (at runtime)

Use the following code as a comparison pre-condition:

if (!left.GetType().Equals(right.GetType()))
{
    return false;
}
// followed by the normal comparison logic

This is because in C#, Object.GetType() returns the runtime type of the object.

rwong
  • 16,695
  • 3
  • 33
  • 81
  • Thank you. "if you call IComparable between a Cat and a Dog, it should return false. " No :-) It is an error, does space shuttle come before or after Monday? Not less, not the same, not greater than, an error :-). Approach #1 -- thank you. One problem I can see right now, that for example you write open type today and implement some protocol for it. A month later you inherit from that class (Animal -> Pet Animal -> Cat -> MutantCat :-) ) and you run into a problem, what to do with middle type implementing protocol. – greenoldman Feb 10 '16 at 18:31
  • @greenoldman C# gives you `sealed` to prevent further inheritance. Unfortunately, if inheritance leads to unexpected behavior and the language doesn't give you the tools for preventing that, you have to consider limiting your use of inheritance, or switch to another language or implementation style. – rwong Feb 10 '16 at 18:37
  • Consider this: C# doesn't implement its unicode `char` instances as a hierarchy that mirrors the unicode categorization. It would have been impossible to do so, for reasons similar to what you discovered. – rwong Feb 10 '16 at 18:39
  • 1
    Just one clarification, I am not asking "how to do it i C#", but how to design **new** language that avoid this trap. (Update: this question is already written in the main text). – greenoldman Feb 10 '16 at 19:43
  • @greenoldman Please add this to the question text, and update the question title as well. – rwong Feb 10 '16 at 19:44
  • 1
    I think I will go with another approach -- making a class either sealed or abstract. http://programmers.stackexchange.com/questions/176692/why-should-a-class-be-anything-other-than-abstract-or-final-sealed – greenoldman Feb 11 '16 at 07:20