When writing const data structure classes I often find myself having to write the same boilerplate over and over:
new makeFunc(|This| f) {
f(this);
}
I propose that when a class has no constructors declared, and has any non-nullable or const fields, that the compiler inject the above constructor in lieu of the standard no-arg, no-op constructor.
That way this class:
const class Point {
const Int x
const Int y
}
Would be desugared by the compiler to act like this:
const class Point {
const Int x
const Int y
new makeFunc(|This| f) {
f(this)
}
}
brianThu 16 Feb 2012
I think the problem is that only makes sense for const classes. In general if you have an auto-generated make method, then you can use with to achieve the same thing:
class Foo { Int x }
// get this for free
Foo { x = 3 }
// because it compiles into
Foo.make.with |it| { it.x = 3 }
The problem is that doesn't work for const classes. But at the same time it doesn't seem like a good idea to be having different rules for what we auto-generate.
This is whole topic also is related to #1370 and also discussions along the lines of how Scala (and now Kotlin) handle constructor/fields too.
qualidafialThu 16 Feb 2012
This is whole topic also is related to #1370 and also discussions along the lines of how Scala (and now Kotlin) handle constructor/fields too.
The point of this ticket was to break out my suggestion to auto-generate the |This| constructor from the rest of the discussion on #1370.
Depending on the outcome of that discussion, this proposal might need to use the default constructor name of make instead of makeFunc.
I think the problem is that only makes sense for const classes. In general if you have an auto-generated make method, then you can use with to achieve the same thing:
So let's look at what we'd have to do to use with to initialize const objects.
First, our auto-generated constructor needs some sensible defaults for each field:
const class Address
{
const Str addr1
const Str? addr2
const Str city
const Str state
const STr zip
new make() {
// uh..
}
}
Next, we need to override with to implement a copy-and-modify strategy:
addr := Address {
addr1 = "123 Main St"
city = "Beverly Hills"
state = "CA"
zip = "90210"
}
However along the way we had to create a throwaway const object with likely to be bogus default data.
The bottom line is that the signature of the with method (where the function accepts a This but returns Void) is just not compatible with immutable objects.
The problem is that doesn't work for const classes. But at the same time it doesn't seem like a good idea to be having different rules for what we auto-generate.
So how about a really crazy proposal: change all default constructors to the following:
new make(|This|? f)
{
f?.call(this)
}
This approach carries some side effects:
Many/most constructor declarations can be omitted
A bunch of compiler errors about uninitialized non-null or const fields would be silenced, and replaced by runtime errors whenever a client caller neglects to initialize any fields.
Authors of virtual classes have to be extra careful about whether to explicitly declare a constructor, and think about whether to pass the |This| f argument to the super constructor, or to call super.make(null) and invoke the function in the subclass.
So what does everybody think of these trade-offs? Are there any other issues that haven't been mentioned yet?
brianThu 16 Feb 2012
So how about a really crazy proposal: change all default constructors to the following: new make(|This|? f) { f?.call(this) }
I think this is the right design and have proposed it along with many other changes. But we can use this specific topic to discuss this aspect.
qualidafialThu 16 Feb 2012
Addendum:
If any fields const or non-nullable fields have no default value, then the signature would be:
new make(|This| f) // f not nullable
{
f(this)
}
Otherwise if all fields are nullable or have default values, the signature would be
new make(|This|? f)
{
f?.call(this)
}
brianThu 23 Feb 2012
I'm still not quite about this, so I'm going to hold off adding it immediately. I'm debating if saving that one line boilerplate really outweighs the errors you would get if you didn't realize you had non-initialized non-nullable fields. Plus this is really one one small part of an overall problem with boilerplate "struct like" classes.
qualidafial Thu 16 Feb 2012
When writing
const
data structure classes I often find myself having to write the same boilerplate over and over:I propose that when a class has no constructors declared, and has any non-nullable or const fields, that the compiler inject the above constructor in lieu of the standard no-arg, no-op constructor.
That way this class:
Would be desugared by the compiler to act like this:
brian Thu 16 Feb 2012
I think the problem is that only makes sense for const classes. In general if you have an auto-generated
make
method, then you can usewith
to achieve the same thing:The problem is that doesn't work for const classes. But at the same time it doesn't seem like a good idea to be having different rules for what we auto-generate.
This is whole topic also is related to #1370 and also discussions along the lines of how Scala (and now Kotlin) handle constructor/fields too.
qualidafial Thu 16 Feb 2012
The point of this ticket was to break out my suggestion to auto-generate the
|This|
constructor from the rest of the discussion on #1370.Depending on the outcome of that discussion, this proposal might need to use the default constructor name of
make
instead ofmakeFunc
.So let's look at what we'd have to do to use
with
to initializeconst
objects.First, our auto-generated constructor needs some sensible defaults for each field:
Next, we need to override
with
to implement a copy-and-modify strategy:Now our expression runs as expected:
However along the way we had to create a throwaway const object with likely to be bogus default data.
The bottom line is that the signature of the with method (where the function accepts a
This
but returnsVoid
) is just not compatible with immutable objects.So how about a really crazy proposal: change all default constructors to the following:
This approach carries some side effects:
|This| f
argument to the super constructor, or to call super.make(null) and invoke the function in the subclass.So what does everybody think of these trade-offs? Are there any other issues that haven't been mentioned yet?
brian Thu 16 Feb 2012
I think this is the right design and have proposed it along with many other changes. But we can use this specific topic to discuss this aspect.
qualidafial Thu 16 Feb 2012
Addendum:
If any fields const or non-nullable fields have no default value, then the signature would be:
Otherwise if all fields are nullable or have default values, the signature would be
brian Thu 23 Feb 2012
I'm still not quite about this, so I'm going to hold off adding it immediately. I'm debating if saving that one line boilerplate really outweighs the errors you would get if you didn't realize you had non-initialized non-nullable fields. Plus this is really one one small part of an overall problem with boilerplate "struct like" classes.