(This topic is large, so this entry is just the second installment. This is Part II; here is a link to
Part I.)
In Object Oriented languages, objects represent the combination of related
state and
behavior. Java classes declare
fields to hold state, and
methods to provide behavior. Java fields and the methods are nested immediately within the class that contains them. This is an example of a common pattern for a Java class exposing state, stored in fields, via property accessors:
public class Person
{
public Person(String name, String phone)
{
setName(name);
setPhone(phone);
}
private String name;
private String phone;
public String getName()
{
return name;
}
public void setName(String name)
{
assert name != null;
this.name = name;
}
public String getPhone()
{
return phone;
}
public void setPhone(String phone)
{
this.phone = phone;
}
}
Ecstasy classes do not declare fields; instead, Ecstasy classes have
properties that represent object state. A property is like an object, in that it can have its own nested state, and its own nested behavior. For example, to obtain the value of a property, one can invoke the
get() method on the property. If the property is writable, then one can modify the value of the property by invoking the
set() method on the property. (Of course, it is possible to use the simple dot notation for property access, which means that explicit calls to
get() and
set()are unnecessary.) Here is the above class, re-written in Ecstasy:
class Person(String name, String? phone);
You could also write it out in long-hand if you prefer; the following code compiles to the same exact result as the above code:
class Person
{
construct(String name, String? phone = Null)
{
this.name = name;
this.phone = phone;
}
String name;
String? phone;
}
It's possible in Java to make the "getter" and "setter" have different access, such as:
public String getName()
{
return name;
}
private void setName(String name)
{
assert name != null;
this.name = name;
}
To accomplish this in Ecstasy, the equivalent is:
public/private String name;
The first access, "
public", specifies that the property shows up in the public type as a
Ref<String>; a
Ref represents a read-only reference to a value. The second access, "
private", specifies that the property shows up in the private type as a
Var<String>; a
Var represents both read and write access to the value.
Remember, though, that a property is like an object. Let's expand the Java example slightly, to validate that the name is not an empty
String:
public void setName(String name)
{
assert name != null && name.length() > 0;
this.name = name;
}
In Ecstasy, the property contains a method called
set(String) that we can override:
public/private String name.set(String name)
{
assert:arg name.size > 0;
super(name);
}
The above is just short-hand notation for:
public/private String name
{
@Override void set(String name)
{
assert:arg name.size > 0;
super(name);
}
}
There are a couple of important points here:
- It's not the set() method that is private. The set() method is public, because it is part of the Var interface, as explained above.
- Instead, the public Person type (known as Person:public or Person.PublicType) has a property that does not have a set() method, while the private Person type (known as Person:private or Person.PrivateType) has a property that does have a set() method.
- In Java, "super" refers to the super-class. In Ecstasy, super is a reference to the function (like a function pointer) that is next in line to invoke in the virtual method's invocation chain. In other words, super is a function.
- While it's not directly related, you can read more about the various specializations of the assert statement on this blog. The assert:arg statement produces an IllegalArgument exception if the assertion fails.
The interesting thing, though, is that we never have to deal with the field. We know it's there, because it has to be in order to hold the value, but the field doesn't have a name, we don't access it, and we don't modify it. Instead, we just call the
super function for
get() or
set(), and at the end of that chain there is some implementation of the method (that we didn't have to write!) that accesses or stores the value for us using the field.
But what if we made it so that we could never reach the end of those method chains?
public/private String name
{
String get()
{
return "Bob";
}
void set(String name)
{
assert:arg name.size > 0;
// do nothing with the name ... do not store it!
}
}
In this case, there would be no field for the
name property, because it's obvious to the compiler that one is not needed!
So now it should be obvious that fields exist in Ecstasy, but that we never really have to mess around with them. Where are those fields actually held, though?
In one of the examples above, we talked about how the
Person class has a
public type and a
private type, so you probably already guessed that the
Person class also has a
protected type, and you would be correct!
But the
Person class has one more type: the
Person:struct type. The
Person:struct type has one property for each property of the
Person class that needs a field. We call each property on the
struct type a "field"; in Ecstasy, a field is just a property on a class'
struct type.
The
struct type is not user-definable. The
struct type is automatically calculated by the compiler at compile-time, and by linker/loader at run-time. While the
public,
protected, and
private types all refer to the same underlying object -- as if they were three different lenses through which you can view the same object -- the
struct, on the other hand, is a
separate object that is an implementation of the
Struct interface.
For the purpose of this article, this is already way too much low-level information about structs, but the details are important for one reason: To understand constructors.
Constructors are weird. They live in a zone between non-existence and existence. They play by some extraordinary rules in Java, and the same is true in Ecstasy, because they fit into a
zone of unknowns. Here's what a constructor looks like in Java, just to pick one at random from our own prototype compiler that was written in Java:
public StringConstant(ConstantPool pool, String sVal)
{
super(pool);
assert sVal != null;
m_sVal = sVal;
}
First, in Java, a constructor
must call either a different constructor on
this class, or a constructor on the
super class. Then, it is free to do other stuff, like checking parameters and initializing fields. Fields that aren't explicitly initialized are all set to their defaults, which is easy when
null is a
sub-class of everything.
Ecstasy is different. Not necessarily simpler or more complicated. Not necessarily better or worse. But it is different for very purposeful reasons:
- Contruction is treated as a finite state automaton. Eliminating unknowns and improving predictability of execution is extremely important, and that is exactly what a finite state automaton does.
- There is a period of time before the object is constructed. The developer gets complete control over that process.
- There is a period of time after the object is constructed. The developer gets complete control over that process.
- In between the before and the after, the developer is completely absent, and completely out of the picture for the moment of creation. During that moment, all the rules of object instantiation can be verified, and the object is created. We say that "the this becomes existent".
In that period before the object creation, there are two phases that the developer can implement:
- The construct(...) function(s) allows the developer to specify what information is needed to initialize the state of the object, and the developer can validate that information and initialize the structure of the object, which is the aforementioned struct.
- The assert() function allows the developer to collect, in one place, any assertions (or any other last-second work) that must occur before the object is created.
Here is an example of a constructor, from the
Date class, which simply delegates to another constructor using the
construct keword:
construct (Int year, Int month, Int day)
{
construct Date(calcEpochOffset(year, month, day));
}
For both
construct(...) and
assert(), the
this variable is the
struct -- not the object, because it has not yet been created! After that code has all completed successfully, the
struct is checked
by the system to make sure that all necessary fields have been assigned a value, and then the object is
instantiated based on the
struct. Then -- after the moment of creation, and before the newly created "
this" reference is returned to the code that invoked the
new operator -- one more step occurs: The corresponding
finally(...) function for each previously invoked
construct(...) function is executed, so that the object itself gets to see itself (and finish anything that it needs to) before being returned to the code that requested it.
Here's an example from the
Array class:
protected construct(ArrayDelegate<Element> delegate)
{
this.delegate = delegate;
}
finally
{
if (mutability == Constant)
{
makeImmutable();
}
}
In this example, the
construct(...) function fills in the fields of the
struct, but because the
Array object does not yet exist at this point, the
construct(...) function can
not call the
makeImmutable() method on the
Array -- until the
Array actually exists! And
that is the purpose of the
finally function -- to allow the new
Array object to perform behavior that must occur
as if it were part of the instantiation of the object,
before that object is returned to the code that requested the object to be created.
A class can define an
assert() function (with no parameters) as well. Regardless of whether any particular
construct(...) function is invoked by the
new operator, or by a sub-class -- and note that a sub-class is
not required to invoke any
construct(...) function on its super-class! -- the assert() function will be invoked
before the object is created.
There are many details regarding the specific order of execution, handling of exceptions, and so on, but this post hopefully has given you a glimpse into how object structure works in Ecstasy, and how Ecstasy objects are created.
(Continue to
Part III.)