Nice TWiki > Dev > CurrentDiscussions > NiceConstructors (r1.12) TWiki webs:
Dev | Doc | Main | TWiki | Sandbox
Dev . { Changes | Index | Search | Go }
Nice doesn't have custom constructors but the default one is not always sufficient to express everything you want. The implementation of field depending on previous fields will help in some case.

In what cases are default constructor not enough and what could be added to Nice to solve that problem?

One possibility is to allow that each class can have one constructor without any arguments that is called after the fields are initialized and the constructor of the superclass is called.

-- ArjanB - 09 May 2003

Given that DesignByContract? is already part of the language, it's worth noting that using factory methods for object construction can cause problems with ClassInvariants. The invariant should be true on exiting a constructor, but need not be on entering, so some way of marking the factory method as for object creation is needed.

-- SamsonDeJ - 13 May 2003

One thing that might be wrong about Java constructors is that they serve two purposes. One is to assign values to the fields of the class. The other is to do any operation that needs to be done when an object is constructed. Let's call the first aspect construction, and the second initialization (are there clearer names, or are those clear?).

The problem with mixing these two purposes arises when you start calling methods from the constructor, before the fields are set. In other words, from a Design by Contract point of view, if you call a method before the invariant is valid. This means that the method called cannot assume the invariant is true. Worse, that method can call others.

How is this handled in other languages? In particular Eiffel, since it has contracts. If, as I suppose, all (public) methods can assume the invariant upon entrance, how is the above problem avoided?


Eiffel lets you mark methods as for instance creation, and these are not checked for the invariant at the start. There's a hack to allow them to call other (non-instance-creation) methods before the invariant is properly set:
invariant
   constructor_constraint: in_constructor implies [<a more limited invariant>) 
   general_constraint: not_in_constructor implies (<the full invariant>) 
so you set in_constructor and not_in_constructor at the appropriate places. It's got great potential for misuse though. To make it work you would have to painstakingly record for each method which parts of the class invariant they really depend on, which kind of defeats the purpose of an invariant. I've got a couple of vague thoughts on the matter, under ClassInvariants -- SamsonDeJ - 15 May 2003

My design goal in this area is to solve this problem, while keeping enough flexibility.

One possibility I see to solve this problem is to separate construction and initialization. They would clearly be marked as two different phases of creating a new object. Construction is already supported in Nice with the automatic constructor, where you provide values for each field (optionally for those with a default value). It might be useful to be able to define custom constructors, that construct the values of the field in other way, for instance with some computation over their parameters. But they would not hold a reference to the constructed object (this in a Java constructor), which solved the invariant problem. (A syntax would need to be created for those).

As for initialization, it could be done via a normal method, called for instance init. Because initialization can require calling arbitrary methods, the invariant should be true upon entrance (and therefore at the end of construction). It is a possiblility to choose to provide special support for initialization. In particular, we could make sure that init is called automatically when an object is constructed, and that the parent init (super) is called.


Doesn't this cause the same sorts of invariant problems as constructors in cases where the invariant depends on initialisation, not just construction? My suggestion (summarised from ClassInvariants) is to allow methods to be marked as either requiring the class invariant or not. Those that don't require it must still maintain it if it already applies, but may be called during initialisation as well as during normal operation. -- SamsonDeJ - 16 May 2003

The invariant is normally a condition on the values of the fields. In that case, anything that makes the invariant become valid belongs to the construction, not the initialization.

Yes, generally, but there are simple cases where this isn't so. For eg., how about a doubly-linked-list Node class. We would like the invariant

!hasPrevious() || (previous().hasNext() && previous().next() == this)
but that is only true after initialisation, since the constructor can't set the next field of the previous Node.

It could help to have a list of common things that needs to be done during initialization:


In this proposal, there could be several ways to construct the object, but the initialization would be a unique method. Comparing to Java where constructors, which serve both pruposes, can be overloaded, is this a limitation? I'd then to think that, besides behaving differently depending on the value of the fields, initialization should be uniform. If something else needs to be done in certain cases, then that can be done as a method call at the creation site.

A summary of this could be: creation = construction + initialization.

Does this sound like a nice and practical handling of object creation?

-- DanielBonniot - 14 May 2003

I'd just like to point out that I think the default constructor as currently provided is useful and should remain available.

_I agree with this. The default constructor will remain, as it is adequate for a rather large proportion of classes. Moto: Easy things should be easy, hard things should be possible. -- DanielBonniot

There are plenty of times, however, when I don't want people to use the default constructor. Mostly it's because I don't want them to assign invalid values to certain attributes. If I do:

class A {
  int i;
  ?int cachedCalculatedValue = null;

  int getCalculatedValue() {
    if (cachedCalculatedValue == null)
      cachedCalculatedValue = i * 5;
    return cachedCalculatedValue.notNull;
  }
}

I really need a way to keep people from doing new A(i: 5, cachedCalculatedValue: -23472398);.

I've been thinking about this for a while now, and I'm now wondering about a three stage approach. The code new A(...) works like this:

1. The arguments (...) get passed to the constructor method for A. "Constructors" don't actually construct anything, however, they just manipulate arguments. I'll call them "preconstructors" to differentiate them from Nice's current constructor. You might imagine preconstructors to look something like this:

//Using class A, above
A(int i) {
  //trim i to be <= 10
  if (i > 10) {
    i: 10;
  }
}
where the assignment i: 10 changes the value of i that will be passed to the default constructor. If no assignment is made, then the value that was passed in is used. The compiler calls all the preconstructors, from most derived to least derived. This ensures that a derived class cannot change the parameters to values which the base class's preconstructor would not allow. Preconstructors on a derived class can specify values for parameters in the base class, but which they do not themselves accept:

class Shape {
  int numberOfSides = 0;
}

class Triangle extends Shape {}

Triangle() {
  numberOfSides: 3;
}

However, preconstructors in derived classes can assign values to those parameters too:

class SuperTriangle extends Triangle {}

SuperTriangle() {
  numberOfSides: 9; 
}

numberOfSides will still be 3, not 9, because Triangle() runs after SuperTriangle?(). The double assignment should probably be reported as a warning. I'm not entirely sure about this, maybe it should be an error. Such a warning would have to be reported at the source location where the subclass preconstructor assigns to the parameter, to be useful.

2. The system calls the default constructor we use now, with the arguments returned from the preconstructor methods.

3. The system calls the 'init' method, passing the fully constructed instance.

As an alternative to this solution, here's a simplest-thing-that-could-possibly-work suggestion: allow to mark fields as off-limits to the constructor. Here's a syntax example, using a hypothetical 'internal' keyword:

class A {
  int i;
  internal ?int cachedCalculatedValue = null;

  int getCalculatedValue() {
    if (cachedCalculatedValue == null)
      cachedCalculatedValue = i * 5;
    return cachedCalculatedValue.notNull;
  }
}

and now doing new A(i: 5, cachedCalculatedValue: -23472398); would result in an error message like "A.cachedCalculatedValue is an internal field, and cannot be specified in the constructor".

Note that 'internal' is not the same as 'private' - internal fields may or may not be private, they just can't be specified in the constructor.

-- BrynKeller - 14 May 2003

This concern about the default constructor exposing too much is valid. It must be adressed.

Your first proposition is worth considering. However, my first take on it is that it looks quite complex.

The second is much clearer at first sight. Couldn't we go one step farther, by using 'private' for this purpose? I expect them to coincide extremely often. (In your example, you would probably want 'private internal'.) In cases where they don't, you can still provide a getter (and possibly a setter), or even use the property-like feature when it is added. The planned semantics for 'private' is that the visibility is limited to the file. So it comes naturally that you cannot refer to a private field in a constructor call, outside of the file declaring the class. Inside that file, you should be able to refer to it, for instance if you declare a custom constructor.

-- DanielBonniot - 15 May 2003

Topic NiceConstructors . { Edit | Attach | Ref-By | Printable | Diffs | r1.35 | > | r1.34 | > | r1.33 | More }
Revision r1.12 - 06 Jun 2003 - 18:48 GMT - DanielBonniot
Parents: WebHome > CurrentDiscussions
Copyright © 1999-2003 by the contributing authors. All material on this collaboration platform is the property of the contributing authors.
Ideas, requests, problems regarding TWiki? Send feedback.