Nice TWiki > Dev > CurrentDiscussions > NiceConstructors (r1.28) TWiki webs:
Dev | Doc | Main | TWiki | Sandbox
Dev . { Changes | Index | Search | Go }

Links to proposals

UPDATE: As a first step, in the current development version, classes can have initializers, like in Java.

One further improvement is to allow CustomConstructors. Comments are welcome about them.

See the latest ConstructorSyntax proposal.

Discussion

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

The default constructor which takes "any" variables is an interesting academic idea, but it's really ignoring the benefits of constructors.

With the Nice language's concept of the "sloppy default constructor", users will have to read comments to know what to provide (and those comments will get out of date) and we will have to have complicated DesignByContract? mechanisms in order to then allow class writers to enforce proper class construction.

Just add regular constructors. If you think that most constructors are simply instantiating all instance variables, please go take a survey of real-world code.

- DavidJeske

Hi David,

I've added comments in italics - DavidJeske

Thanks for your contribution. We are well aware that having only default constructors is not the perfect solution yet. In particular, we know that instantiating all fields is not the only way to use constructors. By allowing users to construct objects with "whatever fields they want" you are placing an unnecessary burden on the object-author to handle unknown initialization cases, and on the object user to figure out what the useful initialization cases are

First, let me explain why "regular constructors" are not perfect either. The most obvious problem is that they are inherently unsafe. They allow you to refer to this inside the constructor, even before the object is completely constructed. This is alot saver than not knowing whether or not the user will suppily enough fields to construct the objects, because you have no control over the constructor AT ALL.

Here is a simple example, in Java:

class Parent {
  Parent() {
    // We want to log the construction of Parent objects
    Logger.log("Instance created: " + this);
  }

  public String toString() {
     return "Parent";
  }
}
Is this class correct? One would easily think so. And indeed, it will work properly by itself. Now imagine a subclass is created:
class Child extends Parent {
  /** @file a non-null File */
  Child(File file) {
    this.file = file;
  }

  private File file;

  public String toString() {
    return "Child, with file: " + file.getCanonicalPath());
  }
}
Apparently, we did nothing dangerous. First thing done in the constructor is to set our private field. We require that the given file is non-null. So toString should never fail with a null pointer exception, right?

Well, it will. This is because the parent constructor is called before the child constructor. And the parent calls toString, when the file field is not set yet. Boum!

I fail to see how Nice's "no constructor" system fixes any of this. In Nice, the user might supply zero paramaters to the constructor, and then what do you have?

So this inocent looking, two class example fails. I think many developers would not spot the bug if they were just given the source. Probably, after the bug is revealed, they will find it in not much time. But what about more complex examples, involving large hierarchies of classes?

This is why we want to think about alternatives to "normal constructors". We are convinced that a better design is possible. Okay, I'm all for that, but the current step Nice has taken is a step back IMO. I'll have to think about class factories, and other things and get back to you with some better suggestions

Now you have a very good point, which is that even if we try to improve some aspects, we should not go backwards in others. Here is a tentative list of desired features:

I think this is achieved by two means. First, it will be possible not to export the default constructor. I don't understand why I would ever want to export the default constructor Second, if a class used to export the default constructor, and you want to change the implementation without changing the interface, you can hide the default constructor, and define a custom constructor with the same interface, that does the appropriate construction internally.

One last point that should be mentioned: it is planned to allow fields to be declared as public-read, which means they can be read publicly, but not writen to (read access being either package, or private). I like this feature Furthermore, it is possible to replace such public-read fields with a methods, without changing the interface of the class. This is fantastic! This feature lifts two of the main reasons that public fields in Java are considered bad, and should be avoided. Even though public fields should not be used, I don't see any reason to discourage the use of public-read fields. I even like the idea of public write fields if you can do the same trick where they can transparently be values or methods. This is like C# properties without the interface-breaking change from a field to a property. This is how it should have always been done. We have all this fancy runtime JIT stuff, we should be USING it for something.

A few answers to the comments. First let me restate that we are discussing future design. The default constructor is not in itself the solution to these problems. In the final design, the class designer will have complete control over the construction interface.

We'll be glad to look at your suggestions about class factories. Also, could you provide an example of the problem with C# change from field to property?

It's great to have input from different people. This really helps the reflexion, and should lead to a better design in the end :-)

-- DanielBonniot - 19 Jun 2003


From: DavidJeske - 19 June 2003

Here are some thoughts:

public fields

Code readability is easier if accessing public fields uses assignment syntax (a.foo = 1 instead of a.setFoo(1)) C# addresses this by adding "properties". This is a special syntax for get/set methods which yeilds class members which look like fields and act like methods. Here is an example:

class Foo {
  public int count {
     get { return count; }
     set { count = value; }
  }
}

However, this creates confusion for developers:

Ideally, the developer could start with a field, and later migrate to having a method implementation behind the field without changing the classes interface. Since JVMs and CIL both use really-fancy JIT compiling, the "null case" for the method implemention should be easily inlined out when there is no implementation.

class factories and flexibility

Class factories are a useful feature where downstream code can control how other code allocates objects. Just like an application re-uses the code of a library from above, by replacing the code beneath a library you can augment or repurpose that library.

Very useful tools make use of this functionality, but they normally have to go through gyrations to do it. For example, malloc-debug can sit underneath your code and tell you useful things about memory allocation. A debug version of glibc can have instrumentation code which isn't in the normal version. You can see this pattern in effect inside many places in Java. One such place is the socket libraries. Even though you don't have control over the creation of sockets when using the JavaMail IMAP support, you can get it to use SSL for the sockets instead of normal sockets. You do this through an ad-hoc mechanism involving the string names of the classes you would like the factory to use. Here is an example:

final String SSL_FACTORY = "simpleimap.DummySSLSocketFactory";
// Get a Properties object
Properties props = System.getProperties();
props.setProperty( "mail.imap.socketFactory.class", SSL_FACTORY);
props.setProperty( "mail.pop3.socketFactory.class", SSL_FACTORY);
props.setProperty( "mail.imap.socketFactory.fallback", "false");
props.setProperty( "mail.pop3.socketFactory.fallback", "false");
props.setProperty( "mail.imap.port", "993");
props.setProperty( "mail.imap.socketFactory.port", "993");

There are several problems with the JavaMail mechanism which are common to other implementations:

Ideally any object allocation would be controllable by providing built-in configurable object factories at the language level. One possible implementation would be to require that all allocated objects be "class fields" at the class or module level. By providing alternate classes to the class fields, the object would allocate and use a different implementation.

Someone out there is ready to jump up and down and say that "parametric types are the solution"! By providing a parametric SocketType? paramater, a library can easily allow me to override the socket type which is created by my class. Parametric types only solves one of the problems I listed above. The first and most important problems still exists, namely that if the original class designer did not think to make it parametric, then it's not possible for me to do so later.

Perhaps there is a deeper relationship between class factories and parametric types which can be created which will be sure that this paramaterization is always available. For example, perhaps we can implicitly make classes which are allocated in a class part of the paramaterization of that class. Here is a not too well-thought out example:

This code fragment:

class IMAP {
  Socket conn;
  IMAP(String server) {
     conn = new Socket(server);
  }
}

Would be automatically converted into:

class IMAP<S1=Socket implements>
  Socket conn;
  IMAP(String server) {
     conn = new S1(server);
  }
}

-- DavidJeske - 20 Jun 2003

Java compatibility

One thing to consider is that, since there are many more Java programmers than Nice programmers, it should be possible to write libraries in Nice whose intended audience is Java programmers. Ideally, the compiler output would be a jar file and javadoc that look as if they were written in Java. So it would be useful to be able to write constructors that appear in Java to be ordinary Java constructors.

-- BrianSlesinsky - 21 Nov 2003

That's a good point. Default constructors are already compiled as ordinary Java constructors, and can be used as such. I believe it is also possible to compile CustomConstructors as ordinary Java constructors. One difficulty comes from overloading based on argument names: if you have both new Point(double x,double y) and new Point(double angle, double distance), then both cannot be compiled to a Java new Point(double,double) constructor: that would be ambiguous since Java cannot make a distinction based on argument names.

-- DanielBonniot

Getting rid of boilerplate constructor code is fine; although, as noted above, we may just have moved the workload from writing the constructor to updating parameter names at the call site (after changing class variable names).

Not really, because with CustomConstructors you can keep the constructor API even if you decide to change the field names. And if the old names do not make sense at all anymore, then likely it means that you are changing their semantics, so the clients would better be aware of the change (like changing x,y into angle,distance).

Could we avoid that issue by providing parameters in the class definition? This Scala example might fit with Nice's labelled parameters.

-- IsaacGouy - 27 Jan 2004

Isn't the issue solved by CustomConstructors? In Scala, it the constructor arguments are not labelled, which means you cannot precise the meaning of the values you pass.

-- DanielBonniot - 27 Jan 2004

Linking field names, to the constructor (and then being forced to use those field names in the constructor call) feels wrong to me. Seems better to have labelled constructor arguments - and have the programmer take responsibility for setting the fields to the argument values. (Like Scala but require the labels to be used in the constructor call.)

Are there Nice features that rely on there being a custom constructor?

-- IsaacGouy - 27 Jan 2004

We don't absolutely link field names to constructors, we just make that the default, and give you the tools to change it easily if you want or need.

If you want to have another behaviour, it will be possible: mark the default constructor as private, and write a custom constructor which sets the fields. My idea is that in many cases the default constructor will be sufficient and natural, so you don't need to put effort into doing it yourself, obfuscating (ever so slightly) the source. Doesn't having scores of x = this.x; in Java or var x: Int = xc; in Scala feel like a hack?

Something you cannot do with the Scala approach is to have a Point class with both interfaces new Point(x:..., y: ...); and new Point(angle: ..., distance: ...);. You could have either (and they would not need to match the internal representation), but you cannot have both. This means you cannot hide some representation choices to the clients even if you decide too, and you cannot provide an backward compatible way to upgrade the construction API of your class. So you are back in the case where you should always write factory methods, just in case...

-- DanielBonniot - 28 Jan 2004

Topic NiceConstructors . { Edit | Attach | Ref-By | Printable | Diffs | r1.35 | > | r1.34 | > | r1.33 | More }
Revision r1.28 - 28 Jan 2004 - 11:19 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.