#1696 Proposal: defaulting variables to defVal when not affected

MoOm Sat 12 Nov 2011

The fact that non-nullable reference variables don't have a default value is a small annoyance that I often encounter when I write code in Fantom. For example, here is what a Person class looks like:

class Person {
  Str firstName := ""
  Str lastName := ""
  Person[] children := Person[,]
}

whereas I'd just like to write this:

class Person {
  Str firstName
  Str lastName
  Person[] children
}

And when there is no short literal and I'm too lazy, I just declare the field as nullable which is really not what I wanted in the first place.

So I'd really want to see the following change: if a type has a "defVal" slot, then not affected non-nullable variables would default to this value. This would be coherent with value-type variables.

brian Sun 13 Nov 2011

Side note, remember with lists and maps you can shorten that code to:

Person[] children := [,]

My initial thought is that while your idea is cool, it also seems to have a bit of magic that would weaken the readability of the code. Sometimes verbose is actually good :-)

I think the key issue here is that you want a non-nullable field because these fields shouldn't be set to null. But at the same time you aren't going to initialize them in the constructor? That seems like a fairly odd use case, maybe you can explain more? In these sorts of situations I actually think null is a better indicator of "not set yet" than something like empty string, but definitely would like to understand this issue more.

jodastephen Sun 13 Nov 2011

Writing the presentation for Devoxx, I can relate to this. It just looks weird in examples having to specify the "", because I'm not adding a constructor (because Fantom constructors are too verbose). Simply defaulting to an already specified default value would be perfectly simple and obvious in most cases.

DanielFath Sun 13 Nov 2011

+1 to it, if I wrote

class Person {
   Int age  
   Str name
}

I would expect it to default to a defVal for that type.

However I do see this as a potential problem when dealing with persistence.

class Person {
   Int id
   Str name
   Int age
}

id shouldn't be null for persistence reasons and I don't want to set it. OTOH persistence engine will probably overrride it anyway so it's (perhaps) OK.

Middle ground would be something like

@Auto class Person {
   Int id
   Str name
   Int age
}

which would generate a make constructor, set default values and possibly make a hash/equals via reflection.

MoOm Sun 13 Nov 2011

I think the key issue here is that you want a non-nullable field because these fields shouldn't be set to null. But at the same time you aren't going to initialize them in the constructor? That seems like a fairly odd use case, maybe you can explain more? In these sorts of situations I actually think null is a better indicator of "not set yet" than something like empty string, but definitely would like to understand this issue more.

The case when I experience this the more is when I'm writing domain classes. I've found that I'm often pleased with the "natural" default value for these fields. For strings and lists in particular, I prefer to deal with empty strings and empty lists to express the "not set yet" idea. I feel it is easier to manipulate and less dangerous than using null.

brian Sun 13 Nov 2011

or strings and lists in particular, I prefer to deal with empty strings and empty lists to express the "not set yet" idea. I feel it is easier to manipulate and less dangerous than using null.

Remember the entire goal here is for the compiler to warn you that you forgot to set a non-nullable field. Having the compiler not warn you, but just pick a default pretty much defeats the whole purpose of what we have built with non-nullable fields.

Let me ask you this, in your cases do you declare an it-block constructor:

new make(|This| f) { f(this) }

That would let you avoid initializing those fields and require them to be set by the constructor caller. If you don't do that can you explain why? Is this something to tackle with better constructors perhaps?

MoOm Wed 16 Nov 2011

Hi Brian,

Most of the time, I don't define an it-block constructor as it moves check for non-affected fields to runtime instead of compile-time. I only use it-block constructor with const classes as construction-time is the only moment I can affect those const fields.

But I do often use the it-block after the object is affected to change non-default fields. Here is an example:

class Person
{
  Str firstname := ""
  Str lastname := ""
  Person children := [,]
  Date? birthday
}

 personWithFirstname := Person() { firstname = "John" }
 personWithChild := Person() { children = [personWithFirstname] }

Note I used a nullable field for the birthday as their is no natural default/empty value for a Date. I guess "" and \[,] are probably intuitive default values for Str and List as they are container types, and for container types, emptiness expresses the idea that the variable is clean/non-affected.

I'm used to C++. In C++ when you declare a not-affected variable, its default constructor is called. I think it is probably a bit too much for Fantom but the defVal alternative works pretty well here in my opinion. Most implementations of defVal will return a static const object so there will be almost no overhead (unlike with calling default constructor).

qualidafial Wed 16 Nov 2011

+1 to coming up with some defaulting strategy, but -1 to using defVal.

Date is a perfect counterexample: Date.defVal is 2000-01-01. I doubt that a reasonable default date exists.

I'm observing that all the values being given as example default values are falseys: false, zero, empty string/list/map, etc.

One more thing: I don't want to lose the ability to force clients to specify a value. If we do introduce this behavior, I would prefer it be opt-in with some new keyword or symbol to indicate that default values are being assigned.

Example:

class Person
{
  Str! firstName
  Str! lastName
  Person[]! children
}

I'm not necessarily advocating the !, it's just the first thing that came to mind.

qualidafial Thu 17 Nov 2011

My last comment was a little disjointed, so allow me to fill in the blanks:

Date is a perfect counterexample: Date.defVal is 2000-01-01. I doubt that a reasonable default date exists.

That is, Date is one of those classes where it doesn't really make sense to choose a default value for everybody, since a reasonable default is likely to change from one use case to another. 2000-01-01 (the value of Date.defVal) might be a good default for a doomsday field, but not for e.g. a last-modified field.

The compiler should only supply a default if it is clearly a reasonable value for 99% of use cases.

I'm observing that all the values being given as example default values are falseys: false, zero, empty string/list/map, etc.

This might make a good rule of thumb for determining whether a type should have a compiler-supplied default: is there a natural "falsey" for this type?

For a list, sure: the empty list.

For a Person? Probably not.

MoOm Thu 17 Nov 2011

Date is one of those classes where it doesn't really make sense to choose a default value for everybody.

I agree with you that there is no natural default-value for Date. In this sense, I think the defVal method in Date should be removed.

The compiler should only supply a default if it is clearly a reasonable value for 99% of use cases.

On this point, I think it's up to the developer who wrote the class to decide whether or not he wants to allow default affectation on variables of this type (by implementing the defVal as I propose, or by another way but defVal sounds like a good name for this).

This might make a good rule of thumb for determining whether a type should have a compiler-supplied default: is there a natural "falsey" for this type?

I agree on this!

andy Thu 17 Nov 2011

Would have to agree here this feature exists most importantly to force fields to be configured. If you don't do that, and you don't have a reasonable default value, whats the difference with null?

Something like this might be useful for quick and dirty scripting - but it would compromise the language in practice - doesn't seem to outweigh a few extra keystrokes ;)

qualidafial Thu 17 Nov 2011

What exactly is the purpose of defVal? The only place I see it referred to in the docs is sys::Type#make.

I thought there was some compiler magic associated with it, but I didn't find anything.

brian Fri 18 Nov 2011

What exactly is the purpose of defVal?

You are right - it exists primarily for Type.make support for types which don't have a natural constructor (usually atomic, const types like Str, Date, etc). It is very handy when you want to pass around Types, but have easy way to get some instance to use for reflection, populating a field editor, etc. All about reflective/meta sort of programming:

fansh> [Bool#, Int#, Float#, Str#, Uri#].each |t| { echo(t.make->toCode) }
false
0
0.0f
""
``

Regarding this whole topic, as I said before the whole idea here doesn't jive with a) readable code and b) making sure compiler warns you that a non-nullable field isn't being set. You can't have the compiler magically initialize a field and warn you that you forgot to set it at the same time.

I think the deeper root problem is our lack of a mechanism to generate boiler plate type of constructors. We've always said we've wanted to do it, just never came up with suitable design.

MoOm Sun 20 Nov 2011

Regarding this whole topic, as I said before the whole idea here doesn't jive with a) readable code and b) making sure compiler warns you that a non-nullable field isn't being set. You can't have the compiler magically initialize a field and warn you that you forgot to set it at the same time.

There is already no warning for primitive-type or nullable-type variables not being set, and I've never seen anyone complain about it. I'm not sure many bugs would have been avoided if we had to add := 0 or := null each time we declare such a variable. Nor that it would have made code more readable. So why is this different for non-primitive types (only when it makes sense: Str, List, Map...)? My opinion is that when you declare a primitive/nullable variable, either you are fine with the default value or you will think about setting it.

But I'm totally fine with this idea not "making it" in Fantom! :)

Login or Signup to reply.