Re: Covariant Return Types

[email protected] Mon, 23 Feb 2004 18:26:45 -0600
Newsgroups gmane.comp.lang.nice.general
Message-ID <[email protected]>
Quoting Daniel Bonniot <[email protected]>:

> > Why did you choose to make overriding explicit and overloading implicit?
> > I think I am more likely to inadvertently overload a method when I meant
> > to override it, than I am to inadvertently override a method when
> > meaning to overload
> > it, because I am used to programming in Java. Maybe both should be
> > explicit?
> 
> There is a fundamental reason to make overriding explicit and overloading
> implicit.

I am curious about the meanings of "method," "method implementation,"
"specializes," "overrides," and "overloads."

Here is a simple example of my confusion of overriding vs. overloading
("overrides" syntax made up):

    package a;
    public class A { }
    public class B extends A { }
    public A self(A a) { log("A -> A"); return a; }
           B self(B b) overrides A self(A a) { log("B -> B"); return b; }

The intention is that:
    A aa = new A ();
    B bb = new B ();
    A ab = new B ();
    
    A aaa = aa.self();  // prints "A -> A";
    A abb = bb.self();  // prints "B -> B";
    A aab = ab.self();  // prints "B -> B";
    B bbb = bb.self();  // prints "B -> B";

Now, how many =self= methods do we have? One method with two implementations? Or
two methods, with the first one having two implementations and the second one
having one implementation. If you say there is one method with two
implementations, then the type of that method is complex:
    self : (A -> A) or (B -> B).
If you say that there are two methods, then they would be typed:
    self(A) : A -> A
    self(B) : B -> B
In this case, the second definition of =self= is simultaneously overriding and
overloading the first one. I believe that this second way of thinking is close
to the Java 1.5 model. If the second model is chosen, then is the following code
equivalent?:
    package a;
    public class A { }
    public class B extends A { }
    public A self(A a);
           B self(B b) overrides A self(A a);
    self(A a) { log("A -> A"); return a; }
    self(B b) { log("B -> B"); return b; }
If so, it isn't much different than:
    package a;
    public class A { }
    public class B extends A { }
    public A self(A a);
           B anothername(B b) overrides A self(A a);
    self(A a)        { log("A -> A"); return a; }
    anothername(B b) { log("B -> B"); return b; }
That is, allowing specialization of a method by another method that doesn't even
have the same name.

Now, consider something like this (current Nice syntax):
    package b;
    public class A { }
    public class B extends A { }
    public A self(A a) { log("A -> A"); return a; }
             self(B b) { log("B -> B"); return b; }
Now, we want to say that there is one method that has two implementations,
right? That is, we don't want say that =self= is overloaded in this case, right?
Do we want clients of this package to know that the second implementation even
exists? In other words, do the specializations of a public method become part of
the public interface of the package, or is it enough for clients to know that
there is a method =self= with type A -> A? Is the above going to be equivalent to:
    package b;
    public class A { }
    public class B extends A { }
    public A self(A a) { log("A -> A"); return a; }
           // explicitly noted to return A
           A self(B b) { log("B -> B"); return b; } 
or, is =self= now considered to be overloaded again?

It seems perhaps that there may need to be a distinction between
"implementation" and "specialization". Specialization is a relation between
methods (possibly with identical names, possibly with different names).
Implementation is the relation between a particular method M and a set of method
implementations. That set of implementations is the union of all implementations
defined directly for M and those defined for all specializations of M
(transitively).

> > As an alternative, imagine a "method" keyword that must
> > be prefixed to a method declaration. Then you have:
> 
> Yes, that's true: instead of marking overriding implementations, you can
> mark declarations. It works, the point being that implementation and 
> declaration should look different.

I guess this doesn't work in cases where the method we want to override has
already been overloaded:

    package c;
    class A { }
    A doSomething(A a) { ... }

    package d;
    import c;
    void doSomethingElse(A a) { ... }

    package e;
    import c;
    void doSomethingElse(A a) { ... }

    package f;
    import c;
    import b;
    import c;
    class B extends A { }
    B doSomething(B a) { ... }
    doSomethingElse(B b) { ... }

We should be able to say that f.doSomething(B) is NOT a specialization of
c.doSomething(A). If you require specializations to be explicit then there is no
issue for that. Similarly, we should be able to say whether f.doSomethingElse(B)
is a specialization of d.doSomethingElse(a) or e.doSomethingElse(A). But, should
we be able to say that f.doSomethingElse(B) is a specialization of _both_?:
     doSomethingElse(B b) overrides d.doSomethingElse(A),
                                    e.doSomethingElse(A) { ... }

It seems that there is a big syntactical burden (lots of keyboarding) to
requiring overriding to be explicit:
    package g;
    interface Node { }
    Node merge(Node parent,
               Node left,
               Node right);

    package h;
    abstract class AbstractElement { }
    AbstractElement merge(AbstractElement parent,
                          AbstractElement left,
                          AbstractElement right);

    package i;
    import g;
    import h;
    abstract class BranchElement extends AbstractElement implements Node { }
    class TopElement extends BranchElement { }
    abstract class LeafElement extends AbstractElement implements Node { }
    class EmptyElement extends LeafElement { }
    class TextElement extends LeafElement { String text; }
    class WrapperElement extends AbstractElement { AbstractElement e; }

    merge(parent, TextElement left, TextElement right)
          overrides merge(AbstractElement parent,
                          AbstractElement left,
                          AbstractElement right) 
          = new TextElement(text:left.text + right.text);

    merge(parent, EmptyElement left, right)
          overrides merge(AbstractElement parent,
                          AbstractElement left,
                          AbstractElement right)
          = right;

    merge(parent, left, EmptyElement right)
          overrides merge(AbstractElement parent,
                          AbstractElement left,
                          AbstractElement right)
          = left;

    merge(parent, WrapperElement left, right)
          overrides merge(AbstractElement parent,
                          AbstractElement left,
                          AbstractElement right)
          = merge(parent, left.e, right);
    merge(parent, left, WrapperElement right)
          overrides merge(AbstractElement parent,
                          AbstractElement left,
                          AbstractElement right)
          = merge(parent, left, right.e);
    ...

Another case is specialization by value:
    package r; // must be getting close to the end!!!
    public class A { }
    public class B { }
    public <T> T fromString(String s) { throw new Error(); }
               A fromString("a") = new A();
               B fromString("b") = new B();
Do you want to allow this? It would make the type of =fromString=:
   <T> String -> T or "a" -> A or "b" -> B
This seems to make the type system very complicated. 

- Brian




-------------------------------------------------------
SF.Net is sponsored by: Speed Start Your Linux Apps Now.
Build and deploy apps & Web services for Linux with
a free DVD software kit from IBM. Click Now!
http://ads.osdn.com/?ad_id=1356&alloc_id=3438&op=click