24
Jan
2007

Covariant property types round 2

In thinking about it more, I think the right answer to the question of covariant property types might to allow covariant getters and to allow the types of getters and setters to be different.

class Application {
    public function get menu() : Menu;
    public function set menu(menu : Menu);
}

class MyApplication {
    public function get menu() : MyMenu;
    public function set menu(menu : Menu);
}

This covers the read only case and the read/write case. If the author wants to make MyApplication truly robust, the setter for menu will have conversion code to create a MyMenu from the Menu. If it does not need to be robust in this way, the author can throw an exception.

Allowing getters and setters to be of different types might be advantageous in other circumstances.

class Foo {
    public get dataProvider() : ICollection;
    public set dataProvider(provider : Object);
}

One could also imagine overloading the setter.

class Foo {
    public get dataProvider() : ICollection;
    public set dataProvider(provider : Array);
    public set dataProvider(provider : ICollection);
}

Again, I leave it for the language geeks to let me know if this is a bad idea. Just some idle thoughts on how to make properties a little easier to work with.

13 Responses to “Covariant property types round 2”

  1. Matt Garland

    Thanks for these two posts, they’re helpful.

    I didn’t realize there was such a thing as a covariant return types in other languages..I’m jealous.

    I often find myself in this situation: I write an app class and a class for a component it composes…and find myself extending the app class, AND extending the component class, too, simultaneously…but in the extendedApp (myApp, in your example) class, I can’t reference the extendedComponent (myMenu) class without a lot of downcasting. I’ve even shuffled off the inherited property to another, more strictly typed property. It’s damned ugly, and makes me feel like a hack…

    So I’d welcome the possibility of narrowed return types, but with this quibble: You are looking for a clean, well-hidden downcast, whether with getters or return values, but how to make the code more readable when you are perusing old files? How would you know what’s up?

    public override function getMenu(): override MyMenu?

    That looks…funny.

  2. Sho

    Thanks for the note. Your example is exactly the reason I am thinking about this issue. This is especially the case when you are creating a set of framework classes that *must* be subclassed in the way that you describe in order to be used by the application author.

  3. Anatole Tartakovsky

    Quick question – why Object and not * in the base class ? – kind of implies implementation on the base level. If you insist on having references in the base object – let’s consider [abstract|require-initialize-or-override] var menu:Object – will be more explicit to the developer.

    While on the subject of interfaces and dynamic implementations I often find myself doing unnecessary interfaces so developers will get hints in eclipse and is forced to implement the same code multiple times. Other sample would be missing mixins.Include does not work well enough due to debugger and override limitations. Something of a crossover of overridable multiple inheriatnce/delegate model like :
    class a extends b mixins c,d implements e ….
    would be very interesting.
    Thank you,
    Anatole

  4. sho

    I’m not 100% sure I understand your question, but I think you’re asking me a general question about the * type annotation and not something specific to the article above.

    If I’ve got your question right, the answer is somewhat complicated. The * annotation took me a long time to understand when it was first proposed.

    * is a type annotation, which is slightly different than a class. A type is a set of rules that define what kinds of values a variable can hold.

    When you do something like this:

    var foo : Object;
    

    you are saying that foo is a variable of type Object, which means that it can hold two kinds of values:

    1. objects of class Object
    2. null

    In contrast, a variable of type * can hold three kinds of values:

    1. objects of class Object
    2. null
    3. undefined

    Example:

    var foo : Object = bar.sldffj; // bar.sldffj is undefined
    trace(foo); // this prints "null"
    
    var bar : * = bar.sldffj;
    trace(bar); // this prints "undefined"
    

    So the * type is a superset of the Object type. However, this is different from saying that * is a superclass of Object. There is no class called *, and the class Object has no superclass. Remember: * is just a type annotation that defines a set of values that the variable can hold.

    Another way of thinking about how type annotations are different than classes is to consider some of the new type annotations that are being considered for ECMAScript 4. Nullable types allow a variable to hold null even when it normally would not. Example:

    var foo : ?Number = null; // This is a nullable version of the Number type.
    

    There is obviously no class called ?Number.

    As for why we chose to put behavior in the base Object class…

    The advantage of having a class like Object is that you have a place to put behavior that is intrinsic to all objects. For example, the Object class in ActionScript has a method that lets you find out if a property is defined on that object.

    Languages like C# and Java have a base Object class for the same reason. Even in languages like C++, there is usually data and behavior associated with every object despite the fact that there is no well defined Object base class.

    For example, C++ has a typeid operator which acts on any object. This value is stored with each object (I would imagine this is done implicitly through its vtable) But because C++ doesn’t have a well defined root object, you have to invoke it as if it were a global function like so: id = typeid(obj);

    Anyway, that’s my 2c. You could definitely go either way on whether or not to have a common base class for all objects, but my preference is to have the common base class.

  5. Nate Chatellier

    I’m so glad your talking about this. I’m part of a team creating a pretty large scale AS 3.0/FP9 game, and although we use inheritance all over the place, not being able to override getters/setters has been a big problem. In many cases we’ve used getMyProperty():MyProperty instead of get myProperty():MyProperty entirely so we would be able to override the function. It seems like there should be methods for both overloading (I like your suggested implementation) and overriding. Thanks for looking into this.

  6. Adam Cath

    Sho, I think you’ve come to the right conclusion. Covariant returns increase compile-time type checking and decrease downcasting. However, covariant setters are syntactic sugar for

    function set(foo:Super) {
    var myFoo:Sub = foo as Sub;

    }

    which does not increase compile-time type checking or decrease downcasting. Although syntactic sugar can be sweet, I think this sugar only adds danger and confusion.

  7. Ray Greenwell

    Let’s talk about danger and confusion.

    The whole getter/setter system is a bug-generating engine for coders. There’s nothing that can be done with getters and setters that couldn’t be done with good old fashioned functions called getProperty() and setProperty().

    The difference is that those ()’s give a programmer a clue.

    I could write a function that does something to DisplayObjects:

    public function doStuff (objs :Array) :void
    {
    for each (var disp :DisplayObject in objs) {
    disp.x = calculateNewX();
    disp.y = disp.x + calculateYFactor();
    }
    }

    I test it with my display objects and everything seems to work. But see there that I’ve stored a temporary value in disp.x. For regular display objects, I believe the value is immediately readable, but for UIComponents, the value you read back out of x may be different than the value you just set!

    What’s the solution?: “You just have to know that X is undependable like that.” Well, imagine that it’s not a well-known property like x on DisplayObject, but something else…

    I guess, Sho, I don’t want to rain on your parade, but I think that the language has bigger issues than not being able to have covariant return types. I’m no language expert, but I think these would go a long way:

    – overloading functions would be great. There are standard library classes that accept parameters of type “*”, because they want to accept String or QName, for example. Having them accept * is just not correct. Having the variable be of type Object would be more correct, but that has its own connotations, and maybe the * causes people to look at the docs where they find out the actual accepted argument types. Overloading would let the compiler be able to do its job.
    – Overriding a var-args method and calling super is a pain. Usually I end up repackaging all the args into one array and then apply() that array to the super function.
    – static constants are not inherited in subclasses
    – Can arrays be “real” instead of a bastardized associative hash? I bet new programmers love this:

    // shows off lack of integer math and arrays being dynamic at the same time
    for (var ii :int = 0; ii

  8. Ray Greenwell

    [I was cut off, continuing:]

    – Can arrays be “real” instead of a bastardized associative hash? I bet new programmers love this:

    // shows off lack of integer math and arrays being dynamic at the same time
    for (var ii :int = 0; ii

  9. Ray Greenwell

    [Ack, 3rd time’s a charm, it was the “<” in my code]

    // shows off lack of integer math and arrays being dynamic at the same time
    for (var ii :int = 0; ii < 40; ii++) {
    someArray[ii / 2] = getValue(ii);
    }
    // oops, you just broke your array

    – Similarly, “dynamic” is a bug-fest. someArray.size() compiles fine, and later during runtime I may discover that there is no function named “size” attached to the array.
    – The numeric types can have an identity crisis. If you have a variable of type Number that has the value 3.5, and I subtract .5 and typeOf() the result, it will report that it’s of type int, even though Number does not extend from int.
    – There are probably more that I’m not thinking of now.

    There are innumerable other things that aren’t really about the language, but provide HOURS of frustration for new programmers:

    – TextArea.textWidth and TextArea.textHeight have magic constants associated with them (5 and 4, respectively) that need to be added to get the values that can be set on the width/height without chopping off the very text to which you’re sizing. These constants are hidden inside UITextField and used in many places in the flex code. How about adding them to the documentation for TextField? Or better yet, make them constants in that class, so that folks can use them in their code to do the right thing.
    – SharedObject seems to do two very different things: one is storing data on a client machine, one is for sharing data between client and server. I think that maybe one method is shared between the two uses. Make them different classes!
    – Oh, I love this one. Flex components get the “removed” event before they are removed and the “remove” event afterwards. Tense!
    – I’m getting carried away, so I’ll stop. Let’s just talk about the language, not the libraries.

    I guess what I’m saying is: Covariant return types are neat and all, but if you’re really trying to make the language better I think there are some basic issues to address. Now watch me get stomped-on by all the actionscript coders who are used to eating this dog food and think it tastes great.

  10. sho

    Lots of good points, Ray.

    I can understand your point about getters and setters, but my personal opinion is that getters and setters are a good thing. They simplify the syntax. Compare:

    foo.setWidth(bar.getWidth());

    foo.width = bar.width;

    Also, in languages like Java, you typically don’t expose any of your fields to the public, so *every* field access looks like foo.setWidth() instead of foo.width, which means that you are always questioning whether there are any side effects. Can’t you mentally do that with the dot notation as well?

    The other points you make have to do with the history of ECMAScript as a language without strong types.

    Integer math in ECMAScript is somewhat weird. At some level, all math is done as Numbers, which can vary freely from floating point to integer type. When ints and floats collide, there are weird, counterintuitive things in any language. For example, in C++,

    int i = 3;
    double d = i / 2; // this yields 1, not 1.5

    You are probably not bothered by this because you’ve become accustomed to how the / operator is defined in C++, but I think it’s kind of weird.

    As for arrays being dynamic.. you’re right! I’d never noticed that before. That seems like a source of errors. I’m going to ask around about why that decision was made.

  11. sho

    Oh.. one more thing about covariant getters. The reason I think this is important is that there are certain framework things that are just not possible without allowing for covariant return types and getters. For example, if you look at the Cairngorm framework, ModelLocator.getInstance() is not defined in any other way than a series of comments. Why? Because it’s important that ModelLocator.getInstance() returns an object of the proper subtype. There is currently no way to define this in the base class and allow subclasses to specialize the type.

  12. Ray Greenwell

    Sho,

    It’s true, because of setters I’ve had to learn to not re-read values out of objects. The paranoia about side-effects that one has about method calls now extends to any property on any other object.

    I also freely agree that “integer math” as defined in C and Java is probably a strange concept to new programmers. But when I learned C there was time spent teaching me about the pitfalls of integer math and also how it could be useful.

    I don’t remember any mention of how math works in as3. I was not prepared when I discovered that it did NOT do integer math like Java- I had written a very simple program that was acting strangely, and I was tearing my hair out until
    I finally started printing every intermediate value in my calculations.

    The other day I ran into another math related problem. I guess I had assumed that if I add or substract a Number value from an int value, the Number would be rounded the same way every time.

    Check this out:
    var x :int = 25;
    var n :Number = 24.5;
    // whee!
    x = x – n;
    x = x + n;
    trace(“x: ” + x); // outputs “x: 24”

    This is a problem in Java too, but in Java you’re forced to cast the result of each addition or subtraction in order to assign it back to the int variable. You can’t not think about it. In actionscript everything happily copes, which is nice for whipping things up, but can be detrimental for a complex project.

  13. Overriding methods in Actionscript

    […] Of course there is always the quick ‘n’ dirty solution of typing your parameters and/or return values with the * datatype and checking the datatype with if-statements within the method (remember the good old days of AS2?). But of course that’s far from desirable.. Read Sho’s post (and the follow-up) if you’re interested.. […]

Leave a Reply