construction with blocks must be validated (as they can set const fields, and are called from serialization which could be maliciously altered)
a simple solution is required
there are four phases
(1) conversion - where input parameters are adjusted to the storage fields
(2) allocate - where the memory is reserved
(3) assignment - where the data is assigned to the storage
(4) validation - where the data is checked (this could be before assignment)
superclasses must be handled
Proposal
This proposal uses the concept that factories best represent (1). Validation (4) must happen based on either the with-block data, or the data from the factory. Allocation (2) and assignment (3) are automatic.
Here is the example class, a line:
class Line {
const Int startX := 0;
const Int startY := 0;
const Int endX;
const Int endY;
...
}
Factories and the new block
Factory methods are declared using a This return type:
class Line {
...
static This make(Point lineStart, Point lineEnd) {
return new {
startX = lineStart.x;
startY = lineStart.y;
endX = lineEnd.x;
endY = lineEnd.y;
}
}
static This make(Point lineEnd) {
return new {
endX = lineEnd.x;
endY = lineEnd.y;
}
}
static This make(Int endX, Int endY) {
return new {
new.endX = endX;
new.endY = endY;
}
}
}
The new {} block only needs to define those fields that have not got a default value. However, defining a new {} block without a required field would be a compile error.
The new {} block can use the new. prefix to refer to the fields of the new instance as opposed to the local scope. This is similar in concept to the this. prefix.
Factory methods are basically ordinary methods. They can call other factory methods, and return cached instances, proxies or subclasses.
The new {} block can also be used directly on the public API, aka a with block:
The new validator (4) is autogenerated by default:
class Line {
...
new {
}
}
However, it may be specified:
class Line {
...
new {
if (endX == null || endY == null) throw NewErr.new
}
}
Allocation (2) and Assignment (3) take place automatically based on the named parameters passed to the new {} block. These stages occur however the class is instantiated (factory/with-block/deserialization). The code within the new validator can assume that the fields have been assigned.
Declaring the new validator serves two purposes. Firstly, it provides for validation (4). State can be checked - and this occurs after both normal construction, with-block construction and deserialization.
Secondly, the new validator can have access control added. Adding access control may affect deserialization (ie. it might prevent the class from being declared serializable).
Simple assignment factories
A final possible factory style (and this is an optional part of the whole proposal) is syntax sugar for declaring simple factories that just assign data:
class Line {
...
static new make(startX, startY, endX, endY);
}
// syntax sugar for
class Line {
...
static This make(Int startX, Int startY, Int endX, Int endY) {
new {new.startX=startX; new.startY=startY; new.endX=endX; new.endY=endY}
}
}
// calling style
line := Line(0, 10, 20, 30)
line := Line.make(0, 10, 20, 30)
Here, we are defining a factory taking the four listed fields in the order specified. Note that the types of the fields do not need to be declared as they are picked up from the field definitions. All fields without a default value must be listed. Any fields at the end of the list that have a default value would be optional.
Superclasses
Superclasses are handled as proposed by JohnDG, by assigning to super:
class ColouredLine : Line {
Colour colour;
// simple assignment factory
static new make(startX, startY, endX, endY, colour);
// normal factory
static This make(Point start, Point end, Colour colour) {
return new {super=Line(start,end); new.colour=colour}
}
}
The new {} block call must contain either a super= or initialize all the fields of the superclass. If the subclass needs to initialize the superclass using any more complicated mechanism, then the subclass must write factory methods.
Abstract superclasses may have factory methods and use the return new {} syntax. Such a factory can only be called from a subclass new {} block.
The new validator of the subclass only needs to validate its own state. The new validator of the superclass will already have been called:
class ColouredLine : Line {
...
new {
if (colour == null) throw NewErr.new
}
}
Summary
Essentially, this proposal only allows objects to be created by calling the named parameters with-block style new {} block. Factories and the new validator then perform the pre and post processing.
Finally, apologies for the long post - hopefully, this is clear enough to discuss...
tompalmerWed 16 Jul 2008
I sort of like it, but I want my Point(x, y), and I'm really not sure the validator is needed. Also, how to make the default new nonpublic?
I also have yet another variation on Brian's latest recommendation on the other thread, but I'll defer that for now.
jodastephenWed 16 Jul 2008
You can have Point(x,y) - simple declare a simple assignment factory. The calling style, ie. Type(args) is a separate issue to the underlying factories/constructors.
The easiest way to think of the new validator is as the single constructor - you just don't have to declare the arguments (as they are passed and assigned as named parameters automatically).
brianWed 16 Jul 2008
Stephen - thanks for the detailed proposal. I'm still trying to digest it and figure out how it differs from the single constructor proposal.
I think the big differences are:
the new declaration is for the post-construction routine (new validator)
new {} block is the constructor (allocation)
compiler ensures that your super class new validator is always run
initialization computation is done in a static method which utilizes new {} block
What I like most is that you've flipped the initializer method and validator method, so the natural "new method" does post-construction validation. I'm not sure how intuitive that is, but it a good direction to think in because it avoids all the issues we've had about what to call/how to annotate the post construction method(s).
Although these are the problems I see:
I think my current single constructor proposal is easily grasped by a Java/C# programmer (more so than the currently implemented design). This requires a new way of thinking - what advantages does this approach have to make that difference worth it?
I'm having trouble understanding how a static method could have a This return since it will always be the declaring class - but I think you proposed that so I wouldn't have to type the classname? Maybe that might be a nice feature for all static methods, currently I flag as compiler error.
The new inside the new with-block isn't really needed since it implied
The new with-block isn't quite as convenient as just working in a normal instance or ctor method
The "JohnDG super proposal" seems elegant, but seems weird to me (back to the advantages over different question)
It seems a bit counter intuitive that new is called after construction
How does the compiler generate a "default constructor" for you?
tompalmerWed 16 Jul 2008
So the new validator (and the fact that it's run after rather than before the with block) is really the novel feature of this proposal. And the style of super call. I think everything else is mostly doable with current other proposals.
I'm afraid that having some setters only for construction could be a bit odd. Maybe better to pass in other config objects if we want named parameters.
Do static factories really not play well with const initialization? If that's true (and it makes sense at the moment since factories could supply previously created or unrelated objects anyway), then I'm actually starting to favor the idea of not changing Fan's current model much at all. I just have a couple of alternatives for slight modifications that I think we should consider.
My alternatives for slight changes to current Fan (1.0.28) constructors:
Let Point(x, y) call Point.make(x, y) (and maybe not support fromStr with this syntax).
Since new make() looks weird, just let the default constructor be called new instead of make, and as a special case, don't require the redundant word (so just new() {/*...*/}). Again Point(x, y) calls Point.new(x, y).
And any other constructors can be called "supplemental constructors" (constructors just like today's fan, such as new fromStr(Str str) {/*...*/}. These are also called just like today Blah.fromStr("something")). Again, the point is to allow some custom logic while playing nice with const and with blocks.
The question raised by Stephen, I think, is whether it's better to have multiple constructor methods or to allow custom handling in one constructor after setters have been run.
I currently vote for just keep Fan as is today with one of the two slight modifications recommended above.
tompalmerWed 16 Jul 2008
I posted before seeing Brian's reply, by the way. Apologies on any resulting confusion.
JohnDGWed 16 Jul 2008
I really like this proposal for the following reasons:
It completely eliminates constructors in the ordinary sense. Constructors are one of the most abused tools in all of object-oriented programming. I've seen constructors that span more than a hundred lines of code with many points of failure. Similarly, I've seen classes with 10 or more constructors, having several boolean parameters to turn various settings on and off, which are a royal pain to decipher.
It puts object construction where it really belongs: in factories.
It performs automatic validation for superclasses. This really is the right direction -- the semantics are harder to misuse.
It provides a nice route to validation after deserialization.
It permits a lot of flexibility in superclass initialization. Brian has noted that assigning super is weird -- however, super is already treated as a field/slot, even in Fan, because you can access methods using the notation super.. super can be viewed as just another slot, one that happens to point to the superclass, with "automagic" delegation to this slot for non-overridden methods. With this view, it's natural to want to assign super.
Now the one thing I don't like in this proposal is the conflicting uses of new and the non-standard with block. In one instance, new seems to create a new instance and the subsequent with block in the factory method initializes the new instance. However, in the other instance, new is used to perform validation after allocation and initialization. This is confusing.
In my view, new should refer only to allocation, so the syntax of the factory methods would remain unchanged, but the verify method would be changed to something like verify. e.g.:
class Line {
...
verify {
if (endX == null || endY == null) throw NewErr.new
}
}
Even if this proposal isn't accepted, I agree with Brian that it's a productive way to think about the problems involved.
jodastephenWed 16 Jul 2008
Brian, I think part of the problem is that I'm still a little unclear as to the exact details of how your proposal stands after the other long thread.
I also think that your summary of this proposal isn't quite right. I see the new validator as effectively being the "constructor". It implicitly takes all the fields as arguments for itself and its superclass using a named parameter style. They are passed directly from the new-block. As such, the only code you write in this "constructor" is the validation code.
The bytecode generated would be a real constructor with one parameter for each field in the superclass, followed by each field in the subclass. This would not be visible to Fan code (except maybe in reflection).
Effectively, the only way to instantiate an instance is using a new-block. A construction with-block (type (2)/(3) from the other thread) is simply syntax sugar for a call to the new-block. Although the new-block appears to be initialising the state, it is actually just passing named parameters to the "constructor".
On your points:
The This return type is a convenience which I was using to identify constructors. I'm not fussed about that detail.
The new. inside the new-block is needed to override the local scope in the same way that this. overrides the local scope to refer to the enclosing instance.
The super proposal actually opens up a more wild idea. (*What if Fan didn't have direct inheritance at all? What I mean is implementation using composition rather than inheritance. The calling code would see no difference, but within the class itself there would be a super field that holds a reference to an instance of the superclass - which could actually be a subclass of the superclass!*)
As I've explained, new validator is effectively the "constructor".
The default "constructor" is new {}. It still allows assignment to all fields including const, it just doesn't have any validation.
By the way, the use of Point(x,y) falling back to alternatives including make and new is something I think I can agree with.
Comparing
Comparing the proposals as best I can, I don't see how just having a single constructor solves the problem of validating with-blocks that are run immediately post-construction. If those with-blocks have special powers (ie. they can set const fields) then they must be vaildated. If they can only access the public API, then they are nothing special, and are perfectly safe.
Another factor to bear in mind is that IIUC then the JVM Memory Model requires that fields like const that are shared between threads must be declared final in bytecode. This means that with-block data must be set via a genuine JVM constructor.
So, the key advantage of this proposal is that however an object is created it is properly validated wrt with-block or factory:
Point(10,20)
Point.make(10,20)
Point {x=10;y=20}
All three eventually call the new validator "constructor".
The question is whether this is enough to justify the extra complexity. The problem is that as long as you require with-blocks to set const fields, you need the complexity.
jodastephenWed 16 Jul 2008
Actually, having read JohnDG's verify idea, I quite like that. Same concept, better name.
tompalmerWed 16 Jul 2008
Brian, how tough would it be to implement a post-with/new block feature? I'm not convinced it would really be used in practice, but if it were added experimentally (if not too much effort), then it would be a chance for people to see if they would really use it or not.
If it did exist, and if used, I think it would be used for more than validation, so I'm starting to think that the syntax or name should imply that.
brianWed 16 Jul 2008
Brian, I think part of the problem is that I'm still a little unclear as to the exact details of how your proposal stands after the other long thread.
Tonight I'll rewrite up my proposal into a single coherent post.
I also think that your summary of this proposal isn't quite right. I see the new validator as effectively being the "constructor".
I'm having trouble wrapping my head around this, because I'm trying to map it how I actually emit a Java or IL constructor, and use helper methods. Some of it is terminology. I think what you are saying is:
any initialization computation happens in the static factory method, which then:
the with-new-block passes field values to allocatation/Java ctor which inits fields
normal with-block runs and potentially sets fields
new-validator runs to verify the state
My proposal is that 1 and 2 run in a normal Java style constructor and that 4 happens in some other method(s) annotated with the @onNew facet. I think both ways let you do the same things, but I think my proposal is a little more in-line with how Java/C# work today.
What if Fan didn't have direct inheritance at all? What I mean is implementation using composition rather than inheritance
Actually I'm a fan of prototype based inheritance (like Self), but with Fan I was trying to leverage the JVM/CLR OO model as much as possible.
Another factor to bear in mind is that IIUC then the JVM Memory Model requires that fields like const that are shared between threads must be declared final in bytecode
Actually neither proposal solves this because you can't ever really have generic with-blocks run after the constructor or during deserialization on final fields. In practice I don't think it will ever be a problem due to how objects move between threads. But if it was a problem we'd have to use volatile or synchronized to solve it.
Brian, how tough would it be to implement a post-with/new block feature?
Well the whole constructor rework is a huge amount of work, so I want to get it right before I start. The post validation step itself is a rather smaller piece of the whole puzzle.
If I were to write your code using my proposal it would look something like this:
class Line {
...
static This make(Point lineStart, Point lineEnd) {
// you make assumption this is a special construct, to me it just
// means call the no-arg new method/ctor and apply the with-block;
// just dummy of course because one of these would be the "primary
// ctor" we'd force the subclass to use
return new {
startX = lineStart.x;
startY = lineStart.y;
endX = lineEnd.x;
endY = lineEnd.y;
}
// remember can't overload by params, this one needs a new name
static This makeEnd(Int endX, Int endY) {
// don't need special new syntax, because normal with-block works
return new {
endX = endX;
endY = endY;
}
// here we actually have a normal constructor with/without parameters
new (Point s := null, Point e := null)
{
startX = s?.x
startY = s?.y
endX = e?.x
endY = e?.y
}
// validation is done via any method tagged with @onNew facet
@onNew Void verify() {
if (endX == null || endY == null) throw NewErr.new
}
}
class ColouredLine : Line {
Colour colour; // you must be British :)
// subclasses have to route to single superclass ctor (just like
// now, except there only one)
new (Point s := null, Point e := null) : super(s, e) {}
static This make(Point start, Point end, Colour colour) {
return new(start, end) { colour = colour }
}
}
JohnDGWed 16 Jul 2008
I think both ways let you do the same things, but I think my proposal is a little more in-line with how Java/C# work today.
That's true, but it looks out of place in Fan. Not as clean as Joda's proposal, nor as clean as the rest of Fan, generally.
You know you brought all this on yourself by introducing with blocks, don't you? :-)
jodastephenWed 16 Jul 2008
1. any initialization computation happens in the static factory method, which then: 2. the with-new-block passes field values to allocatation/Java ctor which inits fields 3. normal with-block runs and potentially sets fields 4. new-validator runs to verify the state
I am proposing that (1) is always emitted as a JVM static method, and (3)/(4) are a JVM constructor:
// Fan
class Line {
const Int startX;
const Int startY;
Int endX;
Int endY
static This make(Point lineStart, Point lineEnd) {
return new {
endX = lineEnd.x; // note odd order - end followed by start
endY = lineEnd.y;
startX = lineStart.x;
startY = lineStart.y;
}
}
new { // or verify
if (endX == null || endY == null) throw NewErr.new
}
}
line1 := Line(point1, point2)
line2 := Line {startX = 0; startY = 0; endX = 10; endY = 20}
// Java equivalent
public class Point {
final Integer startX;
final Integer startY;
Integer endX;
Integer endY
public static Line make(Point lineStart, Point lineEnd) {
// odd order of definition in fan code is reordered to correct order here
return new Line(lineStart.x(), lineStart.y(), lineEnd.x(), lineEnd.y());
}
public Point(Integer startX, Integer startY, Integer endX, Integer endY) {
super();
this.startX = startX;
this.startY = startY;
this.endX = endX;
this.endY = endY;
if (endX == null || endY == null) throw new NewErr();
}
}
Line line1 = Line.make(point1, point2);
Line line2 = new Line(0, 0, 10, 20);
As shown, the JVM constructor simply takes each field in order of declaration (superclass fields would be listed before subclass fields). This means that the constructor is genuine, and can set final fields correctly.
...you can't ever really have generic with-blocks run after the constructor or during deserialization on final fields.
The new-block is not exactly the same language feature as a with-block. It has different abilities (setting const and followed by verification). You can only save the "generic with-block" concept if you lose the ability to set const fields (because then you don't need verification either).
// new-block
new {
// operations on a newly created instance
// may set const fields
}
// with-block
object {
// operations on an existing instance
// may not set const fields
}
You could think of it as "if the with-block is preceeded by the new keyword then it is a new-block and is used to initialise an object rather than change its state". Of course sometimes the new keyword is invisible:
I still think "change nothing" (except for call syntax) should still be an option on the table.
I plan to refrain from mentioning it again, though, as long as my opinion doesn't change. Hopefully I have the willpower to avoid posting too much.
brianThu 17 Jul 2008
As shown, the JVM constructor simply takes each field in order of declaration (superclass fields would be listed before subclass fields). This means that the constructor is genuine, and can set final fields correctly.
This is very interesting, although I'm not sure how practical it would be. It would likely create some extremely large constructor signatures and very deep call stacks. Plus it doesn't help much with deserialization - I guess I could buffer everything up, then create one big ctor call, but then you couldn't deserialize off the stream as well.
still think "change nothing" (except for call syntax) should still be an option on the table.
I think I'm coming back around full circle on this - I'll create another post.
brianFri 18 Jul 2008
I've gone back and reread this proposal carefully. As far saying that a class has a single new operator which allocates the instance - I really like that (and has been championed by Tom and Andy in the 2nd thread). The hard part is coming up with a design for initialization which chains well through subclasses and is a little more than "just convention".
This proposal uses this:
static This make(Point lineStart, Point lineEnd)
{
return new
{
startX = lineStart.x
startY = lineStart.y
endX = lineEnd.x
endY = lineEnd.y
}
}
static This make(Point start, Point end, Colour colour)
{
return new {super=Line(start,end); new.colour=colour}
}
Current design uses this:
new make(Point lineStart, Point lineEnd)
{
startX = lineStart.x
startY = lineStart.y
endX = lineEnd.x
endY = lineEnd.y
}
new make(Point start, Point end, Colour colour) : super(start, end)
{
this.colour=colour
}
The current design uses less syntax and seems more obvious to a C++/C# programmer (and hopefully a Java programmer). So I'm not understanding how this proposal is more elegant that what we currently have. Am I just missing something? Please educate me :^)
JohnDGFri 18 Jul 2008
Java supports infinite constructors and has no notion of with blocks. Fan, in its current form, supports one constructor and constructing with blocks.
Here's the key point: the purposes and semantics of constructors and constructing with blocks overlap.
This is why I've referred to the design as muddled, because as Stephen has pointed out, construction in Fan is not an atomic operation where everything takes place in a single privileged area (as it is with Java). Rather, it's potentially a three phase system, a back and forth between user and class code, where the implications of some constructs are neither obvious nor intuitive because of modal semantics (identical looking with blocks meaning two different things in different contexts).
Now much Fan source code does not even use constructors: it uses with blocks exclusively. So it's natural to view with blocks as a successor to constructors. That's the direction Fan is facing. Only instead of taking a pure approach, and trying to do all construction using with blocks, Fan's still trying to hold onto something that it's all but replaced for many (most?) classes: constructors, whose form is largely unchanged from C++, the ancestor of Java and C#.
Why is this bad? It's confusing. The semantics are modal. It requires developers to master the new while still holding onto the old. It leads to a bifurcation in classes. It makes the language more complex because there are more rules and more productions in the grammar.
So how about Stephen's proposal? Well, although not directly stated by Stephen, it's possible to view (and explain to new developers) that new is a function or operator that returns an uninitialized chunk of memory -- a blank slate, if you will (Is that correct? No, but it's close enough). Now new is a static function for every class, so that it can be accessed directly by static methods (as in the above examples) or by user code by prefixing the class name (e.g. Foo.new).
Because the slate is blank, you can do special things in the with block that follows: in particular, you can assign const fields and choose the super class by assigning super (but only if you need to; you could just as soon set the super fields directly).
After the constructing with block ends, the slate is no longer blank and it's too late to do these things any more -- it's a hard barrier you can't get through.
This is a beautifully clear mental model that enables developers to really wrap their minds around the semantics.
In particular, it should be obvious what the following code constructs do:
class Foo
{
verify
{
// Do post-construction-with-block verification here
}
static This make()
{
return new
{
// Privileged operation on 'blank slate'
}
}
}
Foo.new
{
// Privileged operation on 'blank slate'
}
Foo
{
// NOT privileged, just shortcut for make()
}
Foo(1, 2, 3, 4)
{
// NOT privileged, just shortcut for make()
}
In other words, new followed by a with block is privileged construction on a blank slate, while anything else is just a normal with block with no special privileges. So simple it can be described in 1 sentence with no exceptions.
Meanwhile, the verify block is executed after a constructing with block. Every single time -- without exception. It's also executed for superclasses. You can do all verification for the whole hierarchy in one place, all because in the constructing with block, you can do any necessary superclass initialization as well.
Although that's really all that's necessary, it might be convenient to provide a default make() method (if none is supplied by the user), which just consists of the uninitialized fields in the class (and its supers), in their declaration order. This would nicely handle classes like Rect and Point and many other data classes and eliminate what is sheer boilerplate in other languages.
For all the other stuff, you use static factory methods, which is really what you should have been using all along.
Now I should point out even though I've written a lengthy reply on this topic, and I do think Stephen's proposal makes Fan more consistent with itself (bad pun), construction does consist of a small amount of total code. So this is by no means all that significant an issue. Would it be nice to ditch constructors in favor of construction with blocks? Sure, for the numerous reasons listed above. Is it a showstopper? By no means. Fan has plenty of other nice features to keep me happy.
jodastephenSat 19 Jul 2008
Good summary by JohnDG.
Here is the CallExpr example:
// Fan today
class CallExpr : NameExpr {
new make(Location location, Expr target := null, Str name := null, ExprId id := ExprId.call)
: super(location, id, target, name) {
args = Expr[,]
isDynamic = false
isSafe = false
isCtorChain = false
}
new makeWithMethod(Location location, Expr target, CMethod method, Expr[] args := null)
: this.make(location, target, method.name, ExprId.call) {
this.method = method
if (args != null)
this.args = args
if (method.isCtor)
ctype = method.parent
else
ctype = method.returnType
}
}
// proposal
class CallExpr : NameExpr {
static This make(Location location, Expr target := null, Str name := null, ExprId id := ExprId.call) {
return new {
super = NameExpr(location, id, target, name)
isDynamic = false
isSafe = false
isCtorChain = false
}
}
static This makeWithMethod(Location location, Expr target, CMethod method, Expr[] args := null) {
return make(locaton, target, method.name, ExprId.call) {
// this is a normal-with-block
method = method
args = args
ctype = (method.isCtor ? method.parent : method.returnType)
}
}
verify {
if (args == null) args = Expr[,]
}
}
For me, a lot now hangs on the discussion wrt consts, with-blocks and serialization on other threads. The more I dig into the three kinds of with-blocks, the more this proposal starts to feel a little like it may be tackling the wrong problem.
alexlamslSat 19 Jul 2008
One concern that I would have with the current proposal is the enforcement of initialisation contract by superclass.
Java:
public abstract class A {
protected A(String a) {
// do something with parameter
}
}
public B extends A {
public B() {} // this fails
public B() {
super(""); // this works
}
public B(String a) {
super(a); // this works
}
}
One way to get around the issue would be for new to be internal, however that would prevent us from simple cases of Foo.new. To balance these 2 concerns, I would suggest the ability for classes to define the visibility of their new, default being public
alexlamslSat 19 Jul 2008
Looking at what brian has written up as a side-by-side comparison, I have to admit that super = Line(...) looks worse (read: counter-intuitive) than simpler superclass constructor call.
I think initialisation should be done through instance methods rather than static methods - they should share an equal status alongside with-blocks that would also satisfy the proposed validation mechanism.
class Line {
Point start, end;
virtual verify() {
if (start == null || end == null) throw Err.new;
}
once Void setPoints(Point start, Point end) {
this.start = start;
this.end = end;
}
}
class Stroke : Line {
Color color;
override verify() {
super.verify();
if (color == null) throw Err.new;
}
}
Line.new // Error
Line.new {
setPoints(a, b); // OK
}
Line.new {
start = end = c; // OK
}
Stroke.new // Error
Stroke.new {
color = Color.RED; // Error
}
Stroke.new {
setPoints(d, e);
color = Color.BLUE; // OK
}
Stroke.new {
start = d;
end = e;
color = Color.BLUE; // OK
}
alexlamslSat 19 Jul 2008
Continue from that train of thought - I am thinking about readonly, and how its compile-time checks could be altered slightly to preserve the convenience factor.
class Obj {
virtual Bool valid() {
return true;
}
virtual Void verify() {
if (!valid()) throw Err.new;
}
}
class Line {
readonly Point start, end;
override Bool valid() {
return start != null && end != null;
}
// Proposed: allow the following to compile
once This initPoints(Point start, Point end) {
this.start = start;
this.end = end;
return this;
}
}
Line.new // Not valid()
Line.new {
initPoints(a, b); // OK
}
i := Line.new { // OK
start = c;
end = d;
}
i.initPoints(e, f); // Compile-time error: start is read-only
brianSat 19 Jul 2008
Here's the key point: the purposes and semantics of constructors and constructing with blocks overlap.
That is true, we are providing two mechanisms to initialize fields (which requires a third mechanism for validation). But we're in this boat because I strongly believe that both mechanisms are extremely valuable.
identical looking with blocks meaning two different things in different contexts
This is Stephen's point, and I'm not in agreement. If you believe this then you are saying that the = operator means two different things based upon use in a constructor or elsewhere. It means the same thing: assignment. Just there is a rule in the compiler to prevent reassignment (either with = or with-block) once fully constructed.
Only instead of taking a pure approach, and trying to do all construction using with blocks, Fan's still trying to hold onto something that it's all but replaced for many (most?) classes: constructors
During this process I've convinced myself that no matter what you must have some sort of help initialization methods. You can call them constructors, factory methods, initialization instance methods, etc. I think we are all in agreement on this, we still need the helper methods. The core of this design is what those "helper methods" look like and how easy are they to use.
My epiphany was that constructors remain the best technique for initialization helpers. They look like factories/statics on the outside, but look like instance methods on the inside. Their key advantage is that they work equally well for both clients and subclasses.
This is a beautifully clear mental model that enables developers to really wrap their minds around the semantics.
I am mostly in agreement with this. But I'm not sure the clear mental model works well in practice when it comes to writing real code:
This proposal seems will typically require more code than using constructors as they are today. You will pretty much always have an extra level of nesting via the new-with-block.
Writing the static helpers is more complex because you can't put statements into a new-with-block. So you have to pre-compute everything to local variables, so that you can init with one big with-new-block. Conversely initializing via a true constructor is easier to work with because it is an instance method on the inside with an implcit this.
If you are required to do Foo.new {} in order to set const fields, then you pretty much will always have to use that construct which doesn't seem right. For example in the FWT I define fields which have to be fixed by the SWT constructor as const:
// today
Text { multiLine=true }
// with this proposal I always have to write:
Text.new { multiLine=true }
And of course the key issue - this proposal doesn't work as well with subclasses. I'm still not exactly sure I use my super class's helper methods. The beauty of true constructors is that they naturally work for both clients and subclasses without worrying about the allocation problem.
Needless to say I haven't been convinced (yet) that this proposal actually makes my life as a developer easier than the current design.
jodastephenSat 19 Jul 2008
constructors remain the best technique for initialization helpers. They look like factories/statics on the outside, but look like instance methods on the inside
I think I am moving towards this. The problem is that the factories can't write normal code inside the new-block, hence there will be lots of local assigns and so on.
The proposal above undoubtedly leads to more code for the developer to write. More code is a Bad Thing, unless its for a Good Reason.
The exception is the short factory concept, which I think current Fan might be able to adopt as a short constructor:
new make(startX, startY, endX, endY) {
// each of these has been assigned to the field of the same name
// by the time processing starts here
}
I think this, or a close variation could save on a lot of boilerplate.
In general, I'd still like a single constructor if we can however, with multiple factories around it. At present however, I can't argue enough of a case to say this proposal beats current Fan with a new{} validation block added.
However, I will start a new thread for my latest random idea (idea not proposal).
katoxWed 3 Sep 2008
This topic still stirs my mind. I've been tackling Wicket lately and though it is a sweet framework sometimes it is rather tricky to customize its default behaviour, specially if the developers didn't thought of that possiblity beforehand (fortunately they mostly do).
Going through sources I found out that not only wicket users are encouraged to put markup initialization to constructors but also that the same technique is used in most base classes like Application, Page, PageView, FeedbackPanel.
It is probably the first java application that I have seen with such a heavy constructor code.
It seems that they chose this way to avoid error-prone mandatory calls of some super.init() elsewhere (including overrides). They use quite a lot of methods while constructing objects including virtual methods which can be particulary tricky as the subclass hasn't start initializing yet (thus even directly assigned fields are still null). Because users can't override constructors due to the mandatory super() call there is a bunch of various factory methods to let them put their own classes in parent objects (encouraging them to subclass those factories). Lastly, wicket developers use lots of lazy binding through so-called Models which initialize themselves only after some other object really demands the data.
What do you think of this approach?
brianThu 4 Sep 2008
I prefer the more declarative model via with-blocks. But anything you can do in Java with constructors, can be done in Fan today - so we aren't limiting your design choices. Fan brings some new options to the table via with-blocks and immutability (although we still don't quite have it all right yet).
katoxThu 4 Sep 2008
Fan brings some new options to the table via with-blocks and immutability
Exactly. I'm wondering if we could hammer those to a shape that we wouldn't need complex constructors (like in Java, we'd probably need some).
The biggest issue I have with the Wicket approach is that you write code and your objects are incomplete. So you do OO with crippled objects. You have to know precisely internals of classes which you extend and thus you are mostly tied up to a particular package version. Quite a few ropes to hang on.
But maybe you have to deal with complex constructors if you want to use inheritance... maybe.
brianThu 4 Sep 2008
The fundamental problem is that anything complex needs to let the ctor client perform some setup - which with-blocks are a great way of doing. However after that client setup, the class needs to verify that things were setup correctly - this piece is still missing, and has been the topic of two months of discussion.
Who knew constructors could be so insanely hard to get right.
jodastephen Wed 16 Jul 2008
A new thread from this one.
This proposal builds on the following principles:
Proposal
This proposal uses the concept that factories best represent (1). Validation (4) must happen based on either the with-block data, or the data from the factory. Allocation (2) and assignment (3) are automatic.
Here is the example class, a line:
Factories and the new block
Factory methods are declared using a
This
return type:The
new {} block
only needs to define those fields that have not got a default value. However, defining anew {} block
without a required field would be a compile error.The
new {} block
can use thenew.
prefix to refer to the fields of the new instance as opposed to the local scope. This is similar in concept to thethis.
prefix.Factory methods are basically ordinary methods. They can call other factory methods, and return cached instances, proxies or subclasses.
The
new {} block
can also be used directly on the public API, aka awith block
:The new validator
The
new validator
(4) is autogenerated by default:However, it may be specified:
Allocation (2) and Assignment (3) take place automatically based on the named parameters passed to the
new {} block
. These stages occur however the class is instantiated (factory/with-block/deserialization). The code within thenew validator
can assume that the fields have been assigned.Declaring the
new validator
serves two purposes. Firstly, it provides for validation (4). State can be checked - and this occurs after both normal construction, with-block construction and deserialization.Secondly, the
new validator
can have access control added. Adding access control may affect deserialization (ie. it might prevent the class from being declared serializable).Simple assignment factories
A final possible factory style (and this is an optional part of the whole proposal) is syntax sugar for declaring simple factories that just assign data:
Here, we are defining a factory taking the four listed fields in the order specified. Note that the types of the fields do not need to be declared as they are picked up from the field definitions. All fields without a default value must be listed. Any fields at the end of the list that have a default value would be optional.
Superclasses
Superclasses are handled as proposed by JohnDG, by assigning to super:
The
new {} block
call must contain either asuper=
or initialize all the fields of the superclass. If the subclass needs to initialize the superclass using any more complicated mechanism, then the subclass must write factory methods.Abstract superclasses may have factory methods and use the
return new {}
syntax. Such a factory can only be called from a subclassnew {} block
.The
new validator
of the subclass only needs to validate its own state. Thenew validator
of the superclass will already have been called:Summary
Essentially, this proposal only allows objects to be created by calling the named parameters with-block style
new {} block
. Factories and thenew validator
then perform the pre and post processing.Finally, apologies for the long post - hopefully, this is clear enough to discuss...
tompalmer Wed 16 Jul 2008
I sort of like it, but I want my
Point(x, y)
, and I'm really not sure the validator is needed. Also, how to make the defaultnew
nonpublic?I also have yet another variation on Brian's latest recommendation on the other thread, but I'll defer that for now.
jodastephen Wed 16 Jul 2008
You can have
Point(x,y)
- simple declare a simple assignment factory. The calling style, ie.Type(args)
is a separate issue to the underlying factories/constructors.The easiest way to think of the
new validator
is as the single constructor - you just don't have to declare the arguments (as they are passed and assigned as named parameters automatically).brian Wed 16 Jul 2008
Stephen - thanks for the detailed proposal. I'm still trying to digest it and figure out how it differs from the single constructor proposal.
I think the big differences are:
What I like most is that you've flipped the initializer method and validator method, so the natural "new method" does post-construction validation. I'm not sure how intuitive that is, but it a good direction to think in because it avoids all the issues we've had about what to call/how to annotate the post construction method(s).
Although these are the problems I see:
new
is called after constructiontompalmer Wed 16 Jul 2008
So the
new
validator (and the fact that it's run after rather than before the with block) is really the novel feature of this proposal. And the style ofsuper
call. I think everything else is mostly doable with current other proposals.I'm afraid that having some setters only for construction could be a bit odd. Maybe better to pass in other config objects if we want named parameters.
Do static factories really not play well with const initialization? If that's true (and it makes sense at the moment since factories could supply previously created or unrelated objects anyway), then I'm actually starting to favor the idea of not changing Fan's current model much at all. I just have a couple of alternatives for slight modifications that I think we should consider.
My alternatives for slight changes to current Fan (1.0.28) constructors:
Point(x, y)
callPoint.make(x, y)
(and maybe not supportfromStr
with this syntax).new make()
looks weird, just let the default constructor be callednew
instead ofmake
, and as a special case, don't require the redundant word (so justnew() {/*...*/}
). AgainPoint(x, y)
callsPoint.new(x, y)
.And any other constructors can be called "supplemental constructors" (constructors just like today's fan, such as
new fromStr(Str str) {/*...*/}
. These are also called just like todayBlah.fromStr("something")
). Again, the point is to allow some custom logic while playing nice with const and with blocks.The question raised by Stephen, I think, is whether it's better to have multiple constructor methods or to allow custom handling in one constructor after setters have been run.
I currently vote for just keep Fan as is today with one of the two slight modifications recommended above.
tompalmer Wed 16 Jul 2008
I posted before seeing Brian's reply, by the way. Apologies on any resulting confusion.
JohnDG Wed 16 Jul 2008
I really like this proposal for the following reasons:
super
is weird -- however,super
is already treated as a field/slot, even in Fan, because you can access methods using the notationsuper.
.super
can be viewed as just another slot, one that happens to point to the superclass, with "automagic" delegation to this slot for non-overridden methods. With this view, it's natural to want to assignsuper
.Now the one thing I don't like in this proposal is the conflicting uses of
new
and the non-standardwith
block. In one instance,new
seems to create a new instance and the subsequentwith
block in the factory method initializes the new instance. However, in the other instance,new
is used to perform validation after allocation and initialization. This is confusing.In my view,
new
should refer only to allocation, so the syntax of the factory methods would remain unchanged, but theverify
method would be changed to something likeverify
. e.g.:Even if this proposal isn't accepted, I agree with Brian that it's a productive way to think about the problems involved.
jodastephen Wed 16 Jul 2008
Brian, I think part of the problem is that I'm still a little unclear as to the exact details of how your proposal stands after the other long thread.
I also think that your summary of this proposal isn't quite right. I see the
new validator
as effectively being the "constructor". It implicitly takes all the fields as arguments for itself and its superclass using a named parameter style. They are passed directly from thenew-block
. As such, the only code you write in this "constructor" is the validation code.The bytecode generated would be a real constructor with one parameter for each field in the superclass, followed by each field in the subclass. This would not be visible to Fan code (except maybe in reflection).
Effectively, the only way to instantiate an instance is using a
new-block
. A construction with-block (type (2)/(3) from the other thread) is simply syntax sugar for a call to thenew-block
. Although thenew-block
appears to be initialising the state, it is actually just passing named parameters to the "constructor".On your points:
This
return type is a convenience which I was using to identify constructors. I'm not fussed about that detail.new.
inside thenew-block
is needed to override the local scope in the same way thatthis.
overrides the local scope to refer to the enclosing instance.super
proposal actually opens up a more wild idea. (*What if Fan didn't have direct inheritance at all? What I mean is implementation using composition rather than inheritance. The calling code would see no difference, but within the class itself there would be asuper
field that holds a reference to an instance of the superclass - which could actually be a subclass of the superclass!*)new validator
is effectively the "constructor".new {}
. It still allows assignment to all fields including const, it just doesn't have any validation.By the way, the use of
Point(x,y)
falling back to alternatives including make and new is something I think I can agree with.Comparing
Comparing the proposals as best I can, I don't see how just having a single constructor solves the problem of validating with-blocks that are run immediately post-construction. If those with-blocks have special powers (ie. they can set
const
fields) then they must be vaildated. If they can only access the public API, then they are nothing special, and are perfectly safe.Another factor to bear in mind is that IIUC then the JVM Memory Model requires that fields like
const
that are shared between threads must be declaredfinal
in bytecode. This means that with-block data must be set via a genuine JVM constructor.So, the key advantage of this proposal is that however an object is created it is properly validated wrt with-block or factory:
All three eventually call the
new validator
"constructor".The question is whether this is enough to justify the extra complexity. The problem is that as long as you require with-blocks to set const fields, you need the complexity.
jodastephen Wed 16 Jul 2008
Actually, having read JohnDG's
verify
idea, I quite like that. Same concept, better name.tompalmer Wed 16 Jul 2008
Brian, how tough would it be to implement a post-with/new block feature? I'm not convinced it would really be used in practice, but if it were added experimentally (if not too much effort), then it would be a chance for people to see if they would really use it or not.
If it did exist, and if used, I think it would be used for more than validation, so I'm starting to think that the syntax or name should imply that.
brian Wed 16 Jul 2008
Tonight I'll rewrite up my proposal into a single coherent post.
I'm having trouble wrapping my head around this, because I'm trying to map it how I actually emit a Java or IL constructor, and use helper methods. Some of it is terminology. I think what you are saying is:
My proposal is that 1 and 2 run in a normal Java style constructor and that 4 happens in some other method(s) annotated with the @onNew facet. I think both ways let you do the same things, but I think my proposal is a little more in-line with how Java/C# work today.
Actually I'm a fan of prototype based inheritance (like Self), but with Fan I was trying to leverage the JVM/CLR OO model as much as possible.
Actually neither proposal solves this because you can't ever really have generic with-blocks run after the constructor or during deserialization on final fields. In practice I don't think it will ever be a problem due to how objects move between threads. But if it was a problem we'd have to use volatile or synchronized to solve it.
Well the whole constructor rework is a huge amount of work, so I want to get it right before I start. The post validation step itself is a rather smaller piece of the whole puzzle.
If I were to write your code using my proposal it would look something like this:
JohnDG Wed 16 Jul 2008
That's true, but it looks out of place in Fan. Not as clean as Joda's proposal, nor as clean as the rest of Fan, generally.
You know you brought all this on yourself by introducing
with
blocks, don't you? :-)jodastephen Wed 16 Jul 2008
I am proposing that (1) is always emitted as a JVM static method, and (3)/(4) are a JVM constructor:
As shown, the JVM constructor simply takes each field in order of declaration (superclass fields would be listed before subclass fields). This means that the constructor is genuine, and can set
final
fields correctly.The
new-block
is not exactly the same language feature as awith-block
. It has different abilities (setting const and followed by verification). You can only save the "generic with-block" concept if you lose the ability to set const fields (because then you don't need verification either).You could think of it as "if the with-block is preceeded by the new keyword then it is a new-block and is used to initialise an object rather than change its state". Of course sometimes the
new
keyword is invisible:tompalmer Thu 17 Jul 2008
I still think "change nothing" (except for call syntax) should still be an option on the table.
I plan to refrain from mentioning it again, though, as long as my opinion doesn't change. Hopefully I have the willpower to avoid posting too much.
brian Thu 17 Jul 2008
This is very interesting, although I'm not sure how practical it would be. It would likely create some extremely large constructor signatures and very deep call stacks. Plus it doesn't help much with deserialization - I guess I could buffer everything up, then create one big ctor call, but then you couldn't deserialize off the stream as well.
I think I'm coming back around full circle on this - I'll create another post.
brian Fri 18 Jul 2008
I've gone back and reread this proposal carefully. As far saying that a class has a single new operator which allocates the instance - I really like that (and has been championed by Tom and Andy in the 2nd thread). The hard part is coming up with a design for initialization which chains well through subclasses and is a little more than "just convention".
This proposal uses this:
Current design uses this:
The current design uses less syntax and seems more obvious to a C++/C# programmer (and hopefully a Java programmer). So I'm not understanding how this proposal is more elegant that what we currently have. Am I just missing something? Please educate me :^)
JohnDG Fri 18 Jul 2008
Java supports infinite constructors and has no notion of
with
blocks. Fan, in its current form, supports one constructor and constructingwith
blocks.Here's the key point: the purposes and semantics of constructors and constructing
with
blocks overlap.This is why I've referred to the design as
muddled
, because as Stephen has pointed out, construction in Fan is not an atomic operation where everything takes place in a single privileged area (as it is with Java). Rather, it's potentially a three phase system, a back and forth between user and class code, where the implications of some constructs are neither obvious nor intuitive because of modal semantics (identical lookingwith
blocks meaning two different things in different contexts).Now much Fan source code does not even use constructors: it uses
with
blocks exclusively. So it's natural to viewwith
blocks as a successor to constructors. That's the direction Fan is facing. Only instead of taking a pure approach, and trying to do all construction usingwith
blocks, Fan's still trying to hold onto something that it's all but replaced for many (most?) classes: constructors, whose form is largely unchanged from C++, the ancestor of Java and C#.Why is this bad? It's confusing. The semantics are modal. It requires developers to master the new while still holding onto the old. It leads to a bifurcation in classes. It makes the language more complex because there are more rules and more productions in the grammar.
So how about Stephen's proposal? Well, although not directly stated by Stephen, it's possible to view (and explain to new developers) that
new
is a function or operator that returns an uninitialized chunk of memory -- a blank slate, if you will (Is that correct? No, but it's close enough). Nownew
is a static function for every class, so that it can be accessed directly by static methods (as in the above examples) or by user code by prefixing the class name (e.g.Foo.new
).Because the slate is blank, you can do special things in the
with
block that follows: in particular, you can assignconst
fields and choose the super class by assigningsuper
(but only if you need to; you could just as soon set thesuper
fields directly).After the constructing
with
block ends, the slate is no longer blank and it's too late to do these things any more -- it's a hard barrier you can't get through.This is a beautifully clear mental model that enables developers to really wrap their minds around the semantics.
In particular, it should be obvious what the following code constructs do:
In other words,
new
followed by awith
block is privileged construction on a blank slate, while anything else is just a normalwith
block with no special privileges. So simple it can be described in 1 sentence with no exceptions.Meanwhile, the
verify
block is executed after a constructingwith
block. Every single time -- without exception. It's also executed for superclasses. You can do all verification for the whole hierarchy in one place, all because in the constructingwith
block, you can do any necessary superclass initialization as well.Although that's really all that's necessary, it might be convenient to provide a default
make()
method (if none is supplied by the user), which just consists of the uninitialized fields in the class (and its supers), in their declaration order. This would nicely handle classes likeRect
andPoint
and many other data classes and eliminate what is sheer boilerplate in other languages.For all the other stuff, you use static factory methods, which is really what you should have been using all along.
Now I should point out even though I've written a lengthy reply on this topic, and I do think Stephen's proposal makes Fan more consistent
with
itself (bad pun), construction does consist of a small amount of total code. So this is by no means all that significant an issue. Would it be nice to ditch constructors in favor of constructionwith
blocks? Sure, for the numerous reasons listed above. Is it a showstopper? By no means. Fan has plenty of other nice features to keep me happy.jodastephen Sat 19 Jul 2008
Good summary by JohnDG.
Here is the CallExpr example:
For me, a lot now hangs on the discussion wrt consts, with-blocks and serialization on other threads. The more I dig into the three kinds of with-blocks, the more this proposal starts to feel a little like it may be tackling the wrong problem.
alexlamsl Sat 19 Jul 2008
One concern that I would have with the current proposal is the enforcement of initialisation contract by superclass.
Java:
One way to get around the issue would be for
new
to beinternal
, however that would prevent us from simple cases ofFoo.new
. To balance these 2 concerns, I would suggest the ability for classes to define the visibility of theirnew
, default beingpublic
alexlamsl Sat 19 Jul 2008
Looking at what
brian
has written up as a side-by-side comparison, I have to admit thatsuper = Line(...)
looks worse (read: counter-intuitive) than simpler superclass constructor call.I think initialisation should be done through instance methods rather than static methods - they should share an equal status alongside with-blocks that would also satisfy the proposed validation mechanism.
alexlamsl Sat 19 Jul 2008
Continue from that train of thought - I am thinking about
readonly
, and how its compile-time checks could be altered slightly to preserve the convenience factor.brian Sat 19 Jul 2008
That is true, we are providing two mechanisms to initialize fields (which requires a third mechanism for validation). But we're in this boat because I strongly believe that both mechanisms are extremely valuable.
This is Stephen's point, and I'm not in agreement. If you believe this then you are saying that the
=
operator means two different things based upon use in a constructor or elsewhere. It means the same thing: assignment. Just there is a rule in the compiler to prevent reassignment (either with = or with-block) once fully constructed.During this process I've convinced myself that no matter what you must have some sort of help initialization methods. You can call them constructors, factory methods, initialization instance methods, etc. I think we are all in agreement on this, we still need the helper methods. The core of this design is what those "helper methods" look like and how easy are they to use.
My epiphany was that constructors remain the best technique for initialization helpers. They look like factories/statics on the outside, but look like instance methods on the inside. Their key advantage is that they work equally well for both clients and subclasses.
I am mostly in agreement with this. But I'm not sure the clear mental model works well in practice when it comes to writing real code:
This proposal seems will typically require more code than using constructors as they are today. You will pretty much always have an extra level of nesting via the new-with-block.
Writing the static helpers is more complex because you can't put statements into a new-with-block. So you have to pre-compute everything to local variables, so that you can init with one big with-new-block. Conversely initializing via a true constructor is easier to work with because it is an instance method on the inside with an implcit this.
If you are required to do
Foo.new {}
in order to set const fields, then you pretty much will always have to use that construct which doesn't seem right. For example in the FWT I define fields which have to be fixed by the SWT constructor as const:And of course the key issue - this proposal doesn't work as well with subclasses. I'm still not exactly sure I use my super class's helper methods. The beauty of true constructors is that they naturally work for both clients and subclasses without worrying about the allocation problem.
Needless to say I haven't been convinced (yet) that this proposal actually makes my life as a developer easier than the current design.
jodastephen Sat 19 Jul 2008
I think I am moving towards this. The problem is that the factories can't write normal code inside the new-block, hence there will be lots of local assigns and so on.
The proposal above undoubtedly leads to more code for the developer to write. More code is a Bad Thing, unless its for a Good Reason.
The exception is the short factory concept, which I think current Fan might be able to adopt as a short constructor:
I think this, or a close variation could save on a lot of boilerplate.
In general, I'd still like a single constructor if we can however, with multiple factories around it. At present however, I can't argue enough of a case to say this proposal beats current Fan with a new{} validation block added.
However, I will start a new thread for my latest random idea (idea not proposal).
katox Wed 3 Sep 2008
This topic still stirs my mind. I've been tackling Wicket lately and though it is a sweet framework sometimes it is rather tricky to customize its default behaviour, specially if the developers didn't thought of that possiblity beforehand (fortunately they mostly do).
Going through sources I found out that not only wicket users are encouraged to put markup initialization to constructors but also that the same technique is used in most base classes like Application, Page, PageView, FeedbackPanel.
It is probably the first java application that I have seen with such a heavy constructor code.
It seems that they chose this way to avoid error-prone mandatory calls of some
super.init()
elsewhere (including overrides). They use quite a lot of methods while constructing objects including virtual methods which can be particulary tricky as the subclass hasn't start initializing yet (thus even directly assigned fields are still null). Because users can't override constructors due to the mandatorysuper()
call there is a bunch of various factory methods to let them put their own classes in parent objects (encouraging them to subclass those factories). Lastly, wicket developers use lots of lazy binding through so-called Models which initialize themselves only after some other object really demands the data.What do you think of this approach?
brian Thu 4 Sep 2008
I prefer the more declarative model via with-blocks. But anything you can do in Java with constructors, can be done in Fan today - so we aren't limiting your design choices. Fan brings some new options to the table via with-blocks and immutability (although we still don't quite have it all right yet).
katox Thu 4 Sep 2008
Exactly. I'm wondering if we could hammer those to a shape that we wouldn't need complex constructors (like in Java, we'd probably need some).
The biggest issue I have with the Wicket approach is that you write code and your objects are incomplete. So you do OO with crippled objects. You have to know precisely internals of classes which you extend and thus you are mostly tied up to a particular package version. Quite a few ropes to hang on.
But maybe you have to deal with complex constructors if you want to use inheritance... maybe.
brian Thu 4 Sep 2008
The fundamental problem is that anything complex needs to let the ctor client perform some setup - which with-blocks are a great way of doing. However after that client setup, the class needs to verify that things were setup correctly - this piece is still missing, and has been the topic of two months of discussion.
Who knew constructors could be so insanely hard to get right.