Corda State Evolution - nullable vs. default properties - kotlin

In Corda, adding new properties to states (state evolution) requires that new properties are nullable in order to remain backwards compatible with previous versions of the state.
data class Version1DummyState(
override val participants: List<AbstractParty>
) : ContractState
data class Version2DummyState(
override val participants: List<AbstractParty>,
val myNewProperty: String? = null
) : ContractState
Since Kotlin also supports properties with default values, I'd like to know why state evolution is restricted to properties of nullable types only, but not non-nullable properties, provided that those properties have a default value?
data class Version2DumyState(
override val participants: List<AbstractParty>,
val myNewProperty: String = "Hello, world."
) : ContractState
My rationale for asking this came from looking at the implicit upgrade sample, in which the obligation state is upgraded to allow the obligor to default on their obligation. true and false accurately represent whether the obligor has defaulted, but null does not. The ability to upgrade with a default value of false seems more natural than using a nullable field.

I think you can make that work by marking the constructor with #JvmOverloads like this:
data class DummyState #JvmOverloads constructor(
override val participants: List<AbstractParty>,
val myNewProperty: String = "blah"
) : ContractState
(don't put version numbers into state class names)
The #JvmOverloads makes the "old" constructors visible to the deserialisation engine for matching.
But, it's probably better to be explicit here with a line of code like:
val inDefault: Boolean get() = myNewProperty ?: false
or
fun priceOr(default: Amount<Currency> = 0.USD) get() = price ?: default
if you really want this.
Backwards compatibility and default values need to be treated quite carefully. A common mistake is to use a default value of zero/empty string/false for newly introduced fields, even when those values are semantically meaningful to the app. That is, knowing that the old message didn't specify something is valuable information that shouldn't be lost or replaced with fragile sentinel values. Consider the new field "price". Prices can't be negative, so perhaps a developer sets a default value of zero. But pricing something as free is a meaningful thing to do - maybe not in today's business scenario, but perhaps tomorrow? Now you have a problem.
Kotlin's type system and syntax is very good at dealing with missing values. It's so easy to substitute a default at the use-site using the ?: operator, I'd be worried about establishing a convention of always supplying a default at the construction site that junior devs follow without being aware of the potential consequences. Explicitly exposing the fact that a default may be substituted forces people to think about whether that's really logical.

Related

Is it possible to pass null type in place of generic type parameter?

I am going to use the following method from Spring Data Kotlin extensions:
inline fun <reified T : Any> MongoOperations.bulkOps(bulkMode: BulkMode, collectionName: String? = null): BulkOperations
The question is: can I somehow avoid specifying T assuming I do not want to provide entity class name (that's because I will explicitly specify collectionName, and in this case class type can be null). I would like to type something like:
val ops = mongoTemplate.bulkOps<null>(BulkOperations.BulkMode.UNORDERED, collectionName = "i_know_better")
Is there a type literal for null with which I can parameterize bulkOps?
I think the short answer is no.
You seem to confuse types with values. null is a value and not a type so it cannot be used as a type in generic methods.
In your specific example, even if you could use null, looking at the code what would you expect to happen?
#Suppress("EXTENSION_SHADOWED_BY_MEMBER")
inline fun <reified T : Any> MongoOperations.bulkOps(bulkMode: BulkMode, collectionName: String? = null): BulkOperations =
if (collectionName != null) bulkOps(bulkMode, T::class.java, collectionName)
else bulkOps(bulkMode, T::class.java)
As you can see there's always T::class.java being called. What would be the result of null::class.java?
I'm unfamiliar with the Spring Data so I can't really provide an alternative, but I'd say you either need to search for another method or use an appropriate class here. The generic type is marked as T : Any so presumably it can be any non-nullable type. I wonder if Unit would work. Again, I'm not sure what this class is used for.
To answer the question in general, you can use Nothing? to represent the type that only contains the value null.
That being said, as #Fred already said, the method you're considering here explicitly states T : Any, meaning only non-nullable types are allowed. And it makes sense given that the function is accessing the class of T.

Translate API response to nullable String

An API I consume returns (among other fields) a mandatory telephone1 and an optional telephone2. However, the JSON I fetch always contains both fields and a missing entry is displayed as an empty string.
{
"telephone1": "+1 555 1234",
"telephone2": ""
}
When the response is mapped to a pojo, is it preferable to translate the empty string to null? Such that:
data class(
val telephone1: String,
val telephone2: String?
}
To me, this better communicates the possible states. Should I, though? Are there drawbacks?
At the first sight, problem boils down to different checks before further data processing: x == null or x.isEmpty(). But while nullability check is generally enforced by kotlin compiler (unlike unemptiness), it seems to be a better option.
But there are still some situations when usage of null (without any compiler errors) may lead to problems in runtime (mainly related to interop with languages without enforced nullability): like implicit convertion of null to literal string "null" (when concatenating with another string), or even NPE when passed to method accepting String! (platform type) and not annotated properly.
Sticking to DDD principles, the better option would be declaration of separate datatypes:
sealed class OptionalPhoneNumber
data class PhoneNumber(val value: String) : OptionalPhoneNumber() //You may also add some checks in init block that `value` is really a phone number, not just a set of random chars
object EmptyPhoneNumber : OptionalPhoneNumber()
and defining your data class as:
data class Data (
val telephone1: PhoneNumber,
val telephone2: OptionalPhoneNumber
)
Type system will enforce you to do x is PhoneNumber checks, and thanks to smart casts it's further usage will be type-safe:
if (telephone2 is PhoneNumber) {
println(telephone2.value)
}

How does overload work in Kotlin without default params

I have a function that may take one or two parameters. In Java I would simply overload:
public myMethod( Cat cat, Dog dog){…}
public myMethod( Cat cat){…}
I understood that Kotlin has default params that would make overloading unnecessary. But these are objects for which I really know no default. So how do I proceed? And I don’t want to claim it’s nullable just for the sake of making null the default value. Any options I’m not seeing?
basically I don't want this
fun myMethod(cat:Cat, dog:Dog?=null) //it's never really nullable so don't want to pretend
I understood that Kotlin has default params that would make overloading unnecessary.
They don't; they make a specific (and very common in Java) usage of overloading unnecessary. If in Java you'd write
public myMethod(Cat cat){
myMethod(cat, new Dog(...)) // or myMethod(cat, null)
}
then in Kotlin you'd use a default argument. If you don't, then you use overloaded methods just like in Java, as mightyWOZ's answer shows.
Method (or function) overloading in kotlin works the same way as it works in java. That is you can specify multiple functions with same name but with different signature.
From Kotlin language specification
Kotlin supports function overloading, that is, the ability for several
functions of the same name to coexist in the same scope, with the
compiler picking the most suitable one when such a function is called.
So in your case if you don't want to use default parameters, then you can specify two different functions with same name but with different arguments.
So your java code can be converted to kotlin as.
fun myMethod(cat: Cat, dog: Dog){…}
fun myMethod(cat: Cat){…}
And you can call the overloaded functions as
var dog = Dog()
var cat = Cat()
myMethod(dog,cat)
myMethod(cat)
You can think as Dog is nullable in your in your Java method public myMethod( Cat cat){…}. There is no dog so it can be treated as null, since it doesn't exist. Then just check if it is null in kotlin and proceed as if it never was there.
First let's see what the Kotlin Language Documentation says:
Prefer declaring functions with default parameter values to declaring
overloaded functions.
And I don’t want to claim it’s nullable just for the sake of making
null the default value.
Then don't. So, you have to have some way of initializing dog.
You can specifiy a default value for dog right in the parameter list.
fun myMethod(cat: Cat, dog: Dog = Dog(...)) {
// ...
}
If you don't have a way of initializing dog when calling myMethod, it is not such a bad idea (as you might think) to make the parameter nullable. null means the value is absent and this is exactly the case.
fun myMethod(cat: Cat, dog: Dog? = null) {
// handle nullable dog
}

Internal fileds (columns) in Room's Entity

I'd like to mark some Room entity's properties as internal. E.g.
#Entity(tableName = "users")
class User {
// ...
#ColumnInfo(name = "admin_id")
internal var adminId: String? = null
}
However, this produce compile errors like:
Error:(10, 1) error: Cannot find getter for field.
The only way how to make this works seems to use lateinit modifier, though, it can't be used for nullable neither primitive fields.
I've tried a "hack": a private field with internal getter/setter, but that doesn't work either.
The compiled generated version obviously adds some suffix to the generated methods (setAdminId$sdk_debug) that doesn't work with room. The "lateinited" field's setters/getters have this suffix too, but the field stay itself public.
Is there any way how to make columns internal?
It seems its getting supported in latest Room 2.5.0-alpha01
Old answer: I didn't solve this and I have to define new set of entities and mapper between them.
The internal names get mangled by Kotlin, so I made it work by just making sure the correct name is used with #JvmName:
#Entity(tableName = "users")
class User {
// ...
#ColumnInfo(name = "admin_id")
#get:JvmName("adminId")
internal var adminId: String? = null
}
Note: This might make it easier to accidentally use this from Java then.

POJO field declaration, set to init value or null

In my POJO, i usually declare the fields like this
class SampleModel {
var field1: String? = null
var field2: Int? = null
<more fields here>
}
If I declare like this, I will need to check if the field is null or not.
But if like this:
class SampleModel {
var field1 = ""
var field2 = 0
<more fields here>
}
I can use it directly.
Does it matter on which declaration to use?
Like in terms of memory, performance, or anything that a good practice to do?
Lets say I have 100 fields.
Then I use Moshi or Gson for the deserializer.
In the end even beyond performance considerations is quality of code. If the fields will never actually be null in your use cases then I would not use nullable types. You are forcing yourself to do extra work (albeit Kotlin makes it somewhat easier) to cater for a scenario that it seems like you're saying isn't possible?
On the other side; if a field in the serialized were to be missing in this scenario you may not immediately catch it because the field in the class has a default value. If you're confident in the data being provided though this shouldn't be a problem.
IMO, memory and performance should not be taken into account when you are choosing between these two approaches. It depends on how you want the class to behave. To have a default value (approach 2) or null (approach 1). It matters when you need to handle logic of this class.