Nice TWiki > Dev > ConstructorSyntax (r1.1 vs. r1.17) TWiki webs:
Dev | Doc | Main | TWiki | Sandbox
Dev . { Changes | Index | Search | Go }
 <<O>>  Difference Topic ConstructorSyntax (r1.17 - 09 Feb 2004 - DanielBonniot)
Added:
>
>

On the one hand, I agree that the earlier such new syntax is introduced the better. On the other hand, 0.9.6 has already been delayed a lot, because I did not want to release a feature that we were not quite sure was yet in its probable final version. Furthermore, it might be good to have the new creation syntax some time in development version. And it's purpose is especially evident with factory methods, which are not there yet either. We'll see if that can be implemented soon, but I want 0.9.6 released soon (it has several important bug fixes). In any case, we can only start changing documentation after a released version implements a feature.

-- DanielBonniot - 09 Feb 2004


 <<O>>  Difference Topic ConstructorSyntax (r1.16 - 09 Feb 2004 - BrianSmith)
Added:
>
>

This isn't an issue any more since all object creations will go through factory methods now, right? There remains the issue of whether the factory methods and constructors should be exposed by default but that can be dealt with in VisibilityModifiers.
Added:
>
>

Sure, things make sense as far as I can tell. But, if you are going to recommend that people use Point(...) instead of new Point(...) then I recommend to make that change before you do any more releases. This includes updating the documentation and the examples. Also, are you planning to flag the new Point(...) syntax as deprecated? It seems confusing to have two syntaxes for the same thing, and then also have new Point(...) do three different things depending on the context.

-- BrianSmith - 08 Feb 2004


 <<O>>  Difference Topic ConstructorSyntax (r1.15 - 08 Feb 2004 - DanielBonniot)
Deleted:
<
<

Deleted:
<
<

Added:
>
>

Right, you proposal is an improvement over the current situation in Nice. But it is more inflexible than my proposal because if you don't start with a factory method, you will never able to switch to one without breaking the public API. -- DanielBonniot - 08 Feb 2004
Added:
>
>

I agree, sorry if that was not clear. -- DanielBonniot

Added:
>
>

OK, I hope that we are getting to a conclusion. CustomConstructors are defined with the new Point(...) { ... } syntax. Later on, we will implement factory methods, defined with Point(...) { ... } syntax. At the call site, one always call a factory method (but that can be the one implicitely defined by a CustomConstructor?. We can also allow the syntax Point(...) for creating a Point, which will address the confusion that Bryn signaled, namely that new Point(...) might not create a new instance.

If there's agreement on this, then I'll release 0.9.6 soon with the current implementation of CustomConstructors, since they seem to fit with the global proposal (that is, source code working with the 0.9.6 release will work the same when the global proposal is implemented).

-- DanielBonniot - 08 Feb 2004


 <<O>>  Difference Topic ConstructorSyntax (r1.14 - 03 Feb 2004 - DanielBonniot)
Added:
>
>

The long term goal could be to encourage the use of Point(...) instead of new Point(...) on the client side. But I think for a longish transition period both should be accepted. It would simply be breaking too much assumptions otherwise.

Yes, for each constructor for a given class, there will be a way to create a new instance with those parameters, so that's like an automatically generated factory method. And I think the syntax for factory methods should be just like normal methods. No need for specific treatmeant.

-- DanielBonniot - 03 Feb 2004


 <<O>>  Difference Topic ConstructorSyntax (r1.13 - 03 Feb 2004 - BrianSmith)
Added:
>
>

Added:
>
>

I agree, the visibility issue can be discussed in VisibilityModifiers. The other point I was trying to make is that I think that object creation expressions (currently new Point(x:1,y:2), or Point(x:1,y:2) using the syntax you suggested below) should be defined to always invoke factory methods, for the same reasons that motivated the PropertySyntax proposal.


Added:
>
>

That to make a lot of sense to me; in fact it seems to be the same as my original proposal except that the factory method is automatically generated if an explicit one is not provided. Just to make sure I understand: The expression Point(x: 1, y: 2) would always call a factory method. If a factory method has not been explicitly defined, then there is a compiler-generated one of the form Point(double x, double y) = new Point(x: x, y: y);. What would happen with the expression new Point(x:1, y:2)? Would it be disallowed (allowable only in factory methods from class Point), call a factory method, or directly call a constructor? Personally, I find the idea of new Point(x:1,y:2) expressions not creating a new instance to be confusing.

What would happen if there were multiple constructors defined for the class; would there be an auto-generated factory method for each one?

What would the exact syntax of a factory method be?:

    // same syntax as other methods.
    Point Point(double x, double y) { ... }
or
    // assume the return type is Point.
    Point(double x, double y) { ... }

The Point(x: 1, y: 2) syntax also matches the int('c') syntax already used for primitive types.

    class Point
    {
      final double x;
      final double y;
    }
    // custom factory method
    Point(double x, double y) 
    {
        if (x == 0 && y == 0)
            return origin;
        else
            return new Point(x: x, y: y);
    }

    // overloaded/custom constructor
    new Point(double distance, double angle) {
        this(x: blah, y: blah);
    }

    // class instance initializer for Point.
    // Perhaps the initializer should always
    // appear in the class body so "initialize"
    // doesn't become a keyword?
    initialize Point {
        ...
    }
-- BrianSmith - 02 Feb 2004

 <<O>>  Difference Topic ConstructorSyntax (r1.12 - 02 Feb 2004 - DanielBonniot)
Added:
>
>

But a new version of A might change its public API. So it is normal for the author of B to recompile his application if he upgrades to a new version of A. It's true that you might be a user of B, you don't have B's source code, and you have a new version of A which does not change its public API but is faster, or safer, for instance. In that case, yes, it's better if you can use the new version of A without recompiling B. But that's a quite peculiar situation. And you should keep in mind that even if A's public interface does not change, it's behaviour/semantics might change, so there is no automatic way the compiler can know if it's safe or not to upgrade A with an old B. But anyway, as you showed, it is possible to keep binary compatibility, it's just a question of implementation, so I would rather that we don't complicate the discussion about the desired definition of the language with this.

Added:
>
>

Added:
>
>

Note that you used "overloaded constructor" for what we called custom constructor until now.

Yes, that's a clear distinction of the different aspects, which is important to have in mind. The question is, do we want to use this syntax? It's clear, but it's also very baroque/unusual. What is sure is that we should distinguish the different aspects.

One idea would be to reserve new for constructor declarations, and only the class name for factory method declarations. The idea is that a constructor is involved in the creation of a new instance, while factory methods might return an existing instance, null, ... This would also avoid the ambiguity problem, without introducing those new keywords.

On the usage side, we do not want to distinguish, because the whole idea is that you can evolve the implementation without breaking the API.

We could also support Point(x: 1, y: 2) for calling constructors and factory methods (that is, without the new keyword), since that is a lighter syntax, and puts less stress on the creation of a new instance, which might not be the case anyway. But that's a change that can be done independently of the rest of the proposal.

Added:
>
>

-- DanielBonniot - 02 Feb 2004


 <<O>>  Difference Topic ConstructorSyntax (r1.11 - 02 Feb 2004 - BrianSmith)
Added:
>
>

Added:
>
>

It is slightly different. In Java, public classes have an implicit public constructor by default. I am proposing that any implicit constructors be package-private in Nice.
Added:
>
>

I don't think my suggestion makes it easier to write inflexible code--in fact, I think it does exactly the opposite. Currently, there is no way to enforce that a factory method must be used to construct an instance of your class since the constructors are always public. Therefore, changes to the implementation are restricted because this public constructor must be supported. In my "proposal," the choice of factory method vs. constructor more explicit: if you don't write a public factory method or a public constructor then your class cannot be instantiated from code outside the package at all.
Added:
>
>

You are assuming that package B can be recompiled whenever package A changes, right? But, this is not the case when package A and package B are written by different people, for example when package A is a reusable library packaged in a JAR file and package B is an application that depends on it.

In NiceConstructors, you said "creation = construction + initialization." But, let's extended this to "creation = allocation + construction + initialization" or "new X(...) = allocate X(...) + initialize X()," and by default "allocate X(...) = construct X(...)." Then you could support something like:

    class Point
    {
      final double x;
      final double y;
    }
    // custom factory method
    allocate Point(double x, double y) 
    {
        if (x == 0 && y == 0)
            return origin;
        else
            return construct Point(x: x, y: y);
    }

    // overloaded constructor
    construct Point(double distance, double angle) {
        construct(x: blah, y: blah);
        // instead of "this(x: blah, y: blah);"
    }

    // class instance initializer for Point
    initialize Point {
        ...
    }

Then you would not overloading the new keyword for three different concepts any longer and you avoid ambiguity between overloaded constructors and factory methods.

-- BrianSmith - 02 Feb 2004


 <<O>>  Difference Topic ConstructorSyntax (r1.10 - 01 Feb 2004 - DanielBonniot)
Added:
>
>

Changed:
<
<

package B, version 2.0:

>
>

package A, version 2.0:

Added:
>
>

So what you are advocating is exactly the same as Java, right?

Again, the problem with this approach is that you allow, and even make the "default" since it is easier, to write inflexible code, whose implementation you cannot change afterwards. This is the same problem as methods in C++, which are not virtual unless you explicitely say so, or with fields. So people come up with rules like "always write factory methods" and "always write getters and setters", which indeed avoid the problem, but put the burden on the programmer.

I don't think using Nice will be more complex with this proposal. It will become easier, because you don't need to worry or bother about factory methods, but if you realize you need to override the default behaviour, you are sure to be able to do it.

It's true that the implementation becomes a bit more complex, but don't worry, I am the one doing it, and I think it's worth the above benefits. About binary compatibility, your solution is correct, and anyway this is an implementation detail (I don't think users should even need to know about binary compatibility rules. Computing power for recompilation is cheap, compilers can be made clever if needed, while user brain's attention is better directed at the design of his program). At the moment, the compiler doesn't check if the public API changed or not, it always recompiles if an imported package changed, which is safe. We might optimize this behaviour in the future, but at the moment I consider more important to "get the language right", so I focus on that. -- DanielBonniot - 01 Feb 2004


 <<O>>  Difference Topic ConstructorSyntax (r1.9 - 01 Feb 2004 - BrianSmith)
Added:
>
>

My proposal is to avoid implementing this feature. :) Or, at least, do not overload the constructor syntax for this purpose. If you do so, then you will have to go to great lengths to document a tedious source-code -> bytecode mapping and/or the binary compatibility rules.

How do you deal with binary compatibility for constructing an instance of a class defined in another package? It seems like inter-package object construction must always go through a "thunk" factory method:

package A, version 1.0:

    class Point
    {
      final double x;
      final double y;
    }

package B, version 2.0:

    class Point
    {
      final double x;
      final double y;
    }
    Point new Point(double x, double y)
    {
        if (x == 0 && y == 0)
            return origin;
        else
            return new Point(x: x, y: y);
    }

package B:

    import A;
    let Point myOrigin = new Point(x:0, y:0);

It seems like you can compile package B as a normal object instantiation (using the JVM's new instruction) against version 1.0 of package A. But, you cannot compile it that way against version 2.0 of package A. I believe that package B should not have to be recompiled due to this change in the implementation of package A, since package A's public interface is the same; that is, I should be able to compile package B against version 1.0 of A and then run package B's code against the compiled version of version 2.0 of A. So, it seems like you have to generate a thunk method equivelent to makePoint for both versions of package A.

I think it would be better to simply discourage people from directly constructing instances of classes from external packages by making all constructors protected by default, and requiring people to add code like:

    // I can implement optimizations like the above in my
    // factory method since the Point constructor is not public.
    public Point makePoint(int x, int y) = new Point(x:x,y:y);
or:
    // I cannot implement optimizations like the above since
    // I am directly exposing a constructor to external packages.
    public new Point(int x, int y);

This makes the language simpler to learn and makes the binary compatibility rules much easier to understand.

-- BrianSmith - 01 Feb 2004


 <<O>>  Difference Topic ConstructorSyntax (r1.8 - 30 Jan 2004 - DanielBonniot)
Added:
>
>

Agreed. If I remember, the difference was supposed to come from the presence or absence of a return type (so your first constructor should have one, if it's supposed to be an overloaded constructor, no?). But it's true that if you can specialize an overloaded constructor (which makes sense), then there is syntactic ambiguity.

Any proposal?

-- DanielBonniot - 30 Jan 2004


 <<O>>  Difference Topic ConstructorSyntax (r1.7 - 29 Jan 2004 - BrianSmith)
Added:
>
>

I didn't know this because it isn't documented. Now I understand what you meant.
Added:
>
>

I will concede that it will not cause problems as long as the documentation is clear that the new operator does not guarentee uniqueness w.r.t. object identity (System.identityHashCode(Object)).
Added:
>
>

I disagree. If you start out writing code with the default constructors and then change the fields in the class, you have to remember to add an explicit custom constructor with the old interface to maintain binary compatibility. This is easy to forget to do and the compiler won't be able to help you find this error. For this reason, I think that implicit constructors are bad when used across package boundaries and that explicit constructors should always be used across package boundaries; if VisibilityModifiers were implimented than I would propose that implicit constructors should have package private access. (A more general rule would be that I think that a package's public interface should be explicitly defined in the code.) Once you have written an explicit constructor it doesn't really matter so much which syntax you choose (factory method or constructor), as far as I can tell.
-- BrianSmith - 29 Jan 2004
Added:
>
>

I am just saying that it makes the semantics hard to explain because you can't piggyback on the JLS/JVM specs anymore in these areas. So, you have to write new documentation for the semantics of object creation/identity and for part of the reflection API.
-- BrianSmith - 29 Jan 2004
Added:
>
>

More and more Java API's are designed so that the API is (almost) purely defined by interfaces, so that factories must be used anyway. That is the style I prefer so I don't find makeA to look like a hack to me. Anyway, I think the argument could be turned around as "Isn't using constructors easier to understand because you are used to it?"
-- BrianSmith - 29 Jan 2004
Added:
>
>

I think that there should be a syntactical distinction between OverloadedConstructors and CustomConstructors. They are different concepts and I think users will get confused by this:
    class Foo { } { ... }

    ....

    new Foo(Number n, Number n) { ... }

    ...

    new Foo(Double d, Double d) { ... }

Is the second constructor above an overloaded constructor, a custom constructor, or a specialization (dispatch-wise) of the first constructor. Maybe you can tell just by reading the code. But, imagine that there are 50 lines of code where the ... lines are. Or, imagine now that all three elements are defined in seperate packages. Now how can you tell?

-- BrianSmith - 29 Jan 2004

 <<O>>  Difference Topic ConstructorSyntax (r1.6 - 27 Jan 2004 - DanielBonniot)
Added:
>
>

Not sure if you are familiar with ||, but this is not specific to constructors. The equivalent code in a more traditional notation would be:

new Point(0,0) {
  if (origin == null)
    return origim;
  else
    return super;
}
Added:
>
>

Yes, you get less control over when a new object is created or not. But that's the idea: most of the time, you don't need to know, and giving you this control causes lots of problems, which is why it is advised to use factory methods. When you call a factory method, you don't know if a new object is created or not either.

Could you give an example of craziness that would ensue with IdentityHashMap<Point,T>?

Added:
>
>

What we gain is that you don't need to defensively write all the code of a factory, just in case it turns out later. Either you do it in all cases, and a large proportion will be wasted effort (even with a good IDE, it's still cluttering the code), or you don't, and then you are stuck since clients started using new YourClass?, and you cannot make the changes that you need without breaking this API. I think these are the same benefits as for properties, where there is a known workaround (getters and setters), but it's a pain to have to do it by hand when the compiler could do it for you.

Added:
>
>

True, but this is a rather lowlevel property, isn't it. How often would it be problematic? How often would the new system safe you work or let you improve your code without breaking the API?

It would still be possible to offer another construct for surely creating a new instance, provided it's allowed by the class. That can be discussed. Basically, this would make new ... the equivalent of Java factory methods, and the other one the equivalent of Java's new. Since the latter is much rarer in my opinion, we would gain a lot by making it easier.

Added:
>
>

It's good practice in a language that provides no such feature as we are discussing. The whole point is to make this extra work unecessary.

Isn't the second easier to understand because you are used to it, while the first is a new proposal? Doesn't makeA look like a hack?

Added:
>
>

Yes, this is the distinction between CustomConstructors and OverloadedConstructors?. new Point(0.0, 0.0) is an overloaded constructor, and you cannot use it to construct subclasses, in the same way that in Java, in a constructor you can use a parent constructor but not a parent factory method. Maybe we should say "factory method" instead of OverloadedConstructor??

-- DanielBonniot - 27 Jan 2004


 <<O>>  Difference Topic ConstructorSyntax (r1.5 - 26 Jan 2004 - BrianSmith)
Added:
>
>

Added:
>
>


The syntax new Point(0,0) = origin || super is not intuitive.

From what I understand you want to allow the user to overload the new operator on a class-by-class basis. I think that is very confusing. When the expression new Point(...) is encountered we expect to have a new object allocated. In particular, it is always the case that

   new Point(0,0) != new Point(0,0)
The idea of object identity becomes unclear because it can't be described in terms of new. Imagine the craziness that would ensue for something like IdentityHashMap<Point,T>.

The example above can be written using a factory method, so I don't see what kind of expressivity you are gaining:

    let Point origin = new Point(x: 0, y: 0);
    Point makePoint(double x, double y) = new Point(x: x, y: y);
          makePoint(0.0, 0.0) = origin;

Also currently we have new Point(...) as equivalent to Point.getClass().getConstructor(...).newInstance(...). But, with the new system, we have don't have this symmetry:

    abstract class A      { int getValue();
                            // more methods
                          } 
    class Zero extends A  { int getValue() = 0;
                            // specialze other methods for ZERO
                          }
    class Other extends A { final int value; 
                            int getValue() = value;
                            // generic implementations for non-ZERO values
                          }
    let Zero ZERO = new Zero();

Then we could have either:

    new A(int value) = new Other(value: value);
    new A(0) = ZERO;
or:
    A makeA(int value) = new Other(value: value);
      makeA(0)         = ZERO;

I find the second version to be much easier to understand and it can already be done without any language changes. When VisibilityModifiers are implemented then the package author can mark all constructors private and then provide public factory methods like makePoint and makeA. I think this is a good practice anyway.

Finally, imagine:

    class ColoredPoint { int color; }
    new ColoredPoint(int color) = new ColoredPoint(color:color, x: 0.0, y:0.0);
The coder has specialized Point(0.0,0.0) but this specialization obviously cannot be used by the subclass. So, then you have two sets of rules for deciding what constructor implementation gets chosen (one for direct invocation, one for subclass invocation).

-- BrianSmith - 26 Jan 2004


 <<O>>  Difference Topic ConstructorSyntax (r1.4 - 20 Jan 2004 - DanielBonniot)
Added:
>
>

A different idea is to use super inside a creation method to refer to the real constructor. This would give the following version for the example:

class Point { double x; double y; }

let Point origin = new Point(x: 0, y: 0);

new Point(0,0) = origin || super;

 <<O>>  Difference Topic ConstructorSyntax (r1.3 - 19 Dec 2003 - DanielBonniot)
Changed:
<
<

For a client, a natural way to construct a point would be new Point(x: ..., y: ...);. The author of the class might anticipate or know by analysis that many instances of Point actually have the same coordinates, so he could decide to improve the efficiency by sharing their representation. He should be able to do so without chaning the API, so the clients can still use new Point(x: ..., y: ...);=. So he should be able to write a "creation method", for instance:

>
>

For a client, a natural way to construct a point would be new Point(x: ..., y: ...);. The author of the class might anticipate or know by analysis that many instances of Point actually have the same coordinates, so he could decide to improve the efficiency by sharing their representation. He should be able to do so without chaning the API, so the clients can still use new Point(x: ..., y: ...);. So he should be able to write a "creation method", for instance:


 <<O>>  Difference Topic ConstructorSyntax (r1.2 - 18 Dec 2003 - BrynKeller)
Added:
>
>

It's just struck me that what we're talking about here is very similar to Dylan's way of handling this problem. I'll include a reference here for comparison. Nice is very similar to Dylan in many ways, so it's not a bad idea to see what Dylan does when we have questions about what Nice should do. Instance Creation and Initialization

-- BrynKeller - 18 Dec 2003


 <<O>>  Difference Topic ConstructorSyntax (r1.1 - 18 Dec 2003 - DanielBonniot)
Added:
>
>

%META:TOPICINFO{author="DanielBonniot" date="1071743880" format="1.0" version="1.1"}% %META:TOPICPARENT{name="NiceConstructors"}% I'm trying to summarize my ideas on constructors, based on the discussions we had.

I think we should separate two aspects: creation of new instances, and initialization of new instances. The creation aspect is what interests clients of a class: they want to be able to construct instances that have a certain property, which is described by the arguments they pass. For the author of a class, there are cases where it is possible to return an existing object instead of creating a new one: if the class is immutable, and an object with the correct property already exists. For instance, consider:

class Point
{
  final double x;
  final double y;
}

For a client, a natural way to construct a point would be new Point(x: ..., y: ...);. The author of the class might anticipate or know by analysis that many instances of Point actually have the same coordinates, so he could decide to improve the efficiency by sharing their representation. He should be able to do so without chaning the API, so the clients can still use new Point(x: ..., y: ...);=. So he should be able to write a "creation method", for instance:

let Point origin = new Point(x: 0, y: 0);

Point new Point(double x, double y)
{
  if (x == 0 && y == 0)
    return origin;
  else
    return new Point(x: x, y: y);
}

The problem with this is the inside call to new Point(...). It will be a recursive call, and so will never finish (similarly, the value for origin would execute the creation method, which would try to read origin, which is not set yet). One solution would be to treat specially new inside a "creation method". However, this could get messy, and I would prefer a clean solution without such hacks. One idea is to give a special syntax for calling a "real constructor", ignoring creation methods with the same name. For instance:

let Point origin = Point.make(x: 0, y: 0);

Point new Point(double x, double y)
{
  if (x == 0 && y == 0)
    return origin;
  else
    return Point.make(x: x, y: y);
}

With this, we can treat creation methods as normal methods (except for parsing their name). "new Point" is a normal method, so we can define it in a Nicer way:

let Point origin = Point.make(x: 0, y: 0);

Point new Point(double x, double y) = Point.make(x: x, y: y);
new Point(0,0) = origin;

One could be worried that allowing Point.make is exposing a detail about a class: if a client uses it and the class changes so that there is no such "real constructor", then the client will break. However, I think this is fixable. First, the author of the class can provide a CustomConstructor?, which is also reachable with the Point.make syntax. Second, it should be possible to use visibility. Should Point.make be only package visible, not public? This part needs some more thought.

Another good aspect of the make syntax is that it suggest a syntax for CustomConstructors that differentiates them from creation methods:

Point.make(double angle, double distance) = new Point(x: ..., y: ...);

It should even be possible to define Point.make as a normal method:

// Optimization for angle = 0
Point.make(0, distance) = new Point(x: distance, y: 0);

One possible improvement is to allow creation method implementations without a declaration. In that case, the declaration would be taken by looking at matching constructors (custom or not). This would allow the following:

let Point origin = Point.make(x: 0, y: 0);

new Point(0,0) = origin;

This makes some sense because, from the clients point of view, new Point exists even without a creation method declaration, and it defaults to the custructor. Would there be any drawback with this additional feature?

-- DanielBonniot - 18 Dec 2003


Topic ConstructorSyntax . { View | Diffs | r1.17 | > | r1.16 | > | r1.15 | More }
Revision r1.1 - 18 Dec 2003 - 10:38 GMT - DanielBonniot
Revision r1.17 - 09 Feb 2004 - 00:49 GMT - DanielBonniot
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.