Nullable assigner?


#1
val a: String? = null
val b: String = ""    
fun main(vararg args: String){
    //if a variable is not null 
    a?.apply{ ... } //execute on object
    a?.also{_->... } //execute in general
    
    //if a variable is null
    (a?:b).exec()  //execute on similar object
    b?:run{ ... } //execute in general
}

So, on this moment, we can make oneliners of most of the nullable actions:

  • If a variable is not null:
    • execute function on object
    • execute function in general
  • if a variable is not nullable:
    • execute function on similar object if null
    • execute function in general

The only thing I am missing is the assignment when something is null.
When the settter is lightweight, we can use a = a?:b.
When the setter is not lightweight or perform side effects, we have a problem using the syntaxis above.

Therefore I prupose to make for the nullable assigner an exception in the assignment and add the following operator:

(a ?:= b).exec() mapping to {a ?: b.also{a = b} }.exec()

an example in the real world:

(onDockListeners ?:= mutableListOf()).apply{}
 //which maps to
(onDockListeners?:mutableListOf().also{onDockListeners = it}).apply{}
//or due to not being type inferenced (yet)
(onDockListeners?:mutableListOf<(UIComponent)->Unit>().also{onDockListeners = it}).apply{}

Conditional assignment operator proposal
#2

Unfortunately for your suggested syntax, though, there currently exists no conventional syntax for an operation only performed if an operand is null; only for if it isn’t. Per the current pattern, a ?= b would mean “if a is not null, assign b to it”, whereas what you want it to mean is “if a is null…”.

Even if were agreed that your use case would make more sense to have that operator for, I don’t see Kotlin using the same syntax pattern to mean something different in just one case out of convenience. The Kotlin team has kept a very respectable commitment to thinking beyond their self, to only do things when a proper way to accomplish it can be found.


#3

Yep, the syntax is inconsistent with the rest.
the way to do something when it is null is ?:
This means the operator should be ?:=.

Fixed it in the main post


#4

Wow. Well that’s an interestingly unexpected countersuggestion for a syntax…

Maybe when I’m at a desktop and can better see if it is awkward or clunky when used in actual code, I’ll post some more feedback.

In the meantime, I’ll toss out some quick suggestions:

  1. Possibly quickly review the code excerpts you posted. Although my brain was fried at the time, I remember that when I read your post, the “mappings” and other code appeared to either be incorrect (meaning I didn’t believe they actually did what you expected them to do), or I was alternatively just missing what it was you were trying to show us. Again, my brain was very tired at the moment, so maybe not. If so, you’ll want to fix the code so people don’t act like humans and ignore your suggestion for the wrong reasons.

  2. Additionally, if this is important to you, express through more than one example why this would make your day coding in Kotlin better. Different coders use different coding patterns and may not immediately understand that this is something that would be useful, simply because it doesn’t affect their personal coding routines – or because they don’t realize that it does. Describe a few scenarios (including code) where, for example, the setter is an expensive operation, to hopefully get some more recognition from the current onlookers. (Bonus points if, as it might have looked like earlier, the scenario that has you wanting this is a natural part of working with Android and thus affects an entire invested community)

Cheers and good luck.


#5

The main reason why I posted this is that I found a lot of the following code samples:

if(a != null) a = func()
a!!.exec()

I wanted to refactor this, but right now I need to check four things before I can refactor:

  • Is setA or func expensive?
  • Does setA handle assigning the same variable to it well?
  • Does func have side effects?
  • Does a need to be set, before calling exec on it.

When one of these reasons is true, only the nullable assigner or where it is mapped to is possible.

I will search for founded reasons of this and then I will post it here.


Conditional assignment operator proposal
#6

Btw, that was one of the motivations to add also to the standard library. This way you can replace:

if(a != null) a = func()
a!!.exec()

with a one-liner

(a ?: func().also { a = it }).exec()

You suggest to further simply it to:

(a ?:= func()).exec()

It definitely “reads” properly, though I, personally, is not yet convinced that is better enough to warrant addition of a special syntactical construct (even stdlib function also was debatable). Anyway, I’d suggest you to post your idea to issue tracker: http://kotl.in/issue

P.S. I can see that the chief argument against this new construct is going to be the fact that all Kotlin’s assignments (including compound assignment operators) are not expressions. You cannot write (a = b).exec() in Kotlin, so making only “elvis assignment operator” ?:= to be used as expression would be quite radical and inconsistent.


#7

If you make one-liners and a minimal number of variables goals, you will get difficult to read code like this IMHO. The assignment happens somewhere in the middle here. This is a side effect, and those should be avoided:

My analysis is this:

  1. I have a variable that may be null.
  2. If it is null, it must be given a default value, so I have a variable that cannot be null.
  3. I want to invoke a function on the second variable.

And so I would write it as this. Yes, 2 lines (each having its own purpose) and an additional variable, but the intention is very clear and the code is very short and readable:

val guaranteedA = a ?: func()
guaranteedA.exec()

#8

When guaranteeda is a field, you need to have additional code:

Var temp = a? : func() 
a = temp
temp.exec()

With “one-liners” i would write it like:

(a? : func().also{
     a = it;
}).exec()

which looks the same.
with the operator, it would look like

(a ?:= b).exec()

Which I personally find clearer.

Now, Iets say we need to do two things with func. Then your code will be:

 var temp = a
 if(temp == null){
     temp = func()
     temp.setup()
     temp.start()
 }
 a = temp
 temp.build("dog")

While the special syntaxis uses:

(a?: func().also{
     a = it
     it.setup()
     it.start()
}).build("dog")

Which for me is clearer.

The thing I don’t like about this is that assigning the return type to a is now one of the things done on func, during the building of something similar to a.

With the operator it would look like

(a ?:= func().also{
    it.setup()
    it.start()
}).build("dog")

Where the assignment is removed from the “setting-up-block” , to a more important position.


#9

If the variable that may be null is a field, I think you should change the design and add a wrapper that will create the default value when the value is retrieved but has not been set yet. Something like initialValue() from TheadLocal. Yes, again way more code than what you want, but with a very clear intent, and using the powerful language features already present in Kotlin:

class X {
    private val guaranteedA = GuaranteedValue<AType> {
        func().apply {
            setup()
            start()
        }
    }

    fun setA(a: AType) {
        guaranteedA.set(a)
    }

    fun useA() {
        guaranteedA.get().build("dog")
    }
}

#10
a = a ?: func()

A pretty smart compiler should avoid the useless assignment.


#12

Disagree, although it can be a bad design, setA can do more than just setting A. In that case, the assignment can not be avoided.

@jstuyts
I would use by to avoid the .get() and .set() :wink:
But what if you have different default-values or actions to apply, based on for example:

  • The composition of the class
  • The method being called
  • The params being used

#13

Without actual code it is hard to judge what you want to do. But it sounds like you are trying to manage state in a class where it does not belong.

If the creation of an object depends on state, pass the state to the factory method.

If the default value depends on which function gets called first, modify the design of GuaranteedValue to accept a block that return the value.


#14

And now we combine this with a consumer, or a cleanup, where the field will be set to null.


#15

Again, without actual code, I cannot comment on your design. And again, it sounds like you need to put all this value management in separate classes. Yes, this will require additional code, but it will make the code of the value-using class a lot simpler.

The example that you give in the original post is silly: Postponing the allocation of a mutable list because you may never need it. Simply instantiate a list and your variable will never be null. If you really don’t like instantiation, write a simple function. Note: not thread-safe:

private fun getGuaranteedOnDockListeners(): MutableList<UIComponent> {
    if (onDockListeners == null) {
        onDockListeners = mutableListOf()
    }
    // !! is a hint that the code is not idiomatic Kotlin
    return onDockListeners!!
}

Now you can simply do:

getGuaranteedOnDockListeners().apply {}

Another option to avoid instantiation is to use immutable collections. There will be only 1 instance of an empty collection that can be reused in all situations.


#16

I hate to admit it, but I think you won this round.
I don’t know an example of a class like this where there is no bad design:

class Clazz{
    private var fieldVal: A? = null

    fun setIfNull(value: A){
        fieldVal ?:= value.also{value->
            //do A
        }.exec()
    }

    fun setIfNull(value: B){
        fieldVal ?:= value.also{value->
            //do B
        }.exec()
    }

   fun clear(){
       fieldVal = null
   }
}

This means oneliners are not superior to normal code, but just a preference-choice.
Having said that, when we want want to use oneliners, to assign something to a nullable field, there are two ways:

a = a ?: b

Here we have problems when:

  • setA is expensive.
  • setA has side-effects, even when the same value is set.
  • we want to continue with a afterwards(we need to use !!

the way to deal with this is

(a ?: func().also { a = it }).exec()

Which is not very clean.
Therefor the operator ?:= is proposed, with the drawback that this will be the only assignment expression in the language…


#17

I’d rather call it “bearded elvis operator” :wink:


#18

I like the suggestions. I think this is useful, simple and clean the code.


#19

Been doing a lot of Dart recently that has this using ??= operator and have found it very useful there.