Types vs Classes

Like many object-oriented languages, Grace will have classes. Like some object-oriented languages, Grace will have types. Like a few
object-oriented languages, Grace programmers have the option to ignore classes and use only objects, or to ignore static types and use only dynamic types.

The relationships between objects and classes, and static and dynamic types, are well known. So, then, what’s the relationship between
classes and types in Grace? Or rather, what should be the relationship between classes and (static types?

Here’s the problem. Let’s take a simple Grace class:

This class creates a factory object that supports the creation of new Cat instances in response to the method request “new(aColour,aName)” (cognoscenti will notice we’re trying “def x =” syntax to define constants rather than “const x :=”. Sorry Niklaus).

What type does the variable “fergus” have? As in C#, local type inference gives it whatever type the “Cat.new” method returns. What if we want to declare that type explicitly, say for a variable?

Here the name “Cat” is being used as a type, rather than a factory. The key question is: where did that type come from? There seem to be two options in the design here:

  1. The Cat class declaration implicitly creates a Cat type.
  2. The Cat type must be declared explicitly, separate from the class
    declaration:

The first option, a class implicitly creating a type, is what most typed object-oriented languaes do: a class declaration also creates a type (technically the cone type rooted at the class). Implicit class-types lead to more concise programs, and allow “static typing early” courses to have students write and use their own classes without requiring an explicit concept or separate declaration of a static type.

On the other hand, the second option, explicit type declarations, make static types much more explicit. Under this option, Grace programmers couldn’t declare an explicitly typed variable (or more likely, any method, as method arguments are not inferred) without an explicit declaration of the Cat type. But this clarity comes at a price: simple programs are longer, requiring apparently redundant type declarations, declarations that are close to class declarations, but duplicated some information with some mandatory tweaks.

Grace programmers can avoid the price of a separate declaration in a couple of ways. First they can use dynamic types or local type inference — omitting types from variable and constant definitions will find types via local inference (if the type-checker is run) while omitting types from method arguments and results are interpreted as type dynamic. So the costs of declarations (presumably) would only be required whenever a type is to be written explicitly. Still, this is another case where a ”better” program (with explicit types) is longer and more redundant than a ”worse” program (without them). Most Grace programmers may choose to omit the declarations, so the language would fall into being dynamically typed by default.

In fact, the real situation is worse than this: there are about five or six kinds of “class-like” or “type-like” objects in Grace: a good solution here should address all these roles:

  • ”’Factory”’ object that creates new instances of a class

  • ”’Type”’ with which variables, arguments, and methods are declared

  • ”’Reified Type Parameter”’ Since Grace will have “reified”
    generics, what value or object should the reified value be?

    Note that inside the Collection, the reified type argument will have to be bound to the formal type parameter.

  • ”’Pattern”’ object used to match objects of that type in
    match/case statements

  • ”’Mirrors”’ used to reflect on instances of the class

If these are played by different objects — how many different namespaces will Grace need to name them all? If they are accessible in a shared namespace, how are names resolved?

Finally, following C#, Grace will provide constructs to reify the declared static type of an expression (perhaps “decltype(e)”) and the exact dynamic type of an object (“o.dyntype”, or perhaps alternatively “reflect(o).dyntype” via a mirror). The aim here is to let programmers write programs that interrogate the static and dynamic types in their programs. And, whatever the relationship we end up with, do we need better names for static type and dynamic type?

5 thoughts on “Types vs Classes

  1. So, after discussions on Saturday, it turns out there is (at least) one important role missing:

    A class must also act as a superclass from which new classes and objects can inherit.

    Also: it seems pretty clear that “type” and “reified actual type parameter” must be the same things; these two must also be (subsets) of patterns — patterns can be things other than classes.

  2. I’m glad that you relented and realize that types and “reified actual type parameter” are the same thing. I’m also unclear why mirrors enter into this: surly one get s a mirror on an object by asking the object for it, as in

    moggy.miror

    not by asking some other agent to reach in and mess with the object!

    But yes, it’s very true that an inheritor (better: an heir) sees more of an objet than does a client, and that it needs a more comprehensive (and thus more complicated) kind of type.

    I think that you have a good idea in unifying patterns and types, although the greek squad won’t like it. As I see it, your idea is that a pattern is a predicate on an objects, or, to put it another way, a set of properties that the object must satisfy for the pattern to match. A type is a particular kind of pattern with a bunch of restrictions on it that enable the type to be statically evaluated much of the time (but presumably not all of the time, or else we wouldn’t need typecase at all!) So the challenge now is to do two things.

    1. Define pattern so that the properties are OO, that is, they can be tested by using the client’s interface to an encapsulated object — by requesting methods on the object.

    2. Decide what subset of these properties are in “type”.

  3. I’m also unclear why mirrors enter into this

    I just figured they were another “class or type like (meta)object”

    surely one get s a mirror on an object by asking the object for it, as in

    In Bracha/Ungar style, you’re more likely to write “mirrors.mirrorFor(x)” because you want to be able to configure a system without the mirrors API, or with only some (but not all) of it.

    an inheritor (better: an heir) sees more of an objet than does a client, and that it needs a more comprehensive (and thus more complicated) kind of type.

    Right. so we need two kinds of types for things that may have progeny.
    The question is – what kind of “types” are they, what things may have progeny, etc.

    A type is a particular kind of pattern with a bunch of restrictions on it that enable the type to be statically evaluated much of the time

    Yep. that’s it. But some of this needs to be primitive, I think, or there’s nowhere for patterns- especially those that involve type- to start.

  4. Turns out I forgot one very important application of Class-like things from the list: inheritance!. Classes can, of course, be superclasses of subclasses — and in some languages, can do much more than that.

  5. And I forgot another one: nesting

    In Grace a class (or object) can be nested within another class or object, including a module, or/and a method scope.

Leave a Reply

Your email address will not be published. Required fields are marked *