Re: subtyping and inheritance

From: Markus Mottl (mottl@miss.wu-wien.ac.at)
Date: Fri Jan 15 1999 - 18:37:26 MET


From: Markus Mottl <mottl@miss.wu-wien.ac.at>
Message-Id: <199901151737.SAA21368@miss.wu-wien.ac.at>
Subject: Re: subtyping and inheritance
To: Jerome.Vouillon@inria.fr (Jerome Vouillon)
Date: Fri, 15 Jan 1999 18:37:26 +0100 (MET)
In-Reply-To: <19990115160242.08046@pauillac.inria.fr> from "Jerome Vouillon" at Jan 15, 99 04:02:42 pm

Hello,

> I think the method "compare" in class "ord" need not be a binary method: its
> argument type can probably instead be a type parameter of the class.
> class virtual ['a] ord = object
> method virtual compare : 'a -> bool
> (* ... *)
> end;;
> Then, the method "compare" can be given the type "symbol -> bool" in
> class "symbol" (and its subclasses) :
> class symbol = object (self)
> inherit [symbol] ord
> method x = "x"
> method compare other = self#x = other#x
> end;;
> class terminal = object
> inherit symbol
> method x = "a"
> method y = "y"
> end;;
> This way, you can compare objects from the class "symbol" and any of
> its subclasses.
> # new terminal#compare new symbol;;
> - : bool = false
> # new terminal#compare (new terminal :> symbol);;
> - : bool = true

I am really sorry - I have probably not explained the problem well enough:

What I want to do is:

Compare terminals to terminals:
  Via a method "compare", which is naturally defined in class "terminal"
  This method calls methods in "other", which are only found in terminals

Compare nonterminal to nonterminal:
  Via a method "compare", which is naturally defined in class "nonterminal"
  This method calls methods in "other", which are only found in
  nonterminals

Compare symbols to symbols:
  Via a method "compare", which is naturally defined in class "symbol"
  This method calls methods in "other", which are found in nonterminals
  and terminals the like

The base class "ord" has some useful functions like "leq", "gt", etc...
I would like to use them without having to redefine them in every class.

If I change your example into the direction I want, I would change class
"terminal" e.g. as follows:

  class terminal = object (self)
    inherit symbol
    method x = "a"
    method y = "y"
    method compare (other : terminal) = self#y = other#y (* line 15 *)
  end

Here, I compare terminals to each other with a totally different
comparison method than the one found in "symbol". Unfortunately, your
example won't type check correctly anymore: the compiler generates the
(certainly not correct) message:

  File "bla.ml", line 15, characters 47-52:
  This expression has type terminal
  It has no method y

But "terminal" *has* a method "y"!!! The problem is that "compare" has
been instantiated in "symbol" with "symbol" as type parameter to "ord".
Thus, the method "compare" has type "symbol -> bool". The compiler still
believes that "compare" should have this type, but wrongly takes the
type information provided in the declaration of "compare" in "terminal",
which declares "compare" to be of type "terminal -> bool". This results
in the incorrect error message that "terminal" has no method "y".

What the compiler actually should flag is the (here) incorrect
redefinition, or better *redeclaration* (due to other type) of "compare".

My proposition for the resolution of this conflict (I have no idea
whether this might result in inconsistencies - I am sure you can judge
this better):

  If an object that has inherited from another class is coerced to this
  class, then the definition *and* the type of all methods should be
  taken from this ancestor. Because the object *must* have all the data
  defined in its ancestor (it has inherited all of it), the use of the
  ancestor's methods should not lead to any inconsistencies.

  E.g.

    class virtual ord = object (self:'self)
      method virtual compare : 'self -> bool
    end
    
    class symbol = object (self)
      inherit ord
    
      val mutable symbol_order = 0
      method symbol_order = symbol_order
    
      method compare other = self#symbol_order = other#symbol_order
    end
    
    class terminal = object (self)
      inherit symbol
    
      val mutable terminal_order = 0
      method terminal_order = terminal_order
    
      method compare other = self#terminal_order = other#terminal_order
    
      initializer symbol_order <- 1
    end
    ;;
    
    (new terminal :> symbol)#compare new symbol

  The last line should be resolved as follows:

  "new terminal :> symbol" should be allowed to be coerced to "symbol"
  (although the type of "compare" is not the same). The method "compare"
  is taken from "symbol" - this is intuitive and should lead to no
  inconsistencies (I hope so ;-)

  With this definition we arrive at:
    self#symbol_order = other#symbol_order

  equals "false" (0 = 1), because "symbol_order" has been initialized
  (overwritten) in "terminal" with "1".

Actually, the error message of the compiler for this example code is
(in my eyes) also misleading. It says:

  File "bla.ml", line 26, characters 1-13:
  This expression cannot be coerced to type
    symbol = < compare : symbol -> bool; symbol_order : int >;
  it has type
    terminal =
      < compare : terminal -> bool; symbol_order : int; terminal_order : int >
  but is here used with type
    < compare : symbol -> bool; symbol_order : int; terminal_order : int >
  Type
    terminal =
      < compare : terminal -> bool; symbol_order : int; terminal_order : int >
  is not compatible with type
    symbol = < compare : symbol -> bool; symbol_order : int >
  Only the first object type has a method terminal_order

Although it is correct that types "terminal" and "symbol" are
incompatible, the reason is *not* that "only the first object type has a
method terminal_order". An additional method in the object to be coerced
does not harm coercion. Coercion is only impossible if the object lacks
some methods or has methods of another type. The latter is the case
in this example: "compare" in "terminal" does not match "compare" in
"symbol". My proposition would change this mismatch, because the method
(including its type) is then taken from symbol.

It took me quite some time to figure out what this error message really
meant when I began to play around with the class system...

I hope that my explanations are not hopelessly incomprehensible or
unuseful and would like to know, whether my proposition would make any
sense in the object calculus of OCAML.

Best regards,
Markus Mottl

-- 
Markus Mottl, mottl@miss.wu-wien.ac.at, http://miss.wu-wien.ac.at/~mottl



This archive was generated by hypermail 2b29 : Sun Jan 02 2000 - 11:58:17 MET