Behaviors, classes and metaclasses

In Essence#, a class is a specialized type of namespace. However, since the semantics and behavior of namespaces are explained in the documentation section on namespaces, this section will only discuss the specific semantics and behavior of classes.

In addition to being a namespace, an Essence# class will play at least two of the following, semantically-distinct roles:

  1. Creator of instances, all of which will have the same behavior and the same object state architecture. To have "the same behavior" means that the instances will respond to the same set of messages in the same way, provided the instances have the same state when they receive the same message. To "have the same object state architecture" means that the instances will have the same memory layout, "shape," syntactic structure and/or bit-pattern semantics. In other words, it means the instances of the class will use the same syntax to structure/organize the bits/bytes that represent their value, and that the structure of those bits/bytes will be interpreted semantically in the same way.
  2. Manager/repository of the definition of the object state architecture of the instances it creates, and/or whose behavior it defines.
  3. Manager/repository of the definition of the behavior of the instances it creates, where "behavior" means the set of messages to which the instances of the class can respond, and the semantics that the instances will use when responding to a message.

Of course, not all values are instantiable. There is no way to "create an instance" of primitive values, because such objects are immediate values that are not allocated on the heap, and so aren't referenced by means of pointer indirection. Nevertheless, such values can and do have Essence# classes that a) specify the object state architecture of the values for which they serve as the class, and b) define the behavior (in Essence# code) of such values.

Essence# has 3 different "types" of "classes." Pay attention, because this gets tricky in parts:

  • Behaviors:
    • An instance of the class Behavior is a proto-class.

    • Behavior instances have no name, and so cannot bind to a containing namespace the way that an instance of the class Class can.

    • Although it is legal to create direct instances of Behavior, it should only be necessary for specialized purposes, especially when doing advanced metaprogramming.

    • The class Behavior is a Class (it's an instance of the class Class--but only indirectly by way of inheritance.)

    • The class Behavior is the direct superclass of the classes Class and Metaclass.

  • Classes:
    • The instances of the class Class (whether direct, or indirect by way of inheritance) are referred to as "classes," and correspond more closely to the semantics of the term 'class' as used in most other programming languages than the other two types of "classes" do.

    • The class Class is itself a Class (it's an instance of itself--but only indirectly by way of inheritance.)

    • The instances of a class are usually not themselves classes, with one prominent exception (see below.)

    • Unlike a Behavior or a Metaclass, a class (instance of the class Class) has a name--independently of being stored as a namespace entry.

  • Metaclasses (here's where it gets tricky, so read slowly and think about each point):
    • The class Metaclass is itself a Class (an instance of the class Class--but only indirectly by way of inheritance.)

    • The instances of the class Metaclass are referred to as "metaclasses."

    • A metaclass is called that because it is the class of a class. This is the only common case where the instances of a class are themselves also classes. If you think this enables the creation of a "Möbius strip" type structure, you're right. And that fact has practical consequences.

    • The class of a string is the class String, but the class of the class String is the String metaclass, which is an instance of the class Metaclass. You can consider the class of the class String a metaclass because it's the class of a class, or because it is an instance of the class Metaclass. Or both. Both interpretations are correct, independently. 

    • There is only one canonical instance of a metaclass (pseudo-singleton pattern,) which is traditionally referred to as its sole instance (although that term is a misnomer, because a metaclass may actually have more than one instance.)

    • The class of the class Metaclass is the metaclass of the class Metaclass...which is at the same time an instance of the class Metaclass. If you decide to get a cup of coffee while you ponder that, we'll understand. Draw yourself a picture. And we warned you that there was the possibility of a "Möbius strip" type structure, and that that fact had practical consequences. We also warned you that this would get tricky.

    • Unless its canonical instance has no superclass, the superclass of a metaclass is the class of the superclass of its canonical instance.

    • Metaclasses don't have their own names, but instead derive a name from the name of their canonical instance. If the name of the class that is the canonical instance of a metaclass is "String," then the name of the metaclass will be "String class." And so the name of the class of the class Metaclass is "Metaclass class." And "Metaclass class" is itself an instance of Metaclass. Go get another cup of coffee.

    • The "environment" (containing namespace) of a Metaclass is its canonical instance. That's why the methods defined by a metaclass can see the "class variables" defined by its canonical instance, and can also see any global variables imported by its canonical instance.


Key points:

  • All "classes" are themselves an instance of a class.

  • The class 'Object' has no superclass, but the superclass of the class of the class 'Object' is the class 'Class.' The same is true for any "root class" that has no superclass. And that's the twist that creates the "Möbius strip." See the next point to see the ramifications.

  • The class of which any class is an instance inherits from the class Class, because all metaclasses are sublcasses of the class Class--usually indirectly--even though they are also direct instances of the class Metaclass. That's why any class can legitimately be called a "class," even though all classes are either instances of a metaclass or else are instances of the class Metaclass.

  • Because a class is itself an instance of a class, a class can have instance variables--which are not at all the same as the instance variables it defines for its instances. And a class can also have its own methods, distinct from the ones it defines for its instances. And those "class methods" can be invoked by dynamic dispatch, just like instance methods--because they are in fact the instance methods defined by the class of the class. And the behavior and instance variables of a class can be defined and modified by application programmers. For example, one can add methods to a metaclass which define behavior that is specific to the metaclass' canonical instance--and that behavior can be inherited and/or overridden by the metaclasses of the subclasses of a class.

Configuring classes

To set (or change) the name of a class, send it the message #name: aSymbol (where the symbol that is the message argument is the desired class name.)

To specify the superclass of a class, send it the message #superclass: aClass (with a class as the message argument.)

To set or change the namespace that contains a class, send it the message #environment: aNamespace (with a namespace as the message argument.)

To set or change the instance architecture of a class (that is, to specify whether the instances of a class will or will not have named instance variables, and/or whether its instances will have indexed slots (be array like,) or whether its instances will be instances of some CLR type,) send the message #instanceArchitecture: aSymbol to the class. The valid Symbols that can be used as arguments to the message are all listed in the documentation section on object state architecture.

To specify the names of the instance variables that the instances of a class will have, send the message #instanceVariableNames: anArrayOfSymbols to the class, as in the following example:

 DateAndTime instanceVariableNames: #(date nanosecondsSinceStartOfDay timeZone).

Sending the message #instanceVariableNames: to a class will automatically set its instance architecture to #NamedSlots, if its current instance architecture isn't one that already permits named instance variables.

More useful examples:

"Set the instanceArchitecture of a class to #HostSystemObject:"
OrderedCollection instanceArchitecture: #HostSystemObject.
"Explicitly set the name of the CLR namespace of the CLR type that the class should represent:"
OrderedCollection hostSystemNamespace: #System.Collections.Generic.
"Set the unqualified name of the represented CLR type (so that it will be different from the name of the Essence# class.) The type name in the example is semantically equivalent to the C# type 'List<System.Object>', which uses generic type syntax to specify a List whose elements are statically-typed as System.Object:"
OrderedCollection hostSystemName: 'List`1[[System.Object]]'.
"Set the CLR type that an Essence# will represent using a single message send (the type name in the example is semantically equivalent to the C# type 'System.Collections.Generic.List<System.Object>', which uses generic type syntax to specify a System.Collections.Generic.List whose elements are statically-typed as System.Object):"
OrderedCollection instanceType: 'System.Collections.Generic.List`1[[System.Object]]' asHostSystemType.

 

Sending any of the above messages automatically sets the instance architecture of the class to #HostSystemObject.

Essence# comprehensively and pervasively expects that the names of CLR types will be in the form accepted and used by the CLR's reflection classes. See Microsoft's documentation of System.Type for the details.

Adding methods to classes

In the case of a class whose instance architecture is #HostSystemObject, it's probable that some (and perhaps all) of the aspects defined or inherited by the CLR type represented by the Essence# class will be automatically added "on demand" (when and as needed) to the Essence# class as "virtual" methods. That happens as part of Essence#'s DLR CallSite processing. The methods that will be virtually added in this manner include methods that will get or set any non-private fields and/or properties defined or inherited by the CLR type, methods that will invoke any non-private methods defined or inherited by the CLR type that have no parameters, and methods that will invoke any non-private methods defined or inherited by the CLR type that have just one parameter. Such virtual methods are always added in pairs: The name of one will begin with a capital letter (conforming to .Net naming conventions,) and the name of the other will begin with an initial lower-case letter (conforming to Essence# naming conventions.)

Defining Essence# methods that will invoke methods of a CLR type that have more than one parameter is documented in the section on Using CLR Types.

Methods can be added to classes programmatically at run time by sending messages to the class to which the methods should be added:

Given a string whose text is a syntactically-valid method declaration, sending the following message to a class will compile the method and add it to the class: #compileMethodInProtocol: protocolSymbol fromString: methodDeclarationString (where protocolSymbol is a symbol that names the method protocol, and methodDeclarationString is a string whose text is a syntactically-valid method declaration, as in the following example:

  • targetClass compileMethodInProtocol: #examples fromString: 'example1 ^3 + 4'.


But that would be doing it the hard way. There are two easier ways. One of them would be to send the message #protocol:method: to the target class, with a method literal as the second argument:

  • targetClass protocol: #examples method: [## example1 ^3 + 4].


But what would usually be the best way would be to declare the method in a class library, as explained in the section that documents class libraries.


The essence of OOP: It's all messages, all the time.

Last edited Aug 10, 2014 at 11:36 AM by Strategesis, version 83