#279 New Design for Constructors

brian Fri 11 Jul 2008

I've taken a few weeks to give some hard thought to the discussions we've had on constructors. Immediately after those discussions, my thought was that I didn't want to make any fundamental changes to the current design. But as I write more Fan code, my view is changing - what it I didn't expect was how much with-blocks would fundamentally change how I write code.

The core issue is that convention has become to use with-blocks instead of (or at least in conjunction) with constructors. What this means is that we've lost the ability to use constructors for their primary purpose - setting up an object with checked invariants. Consider this example:

const class Point
{
  new make(Int x := 0, Int y := 0)
  {
    if (x == null || y == null) throw ArgErr.make
    this.x = x; this.y = y
  }
  const Int x
  const Int y
}

Point.make(null, null)       // checking will catch errors
Point { x = null; y = null } // errors not caught

The issue is that with-block expressions are run after the constructor, so we don't get a chance to do any invariant checking. So we can 1) not allow invariant checking (which is basically what we have today) or 2) come up with a mechanism to run a routine after a constructor's with-block. I believe we should have invariant checking - checking an error condition at creation time allows us to fail fast at the point where the error occurs and makes it much easier to debug.

There was some proposals about a @required facet, but in the end you still have to package up the checks written by a human or by the compiler into some method like Tom suggested. We can evaluate facets with special compiler support later. So the question is what should the method be called? My suggestion is invariant. Other options like validate, init, etc are more intuitive, but I'd also hate to take away names like that for application code.

Once we have invariants that removes one of the biggest needs for constructors - invariant checking must now happen in an Obj.invariant override, not the constructor (unless you have no fields). At that point, I think having a single constructor makes sense according to Cedric's original proposal. Our new Point class looks like:

const class Point
{
  new (Int x := 0, Int y := 0) {this.x = x; this.y = y }
  override Void invariant() { if (x == null || y == null) throw InvariantErr.make }
  const Int x
  const Int y
}

Point(3,4)                    // constructor invoke syntax
Point { x=3; y=4 }            // constructor invoke using with-block
Point(null, null)             // correctly throws InvariantErr
Point { x = null; y = null }  // correctly throws InvariantErr

The fcode would look something like this:

Point(3) { y = 4 }

LoadInt 3             // 3
CallNew Point.new     // Point
Dup                   // Point Point
Dup                   // Point Point Point
LoadInt 4             // Point Point Point 4
StoreInst Point.y     // Point Point
CallVirt invariant    // Point

The syntax for calling a super constructor will be like it is now (and in C#):

const class Point3D : Point
{
  new (Int x := 0, Int y := 0, Int z := 0) : super(x,y) { this.z = z }
  override Void invariant() { super.invariant; if (z == null) throw InvariantErr.make }
  const Int z
}

To summarize the proposed changes:

  1. New Obj.invariant method, and InvariantErr class
  2. The invariant method is called after every constructor; if there is a with-block chained to the constructor, then it is called after the with-block has been evaluated
  3. Only one constructor is allowed using the syntax new(<formals>) {}
  4. Only super constructor chaining is allowed (since you can't chain to this anymore)
  5. Constructor invoke syntax changed to Type(<args>)
  6. Ctor fcode and VM emit will need to be seriously reworked probably to just use JVM/CLR constructors
  7. Reflection will report the single ctor as a Method with a name "new"
  8. Refactor the codebase

By convention I will still use make for factory methods. Because this such a huge breaking change I'm going to get it done shortly before our codebase gets any larger.

Comments?

cbeust Fri 11 Jul 2008

This sounds like a good improvement, I'll have to think about it more.

One quick reaction, though: I think the term "invariant" is not appropriate here because it's usually associated to a class-wide property (per Meyer's Design by Contract work). We should probably try to come up with a term that makes it clear that the tests performed in this block only apply to a freshly created object but that future manipulation of the object's state might invalidate it.

Also, one way of not taking away common names and verbs from the user (which is a good concern) could be to move these to facets instead of method names (I made a similar remark on the unit test thread a few minutes ago). For example, @validate?

-- Cedric

brian Fri 11 Jul 2008

One quick reaction, though: I think the term "invariant" is not appropriate here because it's usually associated to a class-wide property (per Meyer's Design by Contract work). We should probably try to come up with a term that makes it clear that the tests performed in this block only apply to a freshly created object but that future manipulation of the object's state might invalidate it.

Actually I like invariant because you could call it whenever you wanted even after initialization. For classes with mutable fields, it is highly likely that your initial invariants are the same as your normal runtime invariants. Of course calling it would be an application level decision.

andy Fri 11 Jul 2008

Overall I like that. I'm not sure about the term invariant, but I haven't been able to come up with a good argument against it, so it will probably work.

andy Fri 11 Jul 2008

Just thinking out load, it might be nice to have some keyword to auto-call invariant on field sets:

// so this
invariant Int x := 0

// is syntax sugar for
Int x := 0 { set { @x = val; invariant }}

JohnDG Fri 11 Jul 2008

+1 on allowing only a single constructor called new. Not sure how this will affect interoperability with existing Java/C# code.

+1 on validating fields either after the constructor or after the initialization with block.

-1 on having to manually verify the super class.

I prefer the guard syntax myself:

Int x | x != null := 0

Because then you can do automatic verification of the super class.

There is still a problem in any case of deciding when to verify. In general, Andy's proposal won't work because verification after setting individual fields doesn't allow an object to transition to an invalid state while on the way to a valid state, but sometimes the dependency for fields is complex and the values one field is allowed to take on depend on the value of another field.

brian Fri 11 Jul 2008

-1 on having to manually verify the super class.

I assume you are referring to manually calling super.invariant? I think that is a general problem with an overrides when you are supposed to call super. But sometimes it does actually make sense to let the subclass decide what to do.

I prefer the guard syntax myself

it might be nice to have some keyword to auto-call invariant on field sets

Additional features for annotating compiler implemented checks on field assignments is something we can later. But first I'd like to get the general purpose hand-coded feature implemented first and give it some runtime.

JohnDG Fri 11 Jul 2008

Any time methods are overridden, the subclass must have explicit knowledge of the implementation details of the superclass. This leads to brittle code.

This is why I don't like inheritance in general, except in the case of abstract superclasses requiring subclasses to implement N methods and preventing them from overriding any implemented methods. Safe, strong semantics that do not require a developer to know anything about the implementation details of the superclass.

In this case, overriding is particularly dangerous, because logic in the super class is designed under the assumption that invariants hold (i.e. that the user has properly initialized the object), and if they do not hold, it will lead to all sorts of difficult to diagnose bugs.

One construct that might lessen the pain is to support the notion of a method that cannot be overridden, but can be decorated. A method such as invariants is a prime candidate for such a method type. Then subclasses that wish their own invariants must use a @decorate facet on a method of the same name. The semantics of the @decorate facet would require an explicit invocation of the super class method.

There are lots of cases where such a construct would be useful (heck, even in a constructor, which might be required to decorate a superclass constructor, thus providing the developer the flexibility of choosing when to invoke the superclass constructor).

tompalmer Fri 11 Jul 2008

Overall, I love the proposal (including the Type(<args>) constructor call syntax). Sounds awesome.

I agree the whole need to call super thing is annoying all the time both here and elsewhere. In some cases, though, you need to control when super is called. Not sure that it should matter for good style in this case (since no object state should be changed). Still, I think I like invariant better than @invariant in this case. Hrmmm.

As far as the transition state, I think Eiffel only calls invariants after method exit, which matches Andy's example. Calling it beforehand breaks reentrant code. But in any case, I agree with not automating the calls for now.

Also, any way to replace the new with a factory method to avoid client code needing to change if there was a need to tweak things? Something to consider for later, perhaps.

Anyway, this is already a great start. Super happy.

brian Sat 12 Jul 2008

I think we all agree that how you override a method and call super might be a problem, but it is an orthogonal, general purpose problem. So let's not side-track the constructor design discussion (we can continue on another thread if you guys want).

Also, any way to replace the new with a factory method to avoid client code needing to change if there was a need to tweak things

I think we are loosing that ability. Although one thing I forgot to mention is that the simple literal syntax Depend("sys 1.2") today resolves to Depend.fromStr. With the new design it would:

  1. First resolve to Depend.new(Str)
  2. If that failed, it would then resolve to Depend.fromStr(Str)

So we could add a third step to resolve to Depend.make, although I'd prefer not to do that now until we get experience with the new design.

Any comments on the basic design? Any better suggestions for the method invariant?

tompalmer Sat 12 Jul 2008

I like invariant even though it's not called automatically after every method. It still teaches the proper principle.

alexlamsl Sat 12 Jul 2008

We can make invariant be called after every public method call (constructor included), and make a switch for whether to do that or just do minimal checks after constructor calls (or even not at all).

(I am thinking along the lines of assertions in Java...)

cbeust Sat 12 Jul 2008

Please, no. Design by Contract just doesn't work. I don't think we should push the concept of invariants beyond verifying that construction created a valid object.

brian Sat 12 Jul 2008

The only thing I really want to do now is automatically call invariant after construction. I'm not comfortable doing anything "automatically" beyond that without a lot more experience under our belt. Although the nice thing about this solution is that application code can always call invariant since it is just a normal method.

jodastephen Sun 13 Jul 2008

OK, this is an intriguing one, and I'm not sure a change here should be rushed. Please give us time to think it through.

The basic thrust of the proposal has some good points, however I have a number of problems.

1) There is an aim to separate construction from validation, however all we actualy get is a convention for doing so. There is nothing to stop a developer writing the validation in the constructor rather than using the invariant method. In fact, most developers transitioning from another language would do exactly that.

2) The proposal means that many classes now need two methods of construction rather than one. Once you start adding documentation with full comments, parameter descriptions and exception types (as is the coding standard for most big shops), this is a lot more "code".

3) The name invariant doesn't work for me at all. It implies way too much, and would constantly cause questions over whether this is DBC - DBC is not mainstream enough for Fan.

4) On the shorter construction syntax, losing the ability move a call from a constructor to a factory is a big loss for me. The existing way this works is a feature that will make codebases more long-lived without needing to resort to refactoring.

What would I change? Well, I think the thing to notice about the examples being thrown up is that the constructor isn't doing any real work. All it is doing is assigning the input variables to the slots. So why not make that automatic? This could be achieved by adding an extra suffix to the field slot definition:

const class Point {
  const Int x new := 0
  const Int y new := 0
  const Str name

  new {
    if (x == null || y == null) throw NewErr.make
  }
}

The new :=0 suffix causes a constructor to be created that accepts the associated field as a parameter. The constructor has each parameters in the order that the fields are defined. Since the name field does not have new it will not be part of the constructor. The with block approach allows all three fields to be assigned in any order.

The invalidate method now becomes a new method - which is not a normal method, thus the superclass override is guaranteed.

I'm out of time to describe this further right now, but I hope you get the picture...

JohnDG Sun 13 Jul 2008

I really like that -- if new is invoked after the initializing with block.

jodastephen Sun 13 Jul 2008

JohnDG - Yes, the new would be invoked after the with block or constructor, whichever was called. Here is a more complex example:

class Vehicle {
  Int topSpeed new
  Str colour new := "Black"
  Str name

  new {
    if (topSpeed == null || color == null) throw NewErr.make
  }
}

class Car : Vehicle {
  Int wheels new := 4

  new {
    if (wheels == null || wheels < 3) throw NewErr.make
  }
}

car1 = Vehicle.make(100, "Red", 4)
car2 = Vehicle.make(120)
car3 = Vehicle.make(110, "Blue", 3)
car4 = Vehicle {topSpeed=110, colour="Pink", wheels=3, name="PlasticPig"}

So, there are two classes, Vehicle and Car. Vehicle specifies one required constructor field (topSpeed), one optional constructor field (colour) and one non-constructor field (name). Because the superclass declares a constructor field that is optional, any constructor fields in the subclass must be optional (wheels).

The caller must pass the superclass arguments to the constructor first, followed by the subclass arguments. Optional arguments can be omitted as normal.

The calling order is as follows:

  • set superclass state from with or constructor
  • invoke superclass new method
  • set subclass state from with or constructor
  • invoke subclass new method

Finally, the new method could potentially be a standard method:

new Void validateInvariants() {
  // do validation
}

This is simply a normal method that is also called as the new method. Because it is a normal method, it can now be invoked at any point by the application code - such as to ensure invariants. Note that this naming would be optional - most code would just define new {...}.

cbeust Sun 13 Jul 2008

That's an interesting direction. The only thing that worries me a bit is that the ordering of fields becomes meaningful. To reuse your example, consider:

class Vehicle {
  Int topSpeed new
  Str colour new := "Black"
}

car1 = Vehicle.make(100, "Red", 4)

Then down the road, I move the fields around:

class Vehicle {
  Str colour new := "Black"
  Int topSpeed new
}

and suddenly, this no longer compiles:

car1 = Vehicle.make(100, "Red", 4)

Not saying it's a show stopper, but something to ponder. I also like the fact that your approach avoids some duplication.

-- Cedric

andy Sun 13 Jul 2008

I sort of lean towards just getting rid of arguments to the constructor, and then invariant can just become the constructor, which is automatically invoked after a with block or new call:

class Point
{
  new { ... }   // new never takes args
  Int x := 0
  Int y := 0
}

pt := Point { x=5; y=7; }   // instantiate using with block
pt := Point.new             // or using new()

Thats a very simply model, and would seem to work well with how we currently write code.

brian Mon 14 Jul 2008

I think there are some good ideas in Stephen's discussion. I would sum up his proposal as having two goals:

  1. can we automate field assignment by constructor param ordering (versus with-blocks). Andy suggested getting rid of ctor parameters altogether - I don't like that. For example Rect(0,0,10,10) is a lot easier to write than Rect { x=0; y=0; w=10; h=10 }.
  2. the current proposal requires a preInit and a postInit method - Stephen is proposing how to collapse that into one postInit method.

I think something towards goal 1 has a lot of merit. But I don't like implicit flattening out in subclasses. Often times subclasses can derive the necessary parameters of their super class other ways. Here is a use case (using current syntax):

class Expr
{
  const ExprId id
  const Location loc
  new make(ExprId id, Location loc) { this.id = id; this.loc = loc }
}

class CallExpr : Expr
{
  new make(Expr target, Str name, Expr[] args) 
    : super(ExprId.call, target.location) {...}
}

The point being that a subclass like CallExpr needs the flexibility to decide how to fulfill the superclass's constructor requirements - often it is computational, not just passing through arguments.

I don't think that goal 2 will work. If you want to collapse the preInit or the postInit into one method, you have to decide which one to ditch. I don't think you can ditch either one. You need preInit (often with parameters) to do state setup. For example it is common to pass an object into your ctor, then use that to initialize a bunch of fields. And you need postInit to validate what happens after the with-block has run. I don't particularly like the idea of two methods, but there is a good need for both: something before the with-block and for something after the with-block. If I had to choose just one, I would ditch the invariant/postInit method before I got rid of the true constructor which runs before the with-block.

I don't have a strong opinion on the current design versus the new design (which is based upon Cedric's original proposal). But there is definitely a hole in the current design for validating with-blocks. So any design is on the table as long as we solve that.

JohnDG Mon 14 Jul 2008

Right now, ordering of parameters in a constructor (or any other function) is meaningful -- if you change the order, you'll get something that doesn't compile. Doesn't seem very different from automatically producing a constructor that orders parameters in the same way they are ordered as field declarations.

Brian makes the excellent point that constructors are often used as factories -- massaging data, performing calculations, accessing fields, and storing the results (or passing them to superclasses) in a form quite different from the parameter list of those constructors. Such patterns of coding are unlikely to go away. If you force people to use the factory pattern, e.g. CallExpr.fromExpr, they'll ask why they can't do that stuff in a constructor, just like they used to do.

If there's a way to reconcile the two dichotomous uses of constructors (as factories and as field initializers), I can't see it. Which means both old-fashioned constructors and new-style with blocks are here to stay.

rainkinz Mon 14 Jul 2008

Hi,

Would named parameters solve this issue? So instead of using With blocks in constructors you could have:

const class Point
{
  new (Int x := 0, Int y := 0) {
   if (x == null || y == null) throw InvariantErr.new
   this.x = x; this.y = y 
  }
  const Int x
  const Int y
}

Point(3,4)       // constructor invoke syntax
Point(y=4, x=3)  // constructor invoke using named parameters instead of with-block
Point(null, 3)   // correctly throws InvariantErr, in this case x = null, y = 3 etc
Point(x = null, y = 3)  // correctly throws InvariantErr

Just a thought. Thanks

jodastephen Mon 14 Jul 2008

Brian, you've summed up what I'm suggesting well. And overnight I realised the problem with constructors changing the data to meet the internal state. However, JohnDG has really suggested the solution to that - factories.

class Expr {
  const ExprId id new
  const Location loc new
}

class CallExpr : Expr {
  const Expr[] args new

  // factory
  This make(Expr target, Str name, Expr[] args) {
    return CallExpr.make(ExprId.call, target.location, args)
  }

  // post-init checks
  new {
    if (args.size == 0) throw NewErr.make
  }
}

So, the factory acts as the pre-init, and thus you have both pre and post init (something I agree we need to allow for).

However, the downside I can see is if the developer wants to prevent there being a constructor - for example to enforce the creation of singleton instances. But this is a problem with with blocks currently anyway unless I've missed something. This might indicate that the problem is the extra-linguistic means of creating an object that with blocks allow (ie. no call to the constructor). This is a flaw in Java that seems to be replicated here - maybe the approach we should look at is making with blocks call the constructor (possibly auto-generated)?

brian Mon 14 Jul 2008

Would named parameters solve this issue?

Named parameters might be a fallback solution, but I'd prefer to work towards general purpose with-blocks because:

  1. a class like fwt::Label can use a default ctor now even though there are a dozen potential fields to intialize
  2. they can be used with any expression including factories (although you can only set const fields on a constructor with-block)
  3. they can invoke methods, not just set fields (including the add convenience)

However, the downside I can see is if the developer wants to prevent there being a constructor - for example to enforce the creation of singleton instances.

You can mark a constructor private or internal. That is how you do it in Java too. So I don't see the problem? Just to clarify:

// this code
Label { text="hello" }  

// is syntax sugar for
Label.make { text="hello" }  

// which is syntax sugar for
temp := Label.make
temp.text="hello"

However, JohnDG has really suggested the solution to that - factories.

Factories don't solve subclass initialization. For example in the compiler code, I also have a subclass of CallExpr called ShortcutExpr - it can't call a super method to initialize itself, but can only call a constructor.

tompalmer Mon 14 Jul 2008

With blocks (especially at construction time) are sweet and central to Fan right now. They also effectively create ad hoc constructors, which is why we are having this invariant question. But maybe it is overkill. Would people use the invariant feature?

I'm not sure I like field order mattering (i.e., the "new" keyword on fields as indicated above). That's far enough differing from current practice that I think it would trip people up too often. But maybe I'm wrong.

Scala solves that issue like so:

class Point(var x: Int, var y: Int)

That one line effectively the same as this in Fan:

class Point {

  Int x

  Int y

  new (Int x, Int y) {
    this.x = x
    this.y = y
  }

}

The Scala syntax is much clearer than the "new" on fields in my opinion (because the order obviously matters and it looks like a function call), but I'm not sure it's the best choice for Fan. Actually, in Scala, the class body is the default constructor. Also, it's the explicit var or val on constructor params that turns them into fields, too. (Leave it off, and it's just a param.) Nice in some ways, but you have to think carefully about the fact that all your local vars in your constructor are public fields by default in your class.

Anyway, I'm not convinced that invariant will hurt anything. Most people won't use it. That's my guess, but maybe I'm wrong. Still, some people could use it effectively, and the name is unlikely to get in people's way. Still, I also think facets like @required might help to clean up common cases and avoid the need for custom init validation.

Just some thoughts.

brian Mon 14 Jul 2008

Anyway, I'm not convinced that invariant will hurt anything. Most people won't use it.

I've kind of been thinking that maybe we don't do it and see how it goes. I've been writing a lot code without that feature using the current constructor syntax and it hasn't been the end of the world. We can always add it later.

JohnDG Mon 14 Jul 2008

Factories don't solve subclass initialization. For example in the compiler code, I also have a subclass of CallExpr called ShortcutExpr - it can't call a super method to initialize itself, but can only call a constructor.

How about introducing the notion of superclass assignment? Many times I've wanted to use a factory method instead of a constructor for initializing a superclass.

Something like,

Child { super = Parent.fromXXX(param2, param2); childField = "foo" }

Or for this thread's example:

static CallExpr fromExpr(Expr target, Str name, Expr[] args)
{
  return CallExpr { 
    super = Expr { id = ExprId.call, loc = target.location }
    // ...
  }  
}

Less magical and very powerful.

tactics Mon 14 Jul 2008

I really do not like the name "invariant". Methods should always be verbs, and invariant is an adjective. You can't invariant something. I don't think you can even invary it.

My suggestion is using "check" instead. It's short and sweet. I don't think it's a very common method name either. It seems like the kind of word that would appear more often as checkXXX or checkYYY, but plain old check would work nicely.

Excepting that, I think verify might work. It's more likely that people will want to use "verify" as a method name for themselves, but, like check, I think it's common for programmers to name methods verifyFoo and verifyBar instead of plain old verify.

I do like the Type(<args>) syntax. It's nice, clean, and consistent.

brian Tue 15 Jul 2008

Been doing a lot of thinking of the whole constructor topic, and basically everything I come up isn't quite right. If you haven't read this post by Gilad Bracha its a good read - although I don't really like his Newspeak solution.

Constructors serve the following purposes:

  1. Allocation - to allocate a chunk of memory for the object, this is always tied to a specific class because a given class determines how much memory it needs
  2. Initialization - how to setup the initial state of an object; in Fan we're using with-blocks for much of this work now, but there is still many reasons to require a real method to perform initialization computation
  3. Invariant Checking - verifying that the object is correctly initialized since either the constructor arguments or with-block could have messed things up. Ideally we want to fail fast so that our stack trace occurs at the point of error

Here is a list of random thoughts which in no way form a cohesive design:

  • Languages like Ruby/Python don't have constructors per se, they just pass thru the arguments of Type.new/Type() to the initialize/__init__ method. So they separate allocation from initialization - maybe that is what Fan should do? One thing to note is that these languages basically have the one constructor limitation we're proposing.
  • Perhaps every class has an internal method call "new" generated which only does allocation, and we leave everything else to normal OO methods which decide how to expose construction publically to other pods; pretty much everything would be convention then?
  • I've kind of switched sides, and now thinking more along Cedric's lines of not having a standard "invariant" method, but rather annotating methods with the @onNew facet which get run at construction time.
  • Maybe invariant checking is just an @after decorator on constructors? Maybe that works on any method after a with-block?
  • Maybe Type() is always syntax sugar for 1) Type.make, 2) Type.fromStr, 3) Type.new - then the caller is somewhat decoupled from how the object is constructed (at the source code level, not necessarily at the binary level)

cbeust Tue 15 Jul 2008

I think that 1) is a non-issue in garbage collected languages like Java or Fan (no malloc), so we can ignore it: it's done implicitly by the compiler/JVM. 2) and 3) are the real issues and I've been thinking along similar lines to yours but so far, I haven't come up with anything I really like.

I think Scala is on to something interesting with parameters declared as part of the "class signature".

Trying to think outside the box, how about allowing two bodies? The first one is the companion constructor and the second one is the constructor check:

class Point(Int x := 0, Int y := 0)
{
  Int surface
  Str name

  new(Str n)
  {
    name := n
    surface := x * y
  }
  {
    assert name != null
  }
}

class Point3D(Int x := 0, Int y := 0, Int z := 0) : Point(x, y) {
}

Of course, you might want to do a few checks before reaching the companion constructor, so we should probably consider allowing three bodies: pre check, companion constructor, post check.

Sounds a bit whacky, I know, but I think the time has come to bounce crazy ideas around and see if we can come up with something useful...

-- Cedric

andy Tue 15 Jul 2008

I find myself continually moving towards just getting rid of constructors. I am in favor of a no-arg new method that handles allocation/field assignment.

a := Foo.new
b := Foo { val="Bar" }
c := Foo.make("Bar")

class Singleton
{
  private new   // can control scope, but not define method body
}

I have mixed feelings on the invariant step - I'm not sure whether thats a language issue or an API issue. But if it was a language feature, I think I'd prefer using a facet to call a method post-construction.

tompalmer Tue 15 Jul 2008

Another purpose of constructors:

4) Provide convenient order for init params when that's nicer than naming. For example, as mentioned above, where Point(3, 2) is better than Point {x = 3; y = 2}. But I think the number of meaningful ordered params is usually small. And with with blocks, I'm not sure this case is as necessary as it is in other languages, so I'm not sure the syntax needs optimized for it (such as in Scala).

Side note, I did mention the split new/init as an option in the prior round of this conversation. It wouldn't be inventing the wheel. Might be less obvious to C#/Java folks, but people have lived with it in Python/Ruby land for a long time.

Anyway, in this model, I'd say there's a default static new method defined on each class that calls init if any (and takes the same params), but you can define your own new if you want (including making it private). And make up special syntax like new Type for instance creation that is only allowed within the given class (but in any method in that class).

So, here, the only thing special about the new method (vs. any other static method) is that it's generated automatically if you don't make it yourself. Also that saying Type(<args>) calls the new method.

By the way, can you call static methods on mixins in Fan?

brian Tue 15 Jul 2008

I think Scala is on to something interesting with parameters declared as part of the "class signature".

I don't care for Scala's constructors - they are a turn-off for me. And I really dislike the way constructor parameters become fields - too weird for me.

Sounds a bit whacky, I know, but I think the time has come to bounce crazy ideas around and see if we can come up with something useful...

It is a bit wacky - but wacky ideas can only help spur thoughts in new directions! That is kind of what I was thinking with a generic @after decorator. Maybe decorators can reference another method or be defined in place:

@after={ assert(name != null) }
new (Str name) {...}

@after=verify
new (Str name) {...}

Provide convenient order for init params when that's nicer than naming.

Actually I think the problem is bigger than this - it important to remember that parameters aren't always just passed thru and assigned to fields. For example:

class Rect
{
  new (Point pt, Size size) { x=pt.x; y=pt.y; w=size.w; h=size.h }
}

tompalmer Tue 15 Jul 2008

So, would @after be usable on any method (to be called after any following "with" block)? I'm not sure I like it, but to spend time on alternatives goes back to rethinking "with" blocks vs. closures again, and I doubt you want to do that.

Also, I guess the Python/Ruby split new/init isn't a winner here? I haven't heard direct feedback. One thing wrong with pure static factory methods is that subclasses have nothing to call for super init unless you think far enough to provide something.

Also, I guess in split new/init, the init method wouldn't be virtual because the parameters often wouldn't match.

brian Tue 15 Jul 2008

By the way, can you call static methods on mixins in Fan?

Yes

Also, I guess the Python/Ruby split new/init isn't a winner here?

I wouldn't say that - its on the table as far as I'm concerned. My main issue with that solution is that init is just a normal instance method. You make the good point it has issues with parameter overrides (because it pretty much has to be virtual). Today this works because constructors are not inherited into their children's slot namespace.

But more importantly there isn't really any contract of how to call your superclass constructor. It would largely rely on the developer having to call super.init.

1) is a non-issue in garbage collected languages like Java or Fan (no malloc), so we can ignore it: it's done implicitly by the compiler/JVM.

Actually this is the heart of the whole constructor issue - when do we actually allocate the object? In Java we allocate and initialize in the constructor - this is why constructors aren't normal OO methods. In Ruby the alloc is done via Type.new, which implicitly calls initialize (which is just a normal method). The reason constructors can't just be instance or static methods is that somewhere we actually need to do the allocation. Deciding when/how that happens is a key part of the design.

On one hand we can go down the Java/C# road where constructors are "special" methods which implicitly allocate the object - but this means they aren't true instance or static methods. Or you can go down the Python/Ruby road where you try to use normal instance/static methods to do initialization and pull allocation into another construct like Type.new.

cbeust Tue 15 Jul 2008

Brian writes:

@after={ assert(name != null) }
new (Str name) {...}

@after=verify
new (Str name) {...}

I have been thinking along similar lines but I ended up rejecting this idea because I couldn't find any practical use for @after on regular methods (if you need @after on a method, why not just put the code at the end of that method?). Another concern with this approach is to allow code inside facet with-blocks, something that you, as one of the language developers, are probably not looking forward to either :-)

-- Cedric

andy Tue 15 Jul 2008

I still think constructors just complicate things - the only argument I heard against the no-arg new method was the Rect case - but that easily be solved with a static factory method:

a := Rect { x=0; y=0; w=100; y=50; }
b := Rect.make(0, 0, 100, 50)
c := Rect.new  // use default field values

The simplest solution tends to be the best, and I have yet to hear a compelling reason why we need anything more complicated.

Allocating a new instance and verifying the state of an object are orthogonal issues to me, so I think its incorrect to couple them together like we've been trying to do. I think a general purpose feature like Brians @after facet is a better solution to that problem. But I'm not sold on anything yet.

brian Tue 15 Jul 2008

I still think constructors just complicate things - the only argument I heard against the no-arg new method was the Rect case

Actually that use case has been stated many times - often you initialize an object's fields from another data structure and you don't necessarily want to require that argument be stored to a field:

class Rect
{
  new (Point pt, Size size) { x=pt.x; y=pt.y; w=size.w; h=size.h }
}

It is just a dummy example, just to show that sometimes the arguments to initialization aren't the same as the fields you store.

And it is easy to wave our hands and say just use a factory - but we have to remember that does not work for subclasses. Factories bind the allocation to a specific class type.

andy Tue 15 Jul 2008

Well that argument only holds if you assume we can't delegate the allocation to the sub-type. I realize its an issue, but was sort of assuming we could figure something out to solve that problem.

brian Tue 15 Jul 2008

Well that argument only holds if you assume we can't delegate the allocation to the sub-type. I realize its an issue, but was sort of assuming we could figure something out to solve that problem.

Cedric kind of dismissed that issue too. But for me, this is the problem we are trying solving. The reason you can't use a normal instance or static method is that somewhere you have to allocate the object.

If you allocate manually inside a factory method, then subclasses can't use that routine for their initialization.

And you obviously can't call an instance method until you've done allocation. Going down this path is how you get to Ruby/Python's solution. But it has all the problems identified above.

This why a real Java/C# like constructor has lots of advantages because it explicitly binds allocation with a specific method call. Because it is special we know how to apply it to subclasses and slot inheritance.

JohnDG Tue 15 Jul 2008

What if a constructor is fed the newly allocated object, and then merely initializes it? That is, perhaps a constructor is just a static method that accepts and returns an object of the same type as the containing class.

Then a subclass can (optionally) not call the constructor, or could explicitly call constructors further up the class hierarchy, if it wanted specific behavior.

This separates allocation from initialization. Allocation happens when you do Class(), while initialization happens when you call a factory-like method. If you didn't supply a new object to the constructor, it would automatically be inserted for you.

static Label make(Label in = Label()) 
{
   // initialize 'in'
}

static Label fromString(Str string, Label in = Label()) 
{
   in.text = string
}

Perhaps add some syntax sugar to eliminate duplication.

tompalmer Tue 15 Jul 2008

JohnDG's solution might be workable, but I'd hate to save the pretty syntax (shortcut Type()) for the uncommon case (hidden allocation within the type). I think it's also somewhat awkward to need the special param last (though I understand why it's there).

Anyway, I think the original proposal on this thread (only one constructor using the new keyword - but we should leave out invariant for now) is the simplest thing that could work. I like it, and I think it's good enough. We've survived constructors in Java all this time, despite the annoyances. I think this proposal is better than current Fan. And it's not really much incompatible with Andy's or John's preferences.

Still, just for fun, split new/init provides combined convenience and flexibility. It might be worth considering. It requires changed rules on reusing names for nonvirtual (including static) methods. And we shouldn't worry about the compiler enforcing super.init calls in this model. Might be a way to force it, but there are all kinds of such bugs we can write in code, anyway. Maybe it's just tradition that makes us think this case is special. Or in other words, I agree with John that it might be okay to let people just call supers as they see fit.

Here are a few versions of a sample program to show how split new/init might work. (And again, I think failing this we should just stick to one constructor named new - and no invariant for now.)

// Using the split new/init proposal.

class Nonvirtuals {

  Void main() {
    echo(Person("Dude"))
    echo(AgedPerson.namedBob(999))
  }

}

**
** An abstract type, just to show how you could make custom 'new' factories.
**
mixin Person {

  **
  ** This method would *not* be generated by the compiler. It wouldn't know what type to use.
  ** However, we can make it custom here if we want.
  **
  static Person new(Str name) {
    return PersonImpl(name)
  }

  **
  ** Note that this is *not* virtual. Will this trip some folks up?
  **
  Void init(Str name) {
    this.name = name
  }

  abstract Str name

}

**
** This class is just a default implementation for Person.
**
class PersonImpl: Person {

  override Str name

  override Str toStr() {
    return "$name is a person"
  }

}

**
** A subclass with a different init signature.
**
class AgedPerson: PersonImpl {

  **
  ** And here's a non-new factory method just for fun.
  **
  static AgedPerson namedBob(Int age) {
    return new("Bob", age) // equivalent to AgedPerson.new("Bob", age) or shortcut AgedPerson("Bob", age)
  }

  Int age

  Void init(Str name, Int age) {
    super.init(name)
    this.age = age
  }

  override Str toStr() {
    return "${name} is ${age} years old"
  }

}
// What the compiler would generate from the split new/init code.

class Nonvirtuals {

  Void main() {
    echo(Person("Dude"))
    echo(AgedPerson.namedBob(999))
  }

}

**
** An abstract type, just to show how you could make custom 'new' factories.
**
mixin Person {

  **
  ** This method would *not* be generated by the compiler. It wouldn't know what type to use.
  ** However, we can make it custom here if we want.
  **
  static Person new(Str name) {
    return PersonImpl.new(name)
  }

  **
  ** Note that this is *not* virtual. Will this trip some folks up?
  **
  Void init(Str name) {
    this.name = name
  }

  abstract Str name

}

**
** This class is just a default implementation for Person.
**
class PersonImpl: Person {

  **
  ** This method would be generated by the compiler.
  **
  static PersonImpl new(Str name) {
    person := PersonImpl.type.alloc // or 'new PersonImpl'
    person.init(name)
    return person
  }

  override Str name

  override Str toStr() {
    return "$name is a person"
  }

}

**
** A subclass with a different init signature.
**
class AgedPerson: PersonImpl {

  **
  ** This method would be generated by the compiler.
  **
  static AgedPerson new(Str name, Int age) {
    person := AgedPerson.type.alloc // or 'new AgedPerson'
    person.init(name, age)
    return person
  }

  **
  ** And here's a non-new factory method just for fun.
  **
  static AgedPerson namedBob(Int age) {
    return new("Bob", age)
  }

  Int age

  Void init(Str name, Int age) {
    super.init(name)
    this.age = age
  }

  override Str toStr() {
    return "${name} is ${age} years old"
  }

}
// Effective behavior that runs in Fan 1.0.28.

class Nonvirtuals {

  Void main() {
    echo(Person.new1("Dude"))
    echo(AgedPerson.namedBob(999))
  }

}

**
** An abstract type, just to show how you could make custom 'new' factories.
**
mixin Person {

  **
  ** This method would *not* be generated by the compiler.
  ** It wouldn't know what type to use, but we can make it custom here if we want.
  **
  static Person new1(Str name) {
    return PersonImpl.new2(name)
  }

  **
  ** Note that this is *not* virtual. Will this trip some folks up?
  **
  Void init1(Str name) {
    this.name = name
  }

  abstract Str name

}

**
** This class is just a default implementation for Person.
**
class PersonImpl: Person {

  **
  ** This method would be generated by the compiler.
  **
  static PersonImpl new2(Str name) {
    person := PersonImpl.make
    person.init1(name)
    return person
  }

  override Str name

  override Str toStr() {
    return "$name is a person"
  }

}

**
** A subclass with a different init signature.
**
class AgedPerson: PersonImpl {

  **
  ** This method would be generated by the compiler.
  **
  static AgedPerson new3(Str name, Int age) {
    person := AgedPerson.make
    person.init3(name, age)
    return person
  }

  **
  ** And here's a non-new factory method just for fun.
  **
  static AgedPerson namedBob(Int age) {
    return new3("Bob", age)
  }

  Int age

  Void init3(Str name, Int age) {
    super.init1(name)
    this.age = age
  }

  override Str toStr() {
    return "${name} is ${age} years old"
  }

}

brian Tue 15 Jul 2008

static method that accepts and returns an object of the same type as the containing class.

A static method which always operates on an instance is pretty much an instance method isn't it? That is why a language like Ruby uses Tom's proposed design with the initialization method as just a normal instance method (versus a static method).

Still, just for fun, split new/init provides combined convenience and flexibility.

This is why I'm starting to sour on this design:

  1. since we already mapping to Java/IL, we might as well just use a real constructor which also makes Java/C# to Fan code easier
  2. there isn't a lot of value not using a real constructor as factory/instance intializers can still be used
  3. the Java/C# constructor design might not be perfect, but it isn't one of those features I really feels needed to be fixed - so why buck a design that works ok?
  4. an init method creates very weird inheritance rules - it is a non-private method which isn't inherited into subclasses, yet subclasses might be able to access it via super. Once you start trying to tackle that special case, why not just make it a regular constructor special case? This is really the show stopper for me - because it gets its tentacles into a lot of reflection code too.

Anyway, I think the original proposal on this thread (only one constructor using the new keyword - but we should leave out invariant for now) is the simplest thing that could work.

This is my current thought. We can always add invariant/@onNew later.

One thing I want to tweak with the original design:

Point.new(x,y) => explicitly call the constructor
Point(x,y)     => indirect construction

Indirect construction would be the convention, but uses a set of rules of decouple the caller from how the class constructs itself:

  1. if Point.make factory we use that, parameters much match or compile time error
  2. if one String argument and Point.fromStr defined use that
  3. fallback to Point.new explicit constructor

This seems like a way to have source level compatibility with constructors versus factory methods (and keep our current serialization syntax). I don't love the special rules, but they seem pretty useful and easy to remember.

alexlamsl Wed 16 Jul 2008

It won't be too harmful to just have the default make and initialise everything else using with-block - you can put initialisation logic inside once method(s) and invariant checks as methods as well.

Login or Signup to reply.