Auto contructor with all variables?


#1

If I have this class

class Person {
  var name = String()
  var lastName = String()
}

I can’t make val p = Person(name = "Jonh", lastName = "Smith"), I must use apply (problems with variables with same name) or define the class with contructor

class Person(
  var name: String = String()
  var lastName: String = String())

If I need customs get/set, I must use init { } constructor for these properties… A lot of boilerplate

Maybe and auto contructor with all public values as Groovy maybe useful


#2

You can use with to set the properties which isn’t much longer:

val p = Person()
with (p) { name = "John"; lastName = "Smith" }

#3

What is exactly wrong with defining a class with a constructor? This is exactly how this problem is intended to be solved.

Also note that instead of = String() you can simply write = "".


#4

Going off topic…

Regarding initializing the vars with “”, isn’t that in some ways worse than making the vars nullable? In my view this is the problem with null prevention, it shifts the problem over to a messy “empty object” problem where you don’t really know if state has a proper value or not, instead you’re faced with having to check a multitude of empty objects in their varying forms. Null is much easier to deal with in this respect. I suppose am not convinced the “$1B mistake” is actually a mistake.


#5

Well, I don’t think that the nullability provisions which Kotlin and several other modern languages have are intended to prevent you from using ‘null’ altogether. They’re just trying to make you think whether you really need a nullable type or not and, if you do, provide convenient facilities to prevent NREs.

Since I started using Kotlin late last year after 15 years of using C#, I’ve been surprised at how little I really do need nullable types.

The ‘empty object’ problem only really arises with mutable properties where ideally you’d only want them to be ‘null’ to start with. If you initialize them using a constructor, then you can generally come up with a sensible initial value and, if for some reason you can’t or don’t want to use a constructor, you can declare the property to be ‘lateinit’ to avoid having to initialize it with an empty object in the meantime.

Of course, there will no doubt be awkward cases where you’re forced to use a nullable type but, personally, I much prefer Kotlin’s overall approach to that of Java/C# where all reference types are nullable and where you’re continually checking for null in a program of any size.


#6

Right. Both Java and Kotlin’s approaches have merit. But I’ll argue, in this case, Kotlin is the greater of two evils.

Sure. Java’s “always nullable” approach forces null checks, which sucks. On the other hand, a null check is universal and is easy to manage. Of course the runtime danger is an NPE, but these tend to be relatively easy to diagnose and fix and rarely involve data corruption (the worst of dangers).

Kotlin’s “non-null by default” approach prevents null checks, which is nice. But consider the example in the original post. The author of the class assigns an empty string to initialize the variable, otherwise the compiler complains because it can’t be null. Essentially, the programmer hasn’t thought much about whether or not the value should be nullable, he has simply substituted an empty value for null because THE COMPILER TOLD HIM TO. Can you see how this is a dangerously common behavior precipitated by the language’s choice to avoid null values?

User’s of the variable are under a false sense of security. The variable can’t possibly be null, the compiler made sure of that, but now we have a much worse problem:

  1. We no longer have a universal way to check (or assign) uninitialized state.

  2. Above all, we likely won’t check for “emptiness” because the whole idea behind non-nullable is to avoid null checking and NPEs. Therefore, we let potentially uninitialized state slip through and increase the risk for data corruption since code downstream will not throw NPEs, which otherwise signal to us we are working with bad data.

Don’t misunderstand. I fully respect the benefit of non-nullable state, but it comes with great responsibility that I argue is not fully respected or easily understood. Alas, the danger is that many consider non-nullable a magic bullet. “Yay, we don’t have to check for null now because the compiler made it so.” Yeeaah, no. You could argue programmers will think critically and decide whether or not to override the default non-nullable behavior, but the language encourages the use of empty values with ERROR: property must be initialized. Again the example in this post speaks volumes.


#7

I’d certainly agree with you that there’s no point replacing generic null checks with class-specific checks for uninitialized or partially initialized state and this therefore places a responsibility on the programmer to ensure that public non-nullable properties are always initialized to sensible (or at any rate harmless) values.

Of course, what would be sensible values for a given class (such as the OP’s Person class) will depend on how it’s going to be used within the context of the program or, if you’re writing a library, the needs of your users.

But I actually see this as a strength of Kotlin’s approach to nullability rather than a weakness. If you really want a property to be non-nullable, then you can’t afford to be sloppy about initialization - you have to come up a value which doesn’t need to be checked later. If this isn’t feasible, you should use a nullable property instead and check it for null in the usual way.


#8

It’s not so much about what a “sensible non-null value” would be for a given variable. It’s more about what happens in users’ code involving a non-nullable variable. Now that the variable is not-nullable users don’t need to check for null… but the real question is, why were they checking in the first place? Consider this example using the OP’s Person:

Nullable Person:

var person = Person()
if(person.name != null) {
  saveName(person.name)
}

Non-nullable Person:

var person = Person()
saveName(person.name)`

The former code conditionally calls doSomething() based on whether or not Name is null; this is the conventional value for an unassigned reference. The latter code, because person is non-nullable, assumes Name must be assigned and eventually corrupts data with an empty value.


#9

Yes, but if the non-nullable property has been initialized to a value which will lead to data corruption, then clearly the programmer hasn’t ensured that it has been set to a sensible or harmless value.

Moreover, even if you make the property nullable, there’s still no implicit guaranteee that a non-null value will be sensible if, say, you’re creating Person objects uncritically from either user input or from some other unchecked data source. It’s still the programmer’s responsibility to ensure that.


#10

Indeed it is…

Regarding:

Yes, but if the non-nullable property has been initialized to a value which will lead to data corruption, then clearly the programmer hasn’t ensured that it has been set to a sensible or harmless value.

Again, it’s not about whether or not the value is “sensible”. My point is about the value finding its way into a data source unintentionally or otherwise altering the course of logic unintentionally, resulting in corruption or nasty, hard to fix bugs. Bottom line: if a programmer fails to check for, in this OP case, the empty string value, sensible or not, that value is not intended to persist, thus corruption.

This brings us full-circle. With non-null variables where a programmer used to check for null as a means to detect empty/unassigned state, he must now check for type-specific empty state, which in my view is less intuitive, messier, more likely to be missed, and above all riskier wrt corruption because no more NPEs to prevent it.

To sum up. I’m not saying non-nullable is wrong or evil. I’m saying it has it’s place and it should be used judiciously, thus I’m also saying it should probably not be the default. Used incorrectly, which again is easy to do with Kotlin’s default behavior, non-null variables exchange easy to diagnose NPEs and null checks for hard to diagnose errors and potentially messier empty state management.


#11

When you need create get and sets, for example.


#12

So what’s the problem with gets and sets?

class Person(name: String) {
    var name: String = name
        set(value: String) { ... }
}

I don’t see much boilerplate here. The only repeated code is the parameter name and type, and the benefit of specifying them explicitly is making sure that the constructor signature is visible and doesn’t change unexpectedly when you add or rearrange the properties of the class.


#13

I have class withs 15-20 fields, so, It is a lot of boilerplate in some cases, and the constructor is too long.


#14

So if someone wants to call the constructor of your class, do you really think it’s a good idea to force them to read the entire body of the class, trying to figure out what properties are there and what their types are?

If you think that your constructor is too long, then it’s too long, and you should solve the problem by changing the design of your code, and not by hiding the complexity behind compiler-generated code.


#15

My class has a lot of field, but I don’t use all all time. I would like call constuctor with parameters as

class Config() {
    var limit: Int  = 10
    var offset: int = 0  
}

fun main() {
  val p = Config(offset = 20)

```

A lot of field are optional, the developer only must set the field that he want and use the defaults value for the rest.

Maybe, instead of "constructor", the compiler can change the 
```
val p = Config(offset = 20)
```
to 

```
val p = Config()
p.offset = 20
```

#16

You can already do this:

class Config(var limit: Int = 10, var offset: Int = 0)

Then, you can call this just as you proposed:

val p = Config(offset = 20)

You can provide default values in constructors. I don’t see the problem. You need custom getters/setters for your properties? You can still do that with combining this and what Yole said.


#17

How do you know that the empty String is bad data? It could be just fine in his use case.


#18

This is precisely the problem. What if it is bad data? With a nullable reference the unassigned value is null and there is convention for checking that. With a non-nullable reference as in the OP example the unassigned value, if applicable, could be the empty string (or some other value). In that case your code must check for the empty value, which is less intuitive and, therefore, less likely to be checked. Thus, for most cases like this it’s better to have a nullable reference.

This is a common problem, I’ve seen it with nullable annotations in Java. Many programmers are under the impression everything should be non-null and create empty/errant/transitional state to avoid null. In my experience it complicates code and introduces errors that are much harder to diagnose than NPEs which tend to be closer to the source of the problem.


#19

I don’t think you understood what I said. I’m saying that you could be getting all worked up about it when an empty string is perfectly okay data. You’re assuming empty strings are a problem, which they may not be, in this case. And you’re all worried that he’s not thinking about doing proper checks, but he specifically remarked on wanting to use custom getters and setters. If I’m wrong, then I think this case would be better served by lateinit than null, since that’ll thrown an exception as soon as it’s referenced if it hasn’t had a decent value assigned yet. You could set up the same situation yourself with a custom getter or delegated property, but why do that when something baked into the language works so well?

Also, you pretty much said all the same things again, which isn’t really going to help.

Beyond that, in a more direct response to what you said, I can see where you’re coming from, but I largely disagree. Takipi did a study of errors raised in PRODUCTION environments, and they still found NPEs to be the most prevalently thrown exceptions. Sure, there are more automatic and natural ways of checking for null, but people still forget to check them, causing problems even in released versions. Granted, Kotlin forces you to deal with null when it’s allowed, but that doesn’t mean that a well thought-out “Null Object” isn’t a better solution. Heck, as mentioned before, lateinit is a better solution than nullability most of the time.


#20

RE:

I’m saying that you could be getting all worked up about it when an empty string is perfectly okay data. You’re assuming empty strings are a problem,

I understand that. And I’m saying that is the problem. Your words… “could be”… “when” (if)… I’m very much not assuming anything, but a lot of users will and do. I’ve suffered through it first-hand. Empty values avoid NPEs. That can be good in some situations and very bad in others.

RE:

Takipi did a study of errors raised in PRODUCTION environments, and they still found NPEs to be the most prevalently thrown exceptions.

This is not a bad thing. However, disguising an NPE as an InvocationException or what have you further downstream is a bad thing. And corrupting your customer’s data is unforgivable.

Listen, I’m not saying non-nullable enforcement is a terrible idea; it’s obviously an effective tool. But in my view after seeing it abused (albeit in Java with annotations) flipping the switch to non-nullable as default is bit overplayed. Feel free to disagree.